1.背景

LeakCanary 是一种方便的内存泄露检查工具,与相对于用dump 内存 然后用MAT工具去分析,要简单便捷很多,我们需要了解如何使用这个工具及其背后的原理

2.基本使用

2.1

加入远程引用

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1'releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'

2.2

LeakCanary.install(this);

3原理追查

因为只有一行代码 LeakCanary.install(this); 我们从这个行进行追查

  public static RefWatcher install(Application application) {return refWatcher(application).listenerServiceClass(DisplayLeakService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()).buildAndInstall();}
public RefWatcher buildAndInstall() {if (LeakCanaryInternals.installedRefWatcher != null) {throw new UnsupportedOperationException("buildAndInstall() should only be called once.");}RefWatcher refWatcher = build();if (refWatcher != DISABLED) {if (watchActivities) {ActivityRefWatcher.install(context, refWatcher);}if (watchFragments) {FragmentRefWatcher.Helper.install(context, refWatcher);}}LeakCanaryInternals.installedRefWatcher = refWatcher;return refWatcher;}

我们可以看到install方法其实是创建了一个 refWatcher 这个核心类是整个LeakCanary的重要成员变量,然后这个成员变量还被
ActivityRefWatcher.install(context, refWatcher); 这个方法是监听Activity的生命周期 代码如下

  public static void install(Context context, RefWatcher refWatcher) {Application application = (Application) context.getApplicationContext();ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);}

这一块其实是用Android 原生的方法,监听Activity的生命周期

public final class ActivityRefWatcher {public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {install(application, refWatcher);}public static void install(Context context, RefWatcher refWatcher) {Application application = (Application) context.getApplicationContext();ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);}private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =new ActivityLifecycleCallbacksAdapter() {@Override public void onActivityDestroyed(Activity activity) {refWatcher.watch(activity);}};private final Application application;private final RefWatcher refWatcher;private ActivityRefWatcher(Application application, RefWatcher refWatcher) {this.application = application;this.refWatcher = refWatcher;}public void watchActivities() {// Make sure you don't get installed twice.stopWatchingActivities();application.registerActivityLifecycleCallbacks(lifecycleCallbacks);}public void stopWatchingActivities() {application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);}
}

我们可以了解到当一个Activity的Destroy被调用的时候会调到到这个方法中来
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};

我们看一下watch方法

 public void watch(Object watchedReference, String referenceName) {if (this == DISABLED) {return;}checkNotNull(watchedReference, "watchedReference");checkNotNull(referenceName, "referenceName");final long watchStartNanoTime = System.nanoTime();String key = UUID.randomUUID().toString();retainedKeys.add(key);final KeyedWeakReference reference =new KeyedWeakReference(watchedReference, key, referenceName, queue);ensureGoneAsync(watchStartNanoTime, reference);}

我们看到销毁的Activity被包成了一个弱引用,弱引用就是那种GC后会被回收的一种引用队列

我们看最关键的ensureGoneAsync 方法

  @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {long gcStartNanoTime = System.nanoTime();long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);removeWeaklyReachableReferences();if (debuggerControl.isDebuggerAttached()) {// The debugger can create false leaks.return RETRY;}if (gone(reference)) {return DONE;}gcTrigger.runGc();removeWeaklyReachableReferences();if (!gone(reference)) {long startDumpHeap = System.nanoTime();long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);File heapDumpFile = heapDumper.dumpHeap();if (heapDumpFile == RETRY_LATER) {// Could not dump the heap.return RETRY;}long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key).referenceName(reference.name).watchDurationMs(watchDurationMs).gcDurationMs(gcDurationMs).heapDumpDurationMs(heapDumpDurationMs).build();heapdumpListener.analyze(heapDump);}return DONE;}

这个是第一层GC判断 就是如果刚好加入就被GC掉了 那个中介Done了 如果暂时还没有被GC掉 那么就走gone(reference)方法
把heapDump信息给拿出来进行真正的分析判断 我们看一下 heapdumpListener.analyze(heapDump); 这个的实现我们看一下具体细节。

 @Override public void analyze(HeapDump heapDump) {checkNotNull(heapDump, "heapDump");HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);}

我们继续追一下

 */
