LeakCanary

简单说明:
LeakCanary:可以检测出各种各样的内存泄露,包括:WebView 导致的内存泄露、资源未关闭导致的内存泄露、非静态匿名内部类导致的内存泄露、Handler 导致的内存泄露等等。LeakCanary 默认只监测 Activity,目前已经在工程中增加针对 Fragment 的监测。

参考文档:
1、github 地址:对于网上说的 release vxx 版本就是对应 tag 的版本,目前使用下来,没有问题的就是 v1.5.1 版本
2、中文使用说明:该文档简要说明了如何使用 leakcanary

具体处理方式

一、增加上传到数据表处理

1、app/build.gradle 中引入依赖包

具体的引入版本,可根据提供的版本而定

sed -i "/dependencies {/a\    dailyImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'\n    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'\n    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'" app/build.gradle
2、在 AndroidManifest.xml 中注册上传到数据表的 service
sed -i "/tools:replace=\"android:label\"/a\        <service android:name=\"LeakUploadService所在包名.LeakUploadService\" android:exported=\"false\"\/>" app/src/main/AndroidManifest.xml
3、在项目的主 Application 类中安装 LeakCanary
sed -i "/在哪个后面追加import/a\  \nimport com.squareup.leakcanary.*;" 主Application.java文件

sed -i "/public class TPApplication extends Application/a\    private RefWatcher refWatcher;\n    protected RefWatcher installLeakCanary(){\n        return LeakCanary.refWatcher(this).listenerServiceClass(LeakUploadService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()).buildAndInstall();\n    }\n    public static RefWatcher getRefWatcher(Context context) {\n        TPApplication application = (TPApplication) context.getApplicationContext();\n        return application.refWatcher;\n    }" 主Application.java文件

sed -i "/super.onCreate/a\        refWatcher = installLeakCanary();" 主Application.java文件
4、增加针对 Fragment 的监测
sed -i "/在哪个后面追加import/a\  \nimport com.squareup.leakcanary.*;" 主Fragment.java文件

sed -i "/public class BaseFragment extends Fragment/a\    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        RefWatcher refWatcher = TPApplication.getRefWatcher(getActivity());\n        refWatcher.watch(this);\n    }" 主Fragment.java文件
5、特别:上传到数据库的处理方法 LeakUploadService
package 包名,与主application在一个包;

import com.squareup.leakcanary.*;
import 提供的网络请求处理方式.retrofit.NetHandler;
import org.json.JSONObject;
import java.util.HashMap;

public class LeakUploadService extends DisplayLeakService {
    private static final String TAG = "RDY";

    @Override
    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
        if (!result.leakFound || result.excludedLeak){
            return;
        }
        String className = result.className.toString();
        TLog.i(TAG,className);
        String pkgName = leakInfo.trim().split(":")[0].split(" ")[1];
        String pkgVer = leakInfo.trim().split(":")[1];
        String leakDetail = leakInfo.split("\n\n")[0] + "\n\n" + leakInfo.split("\n\n")[1];
        String[] infoDetailArray = leakInfo.trim().split("\n\n")[0].split("\n");
        String infoDetail = "";
        for (int i = 1; i < infoDetailArray.length; i++) {
            infoDetail += infoDetailArray[i];
        }
        infoDetail = infoDetail.replaceAll("\\[\\d*\\]", "[]");
        Integer leakKey = infoDetail.trim().hashCode() & 0x7FFFFFFF;
        TLog.i(TAG,leakKey.toString());
        String url = "处理存储数据到数据表的api";
        TLog.i(TAG,url);
        HashMap<String, Object> map = new HashMap<>();
        map.put("className", className);
        map.put("pkgName", pkgName);
        map.put("pkgVer", pkgVer);
        map.put("leakDetail", leakDetail);
        map.put("leakKey", leakKey);
        String body = new JSONObject(map).toString();
        TLog.i(TAG,body);
        String response = NetHandler.postRequest(url, body);
        TLog.i(TAG,response);
    }
}

6、特别:存储到数据表的 api
sql_insert_leakinfo = '''insert into leakcanary(leakKey,className,pkgName,pkgVersion,leakDetail,count) 
                        values(%s,%s,%s,%s,%s,1)  on duplicate key update count=count+1'''

class PostLeakCanaryDataHandler(client.JinjaBaseHandler):
    executor = ThreadPoolExecutor(10)
    @run_on_executor
    def post(self):
        body = json.loads(self.request.body.decode("utf-8"))
        logging.info(body['className'])
        logging.info(body['leakDetail'])
        logging.info(body['leakKey'])
        db_operator.insertOne(sql_insert_leakinfo, (int(body['leakKey']), body['className'], body['pkgName'],
                                                    body['pkgVer'], body['leakDetail']))
        self.make_return(const_var.RESULT_CODE_SUC, '')
