前言
LeakCanary是一个Android端用于内存泄露检测开源工具,可以很方便的自动检测销毁的Activity、Fragment的实例、Fragment中View的实例、清除的ViewModel实例的内存泄露。本篇基于2.4版本进行分析。
LeakCanary官网地址: square.github.io/leakcanary/
LeakCanary的使用
在2.0之后,LeakCanary只需要在build.gradle文件中添加leakcanary-android依赖即可.
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.4'
}
复制代码
添加依赖后,运行应用即可自动检测应用中的内存泄露,主要检测泄露对象为:
- destroyed Activity instances: 销毁的Activity实例
- destroyed Fragment instances: 销毁的Fragment实例
- destroyed fragment View instances: 销毁的fragment中View实例
- cleared ViewModel instances: 清除的ViewModel实例
会在桌面上生成Leaks应用图标,用于展示内存泄露详情;同时你也可以使用logcat中查看TAG为LeakCanary的日志信息。
2.4版本也增加了一个新的plumber-android的库,解决已知的Android leaks问题,使用时直接使用implementation集成;使用Shark CLI也可以检测连接设备上任意应用程序的内存泄漏问题。
LeakCanary的基本原理
1.介绍
什么是内存泄漏? 在java运行时,内存泄漏是导致应用程序持有一个不再需要的对象引用的程序问题,它会导致分配给它的内存无法回收,进而会导致内存溢出问题。
导致内存泄漏的一般原因? 大部分的内存泄漏是跟对象的生命周期有关的bug,例如:
- 添加一个Fragment实例到backstack而没有在onDestroyView中清除Fragment的view.
- 注册一个关联生命周期对象的listener,broadcast receiver或者订阅了一个RxJava,而没有反注册.
为什么需要使用LeakCanary? LeakCanary能够帮助我们在开发中检测和查找隐藏的内存泄漏,这样可以协助我们在开发中就定位和解决这些泄漏问题,减少应用OOM crash问题。
2.LeakCanary如何工作
LeakCanary安装后,它通过4个步骤自动监测和报告内存泄漏:
- 1.Detecting retained objects.
- 2.Dumping the heap.
- 3.Analyzing the heap.
- 4.Categorizing leaks.
I.Detecting retained objects
LeakCanary利用Android lifecycle回调,在Activity、Fragment等的onDestroyed方法中自动监测这些实例对象,将实例传递给ObjectWatcher,并使用WeakReference来持有它们。当onDestroy方法调用后等待5seconds并执行gc后,被观察的对象依然保留,则可能存在内存泄露;LeadCanary等待被保留可能内存泄露对象达到阈值5个时执行dumping the heap,并展示一个通知。
II.Dumping the heap
当保留对象达到阈值5个时,LeakCanary执行dump将java堆栈信息存入.hprof文件,执行dump将会花费一些时间,LeakCanary会显示一个Toast。
LeakCanary在哪里存储hprof文件?
如果应用已经获取了android.permission.WRITE_EXTERNAL_STORAGE权限,则会在Download目录下生成一个leakcanary-**(package name)的文件夹来存储dump的hprof文件;如果未获得权限,则会显示一个通知去获取权限。
复制代码
III.Analyzing the heap
LeakCanary使用Shark库来分析.hprof文件并定位保留的对象位置;对于每个被保留对象,LeakCanary将找到它被持有,不被gc回收的泄露轨迹leak trace;分析结束,将展示一个通知概要,Leakcanary为每个leak trace生成一个签名signature(签名根据怀疑导致泄露的相关对象名称字符串计算sha1Hash得来),并根据签名来将检测到的泄露进行分组。点击通知将展示泄露详情。对于第一次出现的leak signature,将标记New标签。
Finding retainded objects -> Building leak traces -> Found *retained objects grouped as * distinct leaks Tap for more details
IV.Categorizing leaks
LeakCanary将找到的泄漏分为两类:Application Leaks和Library Leaks,可以在Logcat中查看;Library Leaks是由第三方库导致的已知内存泄露bug,会添加Library标签,可能不在我们应用控制范围;
3.解决内存泄露
内存泄露是导致应用程序持有一个本不再需要的对象的程序问题。下面分为4个步骤去解决内存泄露:
1.Find the leak trace. 2.Narrow down the suspect references. 3.Find the reference causing the leak. 4.Fix the leak. 前两步LeakCanary帮我们搞定了,后面两步需要我们自己去解决;
I.查找泄漏轨迹(Find the leak trace)
一个泄漏轨迹是从GC Roots到保留对象的一个最短强引用路径;有4类主要的GC Roots:
- Local variables: 属于线程方法栈的本地变量
- active Java threads: java线程实例
- System Classes: 系统类(不会卸载)
- Native references: 本地
II.缩小怀疑引用(Narrow down the suspect references)
LeakCanary将检查leak trace路径上的怀疑对象的状态和生命周期来确定这些对象是否泄露;你可以自定义ObjectInspector去定义LeakCanary的工作方式。
III.查找导致泄露的引用(Find the reference causing the leak)
根据经验进一步排除leak trace上可能导致泄露的对象。
IV.解决泄露(Fix the leak)
找到泄露的对象引用后,你需要确定具体引用对象是什么,它应该什么时候被销毁但没有销毁;有时候很明显,但有时候你需要更多信息才能确认;你可以添加标签或者直接通过分享hprof去确定。
4.LeakCanary初始化
![](https://img.haomeiwen.com/i12798471/0bb05bbfb8ed01ad.png)
查看LeakCanary源码,可以看到AppWatcher中manualInstall方法,里面调用了InternalAppWatcher的install方法来初始化应用的Activity、Fragment等泄露观察。 AppWatcher.kt
fun manualInstall(application: Application) {
InternalAppWatcher.install(application)
}
复制代码
I.AppWatcher.manualInstall()在主进程中被自动调用
如何自动被调用并初始化的? 该方法在AppWatcherInstaller的OnCreate()方法中调用,而AppWatcherInstall继承自ContentProvider并在AndroidManifest.xml中注册,利用ContentProvider在无需显示初始化;ContentProvider的onCreate的调用时机介于Application的attachBaseContext和onCreate之间,Provider的onCreate优先于Application的onCreate执行,并且此时的Application已经创建成功,而Provider里的context正是Application的对象。
LeakCanary2.4中在values.xml中增加了配置字段:false 来控制是否自动初始化,并在AndroidManifest.xml中配置。
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false" />
</application>
复制代码
如何指定被调用的进程?
AppWatcherInstaller作为密封类里有两个子类MainProcess、LeakCanaryProcess,分别指定LeakCanary运行的进程;使用时分别对应leakcanary-android和leakcanary-android-process依赖。
InternalAppWatcher.install()代码
fun install(application: Application) {
checkMainThread()
if (this::application.isInitialized) {
return
}
SharkLog.logger = DefaultCanaryLog()
InternalAppWatcher.application = application
val configProvider = { AppWatcher.config } //AppWatcher配置信息,可以随时更新
ActivityDestroyWatcher.install(application, objectWatcher, configProvider) //初始化Activity对象监测
FragmentDestroyWatcher.install(application, objectWatcher, configProvider) //初始化Fragment对象监测
onAppWatcherInstalled(application)
}
复制代码
如何更新AppWatcher配置?
Kotlin: AppWatcher.config = AppWatcher.config.copy(watchFragmentViews = false)
Java: AppWatcher.Config config = AppWatcher.getConfig().newBuilder()
.watchFragmentViews(false)
.build();
AppWatcher.setConfig(config);
复制代码
5.LeakCanary如何监测Activity、Fragment内存泄露?
关键代码如下,分别对Activity和Fragment进行监测,内部实现原理相似.
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
复制代码
先来分析ActivityDestroyWatcher.install(application, objectWatcher, configProvider):
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) { //只监听onActivityDestroyed方法
if (configProvider().watchActivities) { //如果配置中监测Activity,则调用ObjectWatcher.watch来检测Activity
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
companion object { //伴生对象,类似java中的静态方法
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider) //类似静态方法中初始化实例
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks) //调用application注册Activity的生命周期回调监听
}
}
}
复制代码
再来分析FragmentDestroyWatcher.install(application, objectWatcher, configProvider): FragmentDestroyWatcher中主要是install()方法
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
) {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>() //fragmentDestroyWatchers列表,支持不同Fragment实例的检测;这里的watcher都继承自(Activity)->Unit表示方法类型/函数类型,参数为Activity,返回值为空;因为是方法类型所以需要重写invoke方法
if (SDK_INT >= O) { //Android O后构建AndroidOFragmentDestroyWatcher
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
)
}
getWatcherIfAvailable( //如果Class.for(className)能找到androidx.fragment.app.Fragment和
ANDROIDX_FRAGMENT_CLASS_NAME, //leakcanary.internal.AndroidXFragmentDestroyWatcher则添加AndroidXFragmentDestroyWatcher则添加
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
getWatcherIfAvailable( //如果Class.for(className)能找到android.support.v4.app.Fragment和
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME, //leakcanary.internal.AndroidSupportFragmentDestroyWatcher则添加AndroidSupportFragmentDestroyWatcher
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
if (fragmentDestroyWatchers.size == 0) {
return
}
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated( //注册Activity生命周期回调,在Activity的onActivityCreated()方法中遍历这些watcher方法类型,实际调用的是对应的invoke方法
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
})
}
复制代码
如果系统是Android O以后版本,使用AndroidOFragmentDestroyWatcher,如果app使用的是androidx中的fragment,则添加对应的AndroidXFragmentDestroyWatcher,如果使用support库中的fragment,则添加AndroidSupportFragmentDestroyWatcher。最终在invoke方法中使用对应的fragmentManager注册Fragment的生命周期回调,在onFragmentViewDestroyed()和onFragmentDestroyed()方法中使用ObjectWatcher来检测fragment。下面以AndroidXFragmentDestroyWatcher为例:
internal class AndroidXFragmentDestroyWatcher(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentCreated( //在AndroidXFragmentDestroyWatcher中添加了onFragmentCreated()回调,添加了ViewModelStoreOwner为Fragment的ViewModelClearedWatcher监测
fm: FragmentManager,
fragment: Fragment,
savedInstanceState: Bundle?
) {
ViewModelClearedWatcher.install(fragment, objectWatcher, configProvider)
}
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null && configProvider().watchFragmentViews) {
objectWatcher.watch(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
if (configProvider().watchFragments) {
objectWatcher.watch(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
}
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) { //此处根据不同FragmentActivity和Fragment,取得对应的FragmentManager,添加生命周期回调
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
ViewModelClearedWatcher.install(activity, objectWatcher, configProvider) //添加了ViewModelStoreOwner为Activity的ViewModelClearedWatcher监测
}
}
}
复制代码
ViewModelClearedWatcher继承自ViewModel,里面使用viewModelMap来存储ViewModelStoreOwner中的ViewModel,并使用伴生对象来初始化自己,关联到ViewModelStoreOwner;在onCleared()方法中使用ObjectWatcher来监测。 综上,最后都是通过ObjectWatcher.watch()来监测Activity、Fragment、Fragment的View、ViewModel。下面重点分析ObjectWatcher.watch()方法:
private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
/**
* References passed to [watch].
*/
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
/**
* Watches the provided [watchedObject].
*
* @param description Describes why the object is watched.
*/
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
removeWeaklyReachableObjects() //1.移除watchedObjects中弱可达对象引用(将被回收)
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue) //2.将要观察的对象放入KeyedWeakReference中
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) " ($description)" else "") +
" with key $key"
}
watchedObjects[key] = reference //3.存入watchedObjects
checkRetainedExecutor.execute { //4.使用checkRetainedExecutor定时执行moveToRetained()
moveToRetained(key)
}
}
@Synchronized private fun moveToRetained(key: String) { //5.moveToRetained()中将再次执行removeWeaklyReachableObjects(),确保没有遗漏,然后将未弱可达及未回收的对象取出回调其ObjectWatcher的onObjectRetained()方法;
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
复制代码
OnObjectRetainedListener是在InternalLeakCanary的invoke()方法中通过 AppWatcher.objectWatcher添加的监听,最终回调到InternalLeakCanary的onObjectRetained()方法,里面回调heapDumpTrigger.onObjectRetained()方法,然后调用scheduleRetainedObjectCheck("found new object retained")
private fun scheduleRetainedObjectCheck(reason: String) {
if (checkScheduled) {
SharkLog.d { "Already scheduled retained check, ignoring ($reason)" }
return
}
checkScheduled = true
backgroundHandler.post { //在后台线程LeakCanary-Heap-Dump中执行
checkScheduled = false
checkRetainedObjects(reason)
}
}
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
if (!config.dumpHeap) {
SharkLog.d { "No checking for retained object: LeakCanary.Config.dumpHeap is false" }
return
}
SharkLog.d { "Checking retained object because $reason" }
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) { //如果retained的引用数量>0,执行gc,更新retained引用数量
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return //当retaind的引用数量< 默认阈值5时,只弹通知不做dump处理
if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) { //如果正在debug,默认debug时不进行heap dump
showRetainedCountWithDebuggerAttached(retainedReferenceCount) //显示debug时的retained对象数量通知
scheduleRetainedObjectCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
SharkLog.d {
"Not checking for leaks while the debugger is attached, will retry in $WAIT_FOR_DEBUG_MILLIS ms"
}
return
}
SharkLog.d { "Found $retainedReferenceCount retained references, dumping the heap" }
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis //更新headpDump时间
dismissRetainedCountNotification() //取消显示的通知
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
scheduleRetainedObjectCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS) //dump失败后5秒钟再进行check
showRetainedCountWithHeapDumpFailed(retainedReferenceCount) //显示dump heap失败的通知
return
}
lastDisplayedRetainedObjectCount = 0
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) //移除heapDumpUptimeMillis之前的被观察对象创建的KeyedWeakReference
HeapAnalyzerService.runAnalysis(application, heapDumpFile) //启动服务HeapAnalyzerService(SDK版本>=26启动前台服务,<26启动服务)进行分析
}
复制代码
HeapAnalyzerService中使用HeapAnalyzer调用analyze方法进行hprof文件的分析,具体使用的就是shark库分析,不再使用之前版本中使用的haha库了。
写在最后
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在IT学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多程序员朋友无法获得正确的资料得到学习提升,故此将并将重要的Android进阶资料包括自定义view、性能优化、MVC与MVP与MVVM三大框架的区别、NDK技术、阿里面试题精编汇总、常见源码分析等学习资料免费分享出来。
![](https://img.haomeiwen.com/i19956127/4d1b5e857c4b3c2c.png)
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。包含知识脉络 + 诸多细节,由于篇幅有限,下面只是以图片的形式给大家展示一部分。
![](https://img.haomeiwen.com/i19956127/1b214e26967dacc6.jpg)
【Android学习PDF+学习视频+面试文档+知识点笔记】
【Android高级架构视频学习资源】
Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
【Android进阶学习视频】、【全套Android面试秘籍】可以简信我【学习】查看免费领取方式!
网友评论