LoginSignup
8
11

More than 5 years have passed since last update.

LeakCanaryでメモリリーク検出をSlackに飛ばす

Last updated at Posted at 2017-01-02

LeakCanaryって何?って人は下記参考
LeakCanaryでメモリリークを検出する

JavaではGCでメモリリークなんてないという都市伝説を信じている人以外は
OutOfMemoryErrorのエラー対策にやったほうが良いです。

Androidでメモリリーク?

Androidの場合、ActivityやFragmentのライフサイクルが終わった後でもGCで解放されない変数がある場合に発生します。
Eight Ways Your Android App Can Leak Memory
そもそもメモリリークを意図的に引き起こすってどうやんの?って話なのですが
メモリリークを意図的に発生させる検証はLeakCanaryのSampleが一番良いです。
ちなみにAndroid SDKの内部にもMemoryLeak部分があったりしてて
その場合はExcludedという目印がLeakCanaryで検出時についてたりします。
(そんなものまで見つけてるSquareしゅごい・・・)

Slackにメモリリーク検出を飛ばす

LeakCanaryにサンプルがあったのですが
動かなかったので作り直しました。

  • build.gradle
app/build.gradle
dependencies{
    // Retrofit2
    compile 'com.squareup.okhttp3:okhttp:3.3.1'
    compile 'com.squareup.retrofit2:retrofit:2.0.2'
    compile 'com.squareup.retrofit2:converter-gson:2.0.2'
}
  • LeakUploadService.java
package com.example.daiki.androidtemplate;

import android.util.Log;

import com.squareup.leakcanary.AnalysisResult;
import com.squareup.leakcanary.DisplayLeakService;
import com.squareup.leakcanary.HeapDump;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;

import android.util.Log;

import com.squareup.leakcanary.AnalysisResult;
import com.squareup.leakcanary.DisplayLeakService;
import com.squareup.leakcanary.HeapDump;

import okhttp3.MediaType;
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;

// upload to slack
public final class LeakUploadService extends DisplayLeakService {


    public interface SlackApi {

        @Multipart
        @POST("/api/files.upload")
        Call<Void> uploadFile(@Part("token") RequestBody token,
                              @Part("file\"; filename=leak.hprof") RequestBody file, // file
                              @Part("filename") RequestBody filename,
                              @Part("title") RequestBody title,
                              @Part("initial_comment") RequestBody initialComment,
                              @Part("channels") RequestBody channels);

    }


    private SlackApi slackApi;
    private String TOKEN;
    private String MEMORY_LEAK_CHANNEL;

    @Override
    public void onCreate() {
        super.onCreate();
        slackApi = new Retrofit.Builder()
                .baseUrl(getString(R.string.slack_url))
                .build()
                .create(SlackApi.class);
        TOKEN = getString(R.string.slack_api_token);
        MEMORY_LEAK_CHANNEL = getString(R.string.slack_memory_leak_channel);
    }


    private static String classSimpleName(String className) {
        int separator = className.lastIndexOf('.');
        return separator == -1 ? className : className.substring(separator + 1);
    }

    @Override
    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {

        // 除外リストは無視する
        if (!result.leakFound || result.excludedLeak) {
            return;
        }
        super.afterDefaultHandling(heapDump,result,leakInfo);


        String name = classSimpleName(result.className);
        if (!heapDump.referenceName.equals("")) {
            name += "(" + heapDump.referenceName + ")";
        }



        String title = name + " has leaked";
        String initialComment = leakInfo;
        Log.d("leakinfo",leakInfo);
        Log.d("heapdump",heapDump.heapDumpFile.toString());
        Call<Void> request = slackApi.uploadFile(RequestBody.create(MediaType.parse("plain/text"), TOKEN),
                RequestBody.create(MediaType.parse("application/octet-stream"), heapDump.heapDumpFile),
                RequestBody.create(MediaType.parse("plain/text"), heapDump.heapDumpFile.getName()),
                RequestBody.create(MediaType.parse("plain/text"), title),
                RequestBody.create(MediaType.parse("plain/text"), initialComment),
                RequestBody.create(MediaType.parse("plain/text"), MEMORY_LEAK_CHANNEL));

        request.enqueue(new Callback<Void>() {
            @Override
            public void onResponse(Call<Void> call, Response<Void> response) {
                Log.d("SendRetrofit",response.toString());

            }

            @Override
            public void onFailure(Call<Void> call, Throwable t) {
                Log.e("ErrorRetrofit",t.toString());

            }
        });

    }
}

serviceなのでAndroidManifest.xmlにサービスを追記します。

<application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>


        <!-- 追記 -->
        <service android:name=".LeakUploadService" />

    </application>
  • strings.xml
<resources>
    <!-- Slack channel for send memory leak-->
    <string name="slack_url">https://slack.com</string>
    <string name="slack_api_token"></string><!-- チームアクセストークン -->
    <string name="slack_memory_leak_channel"></string><!-- 投稿チャンネル -->

</resources>

slack_api_tokenにはSlack APIトークン、
slack_memory_leak_channelにはチャンネル名
を入れてくださいな

  • MainApplication.java

LeakCanary初期化

public class MainApplication extends MultiDexApplication {


    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }

    protected RefWatcher installLeakCanary(Application application) {
        // Build a customized RefWatcher
        RefWatcher refWatcher = LeakCanary.refWatcher(application)
                .listenerServiceClass(LeakUploadService.class)
                .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
                .buildAndInstall();
        return refWatcher;
    }



    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        StrictMode.enableDefaults();
        installLeakCanary(this);



    }


}

ちなみにメモリリークのdumpファイルのアップロードは重いのでデバッグだけにしておいた方が無難です。

8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11