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

LeakCanaryって何？って人は下記参考
[LeakCanaryでメモリリークを検出する](http://qiita.com/rejasupotaro/items/4a8cfe0abda2d83145dd)

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

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

# Slackにメモリリーク検出を飛ばす
LeakCanaryにサンプルがあったのですが
動かなかったので作り直しました。

* build.gradle

```groovy: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

```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にサービスを追記します。

```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

```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初期化

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