简单说明:
LeakCanary:可以检测出各种各样的内存泄露,包括:WebView 导致的内存泄露、资源未关闭导致的内存泄露、非静态匿名内部类导致的内存泄露、Handler 导致的内存泄露等等。LeakCanary 默认只监测 Activity,目前已经在工程中增加针对 Fragment 的监测。参考文档:
1、github 地址:对于网上说的 release vxx 版本就是对应 tag 的版本,目前使用下来,没有问题的就是 v1.5.1 版本
2、中文使用说明:该文档简要说明了如何使用 leakcanary
具体的引入版本,可根据提供的版本而定
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=\"LeakUploadService所在包名.LeakUploadService\" android:exported=\"false\"\/>" app/src/main/AndroidManifest.xml
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文件
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文件
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);
}
}
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, '')
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/
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);
}
}
/*
* 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、共同点:
在使用过程中检测出来的内存泄漏都会实时上传到数据库。