您当前的位置:首页 > 电脑百科 > 程序开发 > 移动端 > Android

Android开发者,是时候了解LeakCanary了

时间:2019-11-04 16:53:08  来源:  作者:

 

原理概述

关于LeakCanary的原理,官网上已经给出了详细的解释。翻译过来就是:1.LeakCanary使用ObjectWatcher来监控Android的生命周期。当Activity和Fragment被destroy以后,这些引用被传给ObjectWatcher以WeakReference的形式引用着。如果gc完5秒钟以后这些引用还没有被清除掉,那就是内存泄露了。
2.当被泄露掉的对象达到一个阈值,LeakCanary就会把JAVA的堆栈信息dump到.hprof文件中。
3.LeakCanary用Shark库来解析.hprof文件,找到无法被清理的引用的引用栈,然后再根据对Android系统的知识来判定是哪个实例导致的泄露。
4.通过泄露信息,LeakCanary会将一条完整的引用链缩减到一个小的引用链,其余的因为这个小的引用链导致的泄露链都会被聚合在一起。

通过官网的介绍,我们很容易就抓住了学习LeakCanary这个库的重点:

  • LeakCanary是如何使用ObjectWatcher 监控生命周期的?
  • LeakCanary如何dump和分析.hprof文件的?

看官方原理总是感觉不过瘾,下面我们从代码层面上来分析。本文基于LeakCanary 2.0 beta版。

基本使用

LeakCanary的使用相当的简单。只需要在module的build.gradle添加一行依赖,代码侵入少。

就这样,应用非常简单就接入了LeakCanary内存检测功能。当然还有一些更高级的用法,比如更改自定义config,增加监控项等,大家可以参考官网

源码分析

1. 初始化

和之前的1.x版本相比,2.0甚至都不需要再在Application里面增加install的代码。可能很多的同学都会疑惑,LeakCanary是如何插入自己的初始化代码的呢? 其实这里LeakCanary是使用了ContentProvider来进行初始化。我之前在介绍Android插件化系列三:技术流派和四大组件支持的时候曾经介绍过ContentProvider的特点,即在打包的过程中来自不同module的ContentProvider最后都会merge到一个文件中,启动app的时候ContentProvider是自动安装,并且安装会比Application的onCreate还早。LeakCanary就是依据这个原理进行的设计。具体可以参考【译】你的Android库是否还在Application中初始化?

我们可以查看LeakCanary源码,发现它在leakcanary-object-watcher-android的AndroidManifest.xml中有一个ContentProvider。

然后我们查看AppWatcherInstaller的代码,发现内部是使用InternalAppWatcher进行的install。

可以看到这里主要把Activity和Fragment区分了开来,然后分别进行注册。Activity的生命周期监听是借助于Application.ActivityLifecycleCallbacks。

而Fragment的生命周期监听是借助了Activity的ActivityLifecycleCallbacks生命周期回调,当Activity创建的时候去调用FragmentManager.registerFragmentLifecycleCallbacks方法注册Fragment的生命周期监听。

最终,Activity和Fragment都将自己的引用传入了ObjectWatcher.watch()进行监控。从这里开始进入到LeakCanary的引用监测逻辑。

题外话:LeakCanary 2.0版本和1.0版本相比,增加了Fragment的生命周期监听,每个类的职责也更加清晰。但是我个人觉得使用 (Activty)->Unit 这种lambda表达式作为类的写法不是很优雅,倒不如面向接口编程。完全可以设计成ActivityWatcher和FragmentWatcher都继承自某个接口,这样也方便后续扩展。

2. 引用监控

2.1 引用和GC

  1. 引用
    首先我们先介绍一点准备知识。大家都知道,java中存在四种引用:
  • 强引用:垃圾回收器绝不会回收它,当内存空间不足,Java虚拟机宁愿抛出OOM
  • 软引用:只有在内存不足的时候JVM才会回收仅有软引用指向的对象所占的空间
  • 弱引用:当JVM进行垃圾回收时,无论内存是否充足,都会回收仅被弱引用关联的对象。
  • 虚引用:和没有任何引用一样,在任何时候都可能被垃圾回收。

一个对象在被gc的时候,如果发现还有软引用(或弱引用,或虚引用)指向它,就会在回收对象之前,把这个引用加入到与之关联的引用队列(ReferenceQueue)中去。如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。

当软引用(或弱引用,或虚引用)对象所指向的对象被回收了,那么这个引用对象本身就没有价值了,如果程序中存在大量的这类对象(注意,我们创建的软引用、弱引用、虚引用对象本身是个强引用,不会自动被gc回收),就会浪费内存。因此我们这就可以手动回收位于引用队列中的引用对象本身。