public final class HeapAnalyzerService extends ForegroundServiceimplements AnalyzerProgressListener {private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";private static final String HEAPDUMP_EXTRA = "heapdump_extra";public static void runAnalysis(Context context, HeapDump heapDump,Class<? extends AbstractAnalysisResultService> listenerServiceClass) {setEnabledBlocking(context, HeapAnalyzerService.class, true);setEnabledBlocking(context, listenerServiceClass, true);Intent intent = new Intent(context, HeapAnalyzerService.class);intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());intent.putExtra(HEAPDUMP_EXTRA, heapDump);ContextCompat.startForegroundService(context, intent);}public HeapAnalyzerService() {super(HeapAnalyzerService.class.getSimpleName(), R.string.leak_canary_notification_analysing);}@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {if (intent == null) {CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");return;}String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);HeapAnalyzer heapAnalyzer =new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,heapDump.computeRetainedHeapSize);AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);}@Override public void onProgressUpdate(Step step) {int percent = (int) ((100f * step.ordinal()) / Step.values().length);CanaryLog.d("Analysis in progress, working on: %s", step.name());String lowercase = step.name().replace("_", " ").toLowerCase();String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);showForegroundNotification(100, percent, false, message);}
}

这个是一个 IntentService 抛开细节它开启这个服务 执行代码在onHandleIntentInForeground 这个方法中 我们看到 执行了heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize); 这个方法

checkForLeak方法是整个项目最关键的方法 真正去判断是否存在泄露

  public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey,boolean computeRetainedSize) {long analysisStartNanoTime = System.nanoTime();if (!heapDumpFile.exists()) {Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);return failure(exception, since(analysisStartNanoTime));}try {listener.onProgressUpdate(READING_HEAP_DUMP_FILE);HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);HprofParser parser = new HprofParser(buffer);listener.onProgressUpdate(PARSING_HEAP_DUMP);Snapshot snapshot = parser.parse();listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);deduplicateGcRoots(snapshot);listener.onProgressUpdate(FINDING_LEAKING_REF);Instance leakingRef = findLeakingReference(referenceKey, snapshot);// False alarm, weak reference was cleared in between key check and heap dump.if (leakingRef == null) {return noLeak(since(analysisStartNanoTime));}return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);} catch (Throwable e) {return failure(e, since(analysisStartNanoTime));}}

我们可以看到他们是使用反射找到相关的引用去做这个事情,如果反射能拿到,那么说明有存在这个弱引用,存在内存泄露

  private Instance findLeakingReference(String key, Snapshot snapshot) {ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());if (refClass == null) {throw new IllegalStateException("Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");}List<String> keysFound = new ArrayList<>();for (Instance instance : refClass.getInstancesList()) {List<ClassInstance.FieldValue> values = classInstanceValues(instance);Object keyFieldValue = fieldValue(values, "key");if (keyFieldValue == null) {keysFound.add(null);continue;}String keyCandidate = asString(keyFieldValue);if (keyCandidate.equals(key)) {return fieldValue(values, "referent");}keysFound.add(keyCandidate);}throw new IllegalStateException("Could not find weak reference with key " + key + " in " + keysFound);}

如果存在弱引用那么将通过镜像搞成最短路径显示在界面上

  private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,Instance leakingRef, boolean computeRetainedSize) {listener.onProgressUpdate(FINDING_SHORTEST_PATH);ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);// False alarm, no strong reference path to GC Roots.if (result.leakingNode == null) {return noLeak(since(analysisStartNanoTime));}listener.onProgressUpdate(BUILDING_LEAK_TRACE);LeakTrace leakTrace = buildLeakTrace(result.leakingNode);String className = leakingRef.getClassObj().getClassName();long retainedSize;if (computeRetainedSize) {listener.onProgressUpdate(COMPUTING_DOMINATORS);// Side effect: computes retained size.snapshot.computeDominators();Instance leakingInstance = result.leakingNode.instance;retainedSize = leakingInstance.getTotalRetainedSize();// TODO: check O sources and see what happened to android.graphics.Bitmap.mBufferif (SDK_INT <= N_MR1) {listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);}} else {retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;}return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,since(analysisStartNanoTime));}

4.总结

LeakCanary 采用的就是 监听Activity的OnDestroy方法,如果调用了就搞成一个弱引用,通过GC去获取这个弱引用看是否还存在,如果存在就那么就存在内存泄露,如果不存在那么说明能被GC回收不存在内存泄露