二、不安装 leak 插件在手机上

参考文档:https://www.jianshu.com/p/e0ec888f0bc2

1、参考 # 一 sed 操作的变化如下:
sed -i "/dependencies {/a\    dailyImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'\n    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.1'\n    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'" app/build.gradle

sed -i "/tools:replace=\"android:label\"/a\        <service android:name=\"包名.LeakUploadServiceWithoutDisplay\" android:exported=\"false\"\/>" app/src/main/AndroidManifest.xml


sed -i "/package 包名/a\  \nimport com.squareup.leakcanary.*;" 主Application.java文件

sed -i "/public class TPApplication extends Application/a\    private RefWatcher refWatcher;\n    public static RefWatcher getRefWatcher(Context context) {\n        TPApplication application = (TPApplication) context.getApplicationContext();\n        return application.refWatcher;\n    }"  主Application.java文件

sed -i "/super.onCreate/a\        refWatcher = LeakCanaryWithoutDisplay.refWatcher(this).listenerServiceClass(LeakUploadServiceWithoutDisplay.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()).buildAndInstall();\n        LeakCanaryWithoutDisplay.enableDisplayLeakActivity(this);\n" 主Application.java文件


sed -i "/package包名/a\  \nimport com.squareup.leakcanary.*;" 主Fragment.java文件

sed -i "/public class BaseFragment extends Fragment/a\    @Override\n    public void onDestroy() {\n        super.onDestroy();\n        RefWatcher refWatcher = TPApplication.getRefWatcher(getActivity());\n        refWatcher.watch(this);\n    }" 主Fragment.java文件


mkdir -p app/src/main/java/com/squareup/leakcanary/
2、隐藏 Icon、Toast、Notify 需要变更的文件,用于覆盖原始的类
a、LeakUploadServiceWithoutDisplay.java
package 包名;

import com.squareup.leakcanary.*;
import 网络请求处理类.retrofit.NetHandler;
import org.json.JSONObject;
import java.util.HashMap;


public class LeakUploadServiceWithoutDisplay extends DisplayLeakServiceWithoutNotification {
    private static final String TAG = "RDY";

    @Override
    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
        if (!result.leakFound || result.excludedLeak){
            return;
        }
        String className = result.className.toString();
        TLog.i(TAG,className);
        String pkgName = leakInfo.trim().split(":")[0].split(" ")[1];
        String pkgVer = leakInfo.trim().split(":")[1];
        String leakDetail = leakInfo.split("\n\n")[0] + "\n\n" + leakInfo.split("\n\n")[1];
        String[] infoDetailArray = leakInfo.trim().split("\n\n")[0].split("\n");
        String infoDetail = "";
        for (int i = 1; i < infoDetailArray.length; i++) {
            infoDetail += infoDetailArray[i];
        }
        infoDetail = infoDetail.replaceAll("\\[\\d*\\]", "[]");
        Integer leakKey = infoDetail.trim().hashCode() & 0x7FFFFFFF;
        TLog.i(TAG,leakKey.toString());
        String url = "上传到数据库的api";
        TLog.i(TAG,url);
        HashMap<String, Object> map = new HashMap<>();
        map.put("className", className);
        map.put("pkgName", pkgName);
        map.put("pkgVer", pkgVer);
        map.put("leakDetail", leakDetail);
        map.put("leakKey", leakKey);
        String body = new JSONObject(map).toString();
        TLog.i(TAG,body);
        String response = NetHandler.postRequest(url, body);
        TLog.i(TAG,response);

    }

}

b、LeakCanaryWithoutDisplay.java
/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.leakcanary;

import android.app.Application;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.util.Log;
import com.squareup.leakcanary.internal.DisplayLeakActivity;
import com.squareup.leakcanary.internal.HeapAnalyzerService;

import static android.text.format.Formatter.formatShortFileSize;
import static com.squareup.leakcanary.BuildConfig.GIT_SHA;
import static com.squareup.leakcanary.BuildConfig.LIBRARY_VERSION;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.isInServiceProcess;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.setEnabled;

public final class LeakCanaryWithoutDisplay {

  /**
   * Creates a {@link RefWatcher} that works out of the box, and starts watching activity
   * references (on ICS+).
   */

  public interface LeakCanaryCallBack {
    void onAnalysisResult(String result);
  }