比如我们经常看到这种用法

还有也有这样一种用法

这样就可以把对象和ReferenceQueue关联起来,进行对象是否gc的判断了。另外我们从弱引用的特征中看到,弱引用是不会影响到这个对象是否被gc的,很适合用来监控对象的gc情况。

2.GC
java中有两种手动调用GC的方式。

2.2 监控

我们在第一节中提到,Activity和Fragment都依赖于响应的LifecycleCallback来回调销毁信息,然后调用了ObjectWatcher.watch添加了销毁后的监控。接下来我们看ObjectWatcher.watch做了什么操作。

这里我们看到,有一个存储着KeyedWeakReference的ReferenceQueue对象。在每次增加watch object的时候,都会去把已经处于ReferenceQueue中的对象给从监控对象的map即watchObjects中清理掉,因为这些对象都已经被回收了。然后再去生成一个KeyedWeakReference,这个对象就是一个持有了key和监测开始时间的WeakReference对象。最后再去调用moveToRetained,相当于记录和回调给监控方这个对象正式开始监测的时间。

那么我们现在已经拿到了需要监控的对象了,但是又是怎么去判断这个对象已经内存泄露的呢?这就要继续往下面看。我们主要到前面在讲解InternalAppWatcher的install方法的时候,除了install了Activity和Fragment的检测器,还调用了onAppWatcherInstalled(application)方法,看代码发现这个方法就是InternalLeakCanary的invoke方法。

我们看到首先是初始化了heapDumper,gcTrigger,heapDumpTrigger等对象用于gc和heapDump,同时还实现了OnObjectRetainedListener,并把自己添加到了上面的onObjectRetainedListeners中,以便每个对象moveToRetained的时候,InternalLeakCanary都能获取到onObjectRetained()的回调,回调里就只是回调了heapDumpTrigger.onObjectRetained()方法。看来都是依赖于HeapDumpTrigger这个类。

HeapDumpTrigger主要的处理逻辑都在checkRetainedObjects方法中。

那么HeapDumpTrigger具体做了些啥呢?我理了一下主要是下面几个功能:

  • 后台线程轮询当前还存活着的对象
  • 如果存活的对象大于0,那就触发一次GC操作,回收掉没有泄露的对象
  • GC完后,仍然存活着的对象数和预定的对象数相比较,如果多了就调用heapDumper.dumpHeap()方法把对象dump成文件,并交给HeapAnalyzerService去分析
  • 根据存活情况展示通知

2.3 总结

看到了这里,我们应该脑海中有概念了。Activity和Fragment通过注册系统的监听在onDestroy的时候把自己的引用放入ObjectWatcher进行监测,监测主要是通过HeapDumpTrigger类轮询进行,主要是调用AndroidHeapDumper来dump出文件来,然后依赖于HeapAnalyzerService来进行分析。后面一小节,我们将会聚焦于对象dump操作和HeapAnalyzerService的分析过程。

3. dump对象及分析

3.1 dump对象

hprof是JDK提供的一种JVM TI Agent native工具。JVM TI,全拼是JVM Tool interface,是JVM提供的一套标准的C/C++编程接口,是实现Debugger、Profiler、Monitor、Thread Analyser等工具的统一基础,在主流Java虚拟机中都有实现。hprof工具事实上也是实现了这套接口,可以认为是一套简单的profiler agent工具。我们在新知周推:10.8-10.14(启动篇)中也提到过,可以参考其中美团的文章。

用过Android Studio Profiler工具的同学对hprof文件都不会陌生,当我们使用Memory Profiler工具的Dump Java heap图标的时候,profiler工具就会去捕获你的内存分配情况。但是捕获以后,只有在Memory Profiler正在运行的时候我们才能查看,那么我们要怎么样去保存当时的内存使用情况呢,又或者我想用别的工具来分析堆分配情况呢,这时候hprof文件就派上用场了。Android Studio可以把这些对象给export到hprof文件中去。

LeakCanary也是使用的hprof文件进行对象存储。hprof文件比较简单,整体按照 前置信息 + 记录表的格式来组织的。但是记录的种类相当之多。具体种类可以查看HPROF Agent。

同时,android中也提供了一个简便的方法Debug.dumphprofData(filePath)可以把对象dump到指定路径下的hprof文件中。LeakCanary使用使用Shark库来解析Hprof文件中的各种record,比较高效,使用Shark中的HprofReader和HprofWriter来进行读写解析,获取我们需要的信息。大家可以关注一些比较重要的,比如:

  • Class Dump
  • Instance Dump
  • Object Array Dump
  • Primitive Array Dump