LeakCanary 详解相关推荐

  1. Android系统性能优化(60)---LeakCanary使用详解

    Android内存优化(六)LeakCanary使用详解 1.概述 如果使用MAT来分析内存问题,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比.  为了能够简单迅速 ...

  2. Android内存优化(六)LeakCanary使用详解

    相关文章 Android性能优化系列 Java虚拟机系列 1.概述 如果使用MAT来分析内存问题,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比. 为了能够简单迅速的 ...

  3. android OKHttp的基本使用详解

    今天,简单讲讲Android里如何使用OKHttp. Android框架系列: 一.android EventBus的简单使用 二.android Glide简单使用 三.android OKHttp ...

  4. Android 性能优化(62)---存检测、卡顿优化、耗电优化、APK瘦身——详解篇

    Android 性能优化,内存检测.卡顿优化.耗电优化.APK瘦身--详解篇 自2008年智能时代开始,Android操作系统一路高歌,10年智能机发展之路,如今 Android 9.0 代号P  都 ...

  5. Android中内存泄漏超级精炼详解

    一.前期基础知识储备 (1)什么是内存? JAVA是在JVM所虚拟出的内存环境中运行的,JVM的内存可分为三个区:堆(heap).栈(stack)和方法区(method). 栈(stack):是简单的 ...

  6. Linux系统与网络、磁盘参数和日志监控等命令详解二

    创作人QQ:851301776,邮箱:lfr890207@163.com, 欢迎大家一起技术交流,本博客主要是自己学习的心得体会,只为每天进步一点点! 个人座右铭: 1.没有横空出世,只要厚积一定发. ...

  7. Retrofit2.0使用详解

    综述 retrofit是由square公司开发的.square在github上发布了很多优秀的Android开源项目.例如:otto(事件总线),leakcanary(排查内存泄露),android- ...

  8. Android 各大厂面试题汇总与详解(持续更新)

    介绍 目前网络中出现了好多各种面试题的汇总,有真实的也有虚假的,所以今年我将会汇总各大公司面试比较常见的问题,逐一进行解答.会一直集成,也会收集大家提供的面试题,如有错误,请大家指出,经过排查存在,会 ...

  9. [Android]多进程知识点详解

    作者:Android开发_Hua 链接:https://www.jianshu.com/p/e0f833151f66 多进程知识点汇总: 一:了解多进程 二:项目中多进程的实现 三:多进程的优缺点与使 ...

最新文章

  1. 设置datagridview的数据源为(DATASET)SQL查寻结果
  2. 人脸识别遇难题,平台先行破局
  3. 云计算背后的秘密(1)-MapReduce
  4. mysql将数据导入mgr_MySQL 8.0.20 MGR数据迁移过程以及注意事项
  5. Find the longest route with the smallest starting point
  6. 手机文字转语音简单方法分享
  7. LayUI分页查询展示数据(SSH框架)
  8. matlab hist函数的使用
  9. 赫夫曼编码树(图解+完整代码)
  10. wtg linux双系统,Windows和Linux同时装入移动硬盘,实现可移动专属双系统
  11. Git Git用户ssh公钥管理
  12. Manim文档及源码笔记-CE文档-主题化指南3渲染文本和公式
  13. 安森美在罗马尼亚设立新研发中心,提升全球设计能力
  14. 液晶显示屏的C语言编码,AT89C51单片机驱动液晶显示汉字C语言
  15. beaglebone black下接nrf24l01与RFID标签的通信(基于EZSDK linux平台)
  16. powerShell、cmd中命令使用Mysql
  17. 3D激光SLAM:LeGO-LOAM---两步优化的帧间里程计及代码分析
  18. 经典数字图像处理素材库
  19. sql语句-既包含又包含
  20. Vite 基本配置及原理

热门文章

  1. PPP学习---天线相位中心改正文件格式学习
  2. 逐鹿量子计算,“先导杯”向世界难题发起冲击!
  3. android studio中使用AIDL进行客户端与服务端互相通信
  4. Web前端开发笔记——HTML和CSS
  5. Webview 将浏览器作为golang的GUI
  6. 基于javaweb+springboot的物流快递在线寄查快递系统(java+SpringBoot+FreeMarker+Mysql)
  7. 寄快递显示服务器既然出错了,快递寄件填写地址需谨慎 一旦出错拦截很麻烦...
  8. 交叉功率谱 matlab,wavelet 小波分析基本程序及交叉 计算, 功率谱、 相干谱 matlab 252万源代码下载- www.pudn.com...
  9. 一万个数查找两个重复数,快速二分查找法 O(logN)(转)
  10. linux - 文件的所有者、所属组、其他人