  private static LeakCanaryCallBack sLeakCanaryCallBack;

  public static LeakCanaryCallBack getLeakCanaryCallBack() {
    return sLeakCanaryCallBack;
  }

  public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

  /** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
  public static AndroidRefWatcherBuilderWithoutToast refWatcher(Context context) {
    return new AndroidRefWatcherBuilderWithoutToast(context);
  }

  public static void enableDisplayLeakActivity(Context context) {
    setEnabled(context, DisplayLeakActivity.class, false);
  }

  /**
   * If you build a {@link RefWatcher} with a {@link AndroidHeapDumper} that has a custom {@link
   * LeakDirectoryProvider}, then you should also call this method to make sure the activity in
   * charge of displaying leaks can find those on the file system.
   */
  public static void setDisplayLeakActivityDirectoryProvider(
      LeakDirectoryProvider leakDirectoryProvider) {
    DisplayLeakActivity.setLeakDirectoryProvider(leakDirectoryProvider);
  }

  /** Returns a string representation of the result of a heap analysis. */
  public static String leakInfo(Context context, HeapDump heapDump, AnalysisResult result,
      boolean detailed) {
    PackageManager packageManager = context.getPackageManager();
    String packageName = context.getPackageName();
    PackageInfo packageInfo;
    try {
      packageInfo = packageManager.getPackageInfo(packageName, 0);
    } catch (PackageManager.NameNotFoundException e) {
      throw new RuntimeException(e);
    }
    String versionName = packageInfo.versionName;
    int versionCode = packageInfo.versionCode;
    String info = "In " + packageName + ":" + versionName + ":" + versionCode + ".\n";
    String detailedString = "";
    if (result.leakFound) {
      if (result.excludedLeak) {
        info += "* EXCLUDED LEAK.\n";
      }
      info += "* " + result.className;
      if (!heapDump.referenceName.equals("")) {
        info += " (" + heapDump.referenceName + ")";
      }
      info += " has leaked:\n" + result.leakTrace.toString() + "\n";
      info += "* Retaining: " + formatShortFileSize(context, result.retainedHeapSize) + ".\n";
      if (detailed) {
        detailedString = "\n* Details:\n" + result.leakTrace.toDetailedString();
      }
    } else if (result.failure != null) {
      // We duplicate the library version & Sha information because bug reports often only contain
      // the stacktrace.
      info += "* FAILURE in " + LIBRARY_VERSION + " " + GIT_SHA + ":" + Log.getStackTraceString(
          result.failure) + "\n";
    } else {
      info += "* NO LEAK FOUND.\n\n";
    }
    if (detailed) {
      detailedString += "* Excluded Refs:\n" + heapDump.excludedRefs;
    }

    info += "* Reference Key: "
        + heapDump.referenceKey
        + "\n"
        + "* Device: "
        + Build.MANUFACTURER
        + " "
        + Build.BRAND
        + " "
        + Build.MODEL
        + " "
        + Build.PRODUCT
        + "\n"
        + "* Android Version: "
        + Build.VERSION.RELEASE
        + " API: "
        + Build.VERSION.SDK_INT
        + " LeakCanary: "
        + LIBRARY_VERSION
        + " "
        + GIT_SHA
        + "\n"
        + "* Durations: watch="
        + heapDump.watchDurationMs
        + "ms, gc="
        + heapDump.gcDurationMs
        + "ms, heap dump="
        + heapDump.heapDumpDurationMs
        + "ms, analysis="
        + result.analysisDurationMs
        + "ms"
        + "\n"
        + detailedString;

    return info;
  }

  /**
   * Whether the current process is the process running the {@link HeapAnalyzerService}, which is
   * a different process than the normal app process.
   */
  public static boolean isInAnalyzerProcess(Context context) {
    return isInServiceProcess(context, HeapAnalyzerService.class);
  }

  private LeakCanaryWithoutDisplay() {
    throw new AssertionError();
  }
}

c、DisplayLeakServiceWithoutNotification.java

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.leakcanary;

import android.app.PendingIntent;
import android.os.SystemClock;
import com.squareup.leakcanary.internal.DisplayLeakActivity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import static android.text.format.Formatter.formatShortFileSize;
import static com.squareup.leakcanary.LeakCanary.leakInfo;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.classSimpleName;
import static com.squareup.leakcanary.internal.LeakCanaryInternals.showNotification;

/**
 * Logs leak analysis results, and then shows a notification which will start {@link
 * DisplayLeakActivity}.
 *
 * You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult,
 * String)} to add custom behavior, e.g. uploading the heap dump.
 */