dump具体的代码在AndroidHeapDumper类中。HprofReader和HprofWriter过于复杂,有兴趣的直接查看源码吧

3.2 对象分析

前面我们已经分析到了,HeapDumpTrigger主要是依赖于HeapAnalyzerService进行分析。那么这个HeapAnalyzerService究竟有什么玄机?让我们继续往下面看。可以看到HeapAnalyzerService其实是一个ForegroundService。在接收到分析的Intent后就会调用HeapAnalyzer的analyze方法。所以最终进行分析的地方就是HeapAnalyzer的analyze方法。

核心代码如下

这段代码中涉及到了专为LeakCanary设计的Shark库的用法,在这里就不多解释了。大概介绍一下每一步的作用:

  • 首先调用HprofHeapGraph.indexHprof方法,这个方法会把dump出来的各种实例instance,Class类对象和Array对象等都建立起查询的索引,以record的id作为key,把需要的信息都存储在Map中便于后续取用
  • 调用findLeakInput.findLeak方法,这个方法会从GC Root开始查询,找到最短的一条导致泄露的引用链,然后再根据这条引用链构建出LeakTrace。
  • 把查询出来的LeakTrace对外展示

总结

本篇文章分析了LeakCanary检测内存泄露的思路和一些代码的设计思想,但是限于篇幅不能面面俱到。接下来我们回答一下文章开头提出的问题。

1.LeakCanary是如何使用ObjectWatcher 监控生命周期的?
LeakCanary使用了Application的ActivityLifecycleCallbacks和FragmentManager的FragmentLifecycleCallbacks方法进行Activity和Fragment的生命周期检测,当Activity和Fragment被回调onDestroy以后就会被ObjectWatcher生成KeyedReference来检测,然后借助HeapDumpTrigger的轮询和触发gc的操作找到弹出提醒的时机。

2.LeakCanary如何dump和分析.hprof文件的?
使用Android平台自带的Debug.dumpHprofData方法获取到hprof文件,使用自建的Shark库进行解析,获取到LeakTrace

转载 https://mp.weixin.qq.com/s/plD0g16u0VEqVXDQJrhhpA



