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ファイルのアップロードは重いのでデバッグだけにしておいた方が無難です。