public class DisplayLeakServiceWithoutNotification extends AbstractAnalysisResultService {

  @Override
  protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    if (LeakCanaryWithoutDisplay.getLeakCanaryCallBack() != null) {
      LeakCanaryWithoutDisplay.getLeakCanaryCallBack().onAnalysisResult(leakInfo);
    }
    CanaryLog.d(leakInfo);

    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if (shouldSaveResult) {
      heapDump = renameHeapdump(heapDump);
//      resultSaved = saveResult(heapDump, result);
    }

//    PendingIntent pendingIntent;
//    String contentTitle;
//    String contentText;
//
//    if (!shouldSaveResult) {
//      contentTitle = getString(R.string.leak_canary_no_leak_title);
//      contentText = getString(R.string.leak_canary_no_leak_text);
//      pendingIntent = null;
//    } else if (resultSaved) {
//      pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
//
//      if (result.failure == null) {
//        String size = formatShortFileSize(this, result.retainedHeapSize);
//        String className = classSimpleName(result.className);
//        if (result.excludedLeak) {
//          contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
//        } else {
//          contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
//        }
//      } else {
//        contentTitle = getString(R.string.leak_canary_analysis_failed);
//      }
//      contentText = getString(R.string.leak_canary_notification_message);
//    } else {
//      contentTitle = getString(R.string.leak_canary_could_not_save_title);
//      contentText = getString(R.string.leak_canary_could_not_save_text);
//      pendingIntent = null;
//    }
//    // New notification id every second.
//    int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
//    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);
  }

  private boolean saveResult(HeapDump heapDump, AnalysisResult result) {
    File resultFile = new File(heapDump.heapDumpFile.getParentFile(),
        heapDump.heapDumpFile.getName() + ".result");
    FileOutputStream fos = null;
    try {
      fos = new FileOutputStream(resultFile);
      ObjectOutputStream oos = new ObjectOutputStream(fos);
      oos.writeObject(heapDump);
      oos.writeObject(result);
      return true;
    } catch (IOException e) {
      CanaryLog.d(e, "Could not save leak analysis result to disk.");
    } finally {
      if (fos != null) {
        try {
          fos.close();
        } catch (IOException ignored) {
        }
      }
    }
    return false;
  }

  private HeapDump renameHeapdump(HeapDump heapDump) {
    String fileName =
        new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS'.hprof'", Locale.US).format(new Date());

    File newFile = new File(heapDump.heapDumpFile.getParent(), fileName);
    boolean renamed = heapDump.heapDumpFile.renameTo(newFile);
    if (!renamed) {
      CanaryLog.d("Could not rename heap dump file %s to %s", heapDump.heapDumpFile.getPath(),
          newFile.getPath());
    }
    return new HeapDump(newFile, heapDump.referenceKey, heapDump.referenceName,
        heapDump.excludedRefs, heapDump.watchDurationMs, heapDump.gcDurationMs,
        heapDump.heapDumpDurationMs);
  }

  /**
   * You can override this method and do a blocking call to a server to upload the leak trace and
   * the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link
   * AnalysisResult#excludedLeak} first.
   */
  protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
  }
}

d、AndroidRefWatcherBuilderWithoutToast.java

package com.squareup.leakcanary;

import android.app.Application;
import android.content.Context;
import java.util.concurrent.TimeUnit;

import static com.squareup.leakcanary.RefWatcher.DISABLED;
import static java.util.concurrent.TimeUnit.SECONDS;

/** A {@link RefWatcherBuilder} with appropriate Android defaults. */
public final class AndroidRefWatcherBuilderWithoutToast extends RefWatcherBuilder<AndroidRefWatcherBuilderWithoutToast> {