Tags:Android   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除,谢谢。
▌相关推荐
6 月 29 日,微软向 Windows 预览体验计划的 Dev 通道推送了 Windows 11 的第一个预览版本,我们也在第一时间升级到了最新系统,可以点击这里查看 APPSO 的抢先体验。 关于 Windo...【详细内容】
2021-07-14  Tags: Android  点击:(5)  评论:(0)  加入收藏
玩过王者荣耀的朋友,几乎无人不晓「鲁班七号」这个英雄。作为 Android 的应用程序包,「APK」对于资深 Android 用户来说,知名度并不亚于前者。 也正因如此,日前 Google 的一份声...【详细内容】
2021-07-08  Tags: Android  点击:(5)  评论:(0)  加入收藏
作者:leafjia,腾讯WXG客户端开发工程师你真的了解Android的线程优先级吗? 看似平平无奇的三行代码却隐藏着巨大的陷阱!Android上如果在主线程执行下面的代码:Thread t = new Thre...【详细内容】
2021-06-18  Tags: Android  点击:(25)  评论:(0)  加入收藏
一、前言最近参加了几轮面试,发现很多5-7年工作经验的候选人在性能优化这一块,基本上只能说出传统的分析方式,例如ANR分析,是通过查看/data/anr/ 下的log,分析主线程堆栈、cpu、...【详细内容】
2021-06-17  Tags: Android  点击:(29)  评论:(0)  加入收藏
随着加密技术的不断升级,设备数据破解的攻防战也在不断升级。比如在打击犯罪的时候,警方可能非常需要查看嫌疑人手机中的内容,以 Cellebrite 为代表的取证工具就此应用而生。不...【详细内容】
2021-05-14  Tags: Android  点击:(86)  评论:(0)  加入收藏
最近在思考一个问题,对于技术人员来说,护城河是什么呢?我想通常答案应该是技术比别人强。那我们说一个人技术比别人强到底指的是什么?哪里比别人强呢?在我看来一点是在某个技术领...【详细内容】
2021-04-20  Tags: Android  点击:(114)  评论:(0)  加入收藏
前言Coil 是一个非常年轻的图片加载库,在 2020 年 10 月 22 日才发布了 1.0.0 版本,但却受到了 Android 官方的推广,在 Android Developers Backstage 这个博客中专门聊过一期...【详细内容】
2021-04-20  Tags: Android  点击:(113)  评论:(0)  加入收藏
然后我们再来看看,androidstudio的布局编辑器的使用. 可以先打开layout中的一个xml去看看,右边有所见即所得的编辑器,以前用eclipse,都是跑起来看,太麻烦了. ​可以看...【详细内容】
2021-04-14  Tags: Android  点击:(114)  评论:(0)  加入收藏
作为一个相当完善的移动操作系统,Android 系统涉及到很多组件。如果宽泛的来讲可以分成两大部分,应用生态和操作系统本身。而对于开发者来说,所选择的编程语言会根据正在开发的...【详细内容】
2021-04-07  Tags: Android  点击:(109)  评论:(0)  加入收藏
本文旨在讲解如何在Android平板或手机上搭建Python开发环境,帮助Python初学者有效利用碎片化时间进行学习,从而达到良好的学习效果。对于大部分初学Python的人来说,由于工作、...【详细内容】
2021-03-24  Tags: Android  点击:(176)  评论:(0)  加入收藏
▌简易百科推荐
作者:leafjia,腾讯WXG客户端开发工程师你真的了解Android的线程优先级吗? 看似平平无奇的三行代码却隐藏着巨大的陷阱!Android上如果在主线程执行下面的代码:Thread t = new Thre...【详细内容】
2021-06-18  leafjia  腾讯技术工程  Tags:Android   点击:(25)  评论:(0)  加入收藏
一、前言最近参加了几轮面试,发现很多5-7年工作经验的候选人在性能优化这一块,基本上只能说出传统的分析方式,例如ANR分析,是通过查看/data/anr/ 下的log,分析主线程堆栈、cpu、...【详细内容】
2021-06-17  Android面试通    Tags:Android   点击:(29)  评论:(0)  加入收藏
随着加密技术的不断升级,设备数据破解的攻防战也在不断升级。比如在打击犯罪的时候,警方可能非常需要查看嫌疑人手机中的内容,以 Cellebrite 为代表的取证工具就此应用而生。不...【详细内容】
2021-05-14    cnBeta  Tags:Android应用   点击:(86)  评论:(0)  加入收藏
最近在思考一个问题,对于技术人员来说,护城河是什么呢?我想通常答案应该是技术比别人强。那我们说一个人技术比别人强到底指的是什么?哪里比别人强呢?在我看来一点是在某个技术领...【详细内容】
2021-04-20  像程序那样思考  今日头条  Tags:Android研发   点击:(114)  评论:(0)  加入收藏
前言Coil 是一个非常年轻的图片加载库,在 2020 年 10 月 22 日才发布了 1.0.0 版本,但却受到了 Android 官方的推广,在 Android Developers Backstage 这个博客中专门聊过一期...【详细内容】
2021-04-20  愿天堂没有代码  今日头条  Tags:Kotlin-First   点击:(113)  评论:(0)  加入收藏
然后我们再来看看,androidstudio的布局编辑器的使用. 可以先打开layout中的一个xml去看看,右边有所见即所得的编辑器,以前用eclipse,都是跑起来看,太麻烦了. ​可以看...【详细内容】
2021-04-14  梦幻神域  今日头条  Tags:   点击:(114)  评论:(0)  加入收藏
作为一个相当完善的移动操作系统,Android 系统涉及到很多组件。如果宽泛的来讲可以分成两大部分,应用生态和操作系统本身。而对于开发者来说,所选择的编程语言会根据正在开发的...【详细内容】
2021-04-07  cnBeta  今日头条  Tags:Rust编写   点击:(109)  评论:(0)  加入收藏
安卓12开发者预览版1已经出来一段时间了,我们已经对它带来的变化有了很好了解。我们还了解到,DP1采用了隐藏式的系统设置设计,灵感来自于三星One UI。此外,安卓12可能会带来一个...【详细内容】
2021-03-02      Tags:Android 12   点击:(97)  评论:(0)  加入收藏
安卓是谷歌将它从开源linux上改造而来,依旧保持开源特性。为了应用开发者更多地开发安卓程序,自然也就保留了linux上的虚拟机机制。同时,安卓的目标是手机等移动终端,这些设备的...【详细内容】
2021-02-08      Tags:Android   点击:(107)  评论:(0)  加入收藏
由于工作需要,需要解决一些性能问题,虽然有 Profiler 、Systrace 等工具, 但是无法实时监控,于是计划写一个能实时监控性能的小工具,经过学习大佬们的文章, 最终完成了这个开源的...【详细内容】
2021-01-14      Tags:Android   点击:(48)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条