  private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);

  private final Context context;

  AndroidRefWatcherBuilderWithoutToast(Context context) {
    this.context = context.getApplicationContext();
  }

  /**
   * Sets a custom {@link AbstractAnalysisResultService} to listen to analysis results. This
   * overrides any call to {@link #heapDumpListener(HeapDump.Listener)}.
   */
  public AndroidRefWatcherBuilderWithoutToast listenerServiceClass(
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

  /**
   * Sets a custom delay for how long the {@link RefWatcher} should wait until it checks if a
   * tracked object has been garbage collected. This overrides any call to {@link
   * #watchExecutor(WatchExecutor)}.
   */
  public AndroidRefWatcherBuilderWithoutToast watchDelay(long delay, TimeUnit unit) {
    return watchExecutor(new AndroidWatchExecutor(unit.toMillis(delay)));
  }

  /**
   * Sets the maximum number of heap dumps stored. This overrides any call to {@link
   * #heapDumper(HeapDumper)} as well as any call to
   * {@link LeakCanary#setDisplayLeakActivityDirectoryProvider(LeakDirectoryProvider)})}
   *
   * @throws IllegalArgumentException if maxStoredHeapDumps < 1.
   */
  public AndroidRefWatcherBuilderWithoutToast maxStoredHeapDumps(int maxStoredHeapDumps) {
    LeakDirectoryProvider leakDirectoryProvider =
        new DefaultLeakDirectoryProvider(context, maxStoredHeapDumps);
//    LeakCanary.setDisplayLeakActivityDirectoryProvider(leakDirectoryProvider);
    return heapDumper(new AndroidHeapDumperWithoutToast(context, leakDirectoryProvider));
  }

  /**
   * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
   */
  public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
    }
    return refWatcher;
  }

  @Override
  protected boolean isDisabled() {
    return LeakCanary.isInAnalyzerProcess(context);
  }

  @Override
  protected HeapDumper defaultHeapDumper() {
    LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
    return new AndroidHeapDumperWithoutToast(context, leakDirectoryProvider);
  }

  @Override
  protected DebuggerControl defaultDebuggerControl() {
    return new AndroidDebuggerControl();
  }

  @Override
  protected HeapDump.Listener defaultHeapDumpListener() {
    return new ServiceHeapDumpListener(context, DisplayLeakServiceWithoutNotification.class);
  }

  @Override
  protected ExcludedRefs defaultExcludedRefs() {
    return AndroidExcludedRefs.createAppDefaults().build();
  }

  @Override
  protected WatchExecutor defaultWatchExecutor() {
    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
  }
}

e、AndroidHeapDumperWithoutToast

/*
 * Copyright (C) 2015 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.squareup.leakcanary;

import android.content.Context;
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.widget.Toast;
import com.squareup.leakcanary.internal.FutureResult;
import java.io.File;

import static java.util.concurrent.TimeUnit.SECONDS;

public final class AndroidHeapDumperWithoutToast implements HeapDumper {

  private final Context context;
  private final LeakDirectoryProvider leakDirectoryProvider;
  private final Handler mainHandler;

  public AndroidHeapDumperWithoutToast(Context context, LeakDirectoryProvider leakDirectoryProvider) {
    this.leakDirectoryProvider = leakDirectoryProvider;
    this.context = context.getApplicationContext();
    mainHandler = new Handler(Looper.getMainLooper());
  }

  @SuppressWarnings("ReferenceEquality") // Explicitly checkinnamed null.
  @Override
  public File dumpHeap() {
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

    if (heapDumpFile == RETRY_LATER) {
      return RETRY_LATER;
    }

    FutureResult<Toast> waitingForToast = new FutureResult<>();
    showToast(waitingForToast);

    if (!waitingForToast.wait(5, SECONDS)) {
      CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
      return RETRY_LATER;
    }

    Toast toast = waitingForToast.get();
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      cancelToast(toast);
      return heapDumpFile;
    } catch (Exception e) {
      CanaryLog.d(e, "Could not dump heap");
      // Abort heap dump
      return RETRY_LATER;
    }
  }

  private void showToast(final FutureResult<Toast> waitingForToast) {
    mainHandler.post(new Runnable() {
      @Override
      public void run() {
        final Toast toast = new Toast(context);
        toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
        toast.setDuration(Toast.LENGTH_LONG);
        LayoutInflater inflater = LayoutInflater.from(context);
        toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
//        toast.show();
        // Waiting for Idle to make sure Toast gets rendered.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override
          public boolean queueIdle() {
            waitingForToast.set(toast);
            return false;
          }
        });
      }
    });
  }

  private void cancelToast(final Toast toast) {
    mainHandler.post(new Runnable() {
      @Override
      public void run() {
        toast.cancel();
      }
    });
  }
}

使用以上工程可以生成两种类型的 apk,
A、不同点:
1、安装 APP 的同时会把 LeakCanary 插件同时安装上去,就是 App 的图标后面跟着的 Leaks 图标。以后卸载 APP 的时候 LeakCanary 也会一起被卸载。此类型发生内存泄漏时会有 toast,通知栏会有提示,可以实时看到泄漏具体信息。
2、安装 APP 的同时不会把 LeakCanary 插件同时安装上去,发生内存泄漏时不会有 toast,通知栏不会有提示。
B、共同点:
在使用过程中检测出来的内存泄漏都会实时上传到数据库。


↙↙↙阅读原文可查看相关链接,并与作者交流