OOM是Android开发中常见的问题,而内存泄漏往往是罪魁祸首。 Square开源了LeakCanary,它可以实时监测活动是否发生了泄漏,一旦发现就会自动弹出提示及相关的泄漏信息供分析。 本文的目的是试图通过分析LeakCanary源码来探讨它的Activity泄漏检测机制。 Leak
OOM是Android开发中常见的问题,而内存泄漏往往是罪魁祸首。
Square开源了LeakCanary,它可以实时监测活动是否发生了泄漏,一旦发现就会自动弹出提示及相关的泄漏信息供分析。
本文的目的是试图通过分析LeakCanary源码来探讨它的Activity泄漏检测机制。
LeakCanary使用方式
为了将LeakCanary引入到我们的项目里,我们只需要做以下两步:
-
依赖{
-
debugCompile'com.squareup.leakcanary :leakcanary-android:1.5.1'
-
releaseCompile'com.squareup.leakcanary :leakcanary-android-no-op:1.5.1'
-
testCompile'com.squareup.leakcanary :leakcanary-android-no-op:1.5.1' } public class ExampleApplication extends Application {@Override public void onCreate(){super.onCreate(); 如果(LeakCanary.isInAnalyzerProcess(本)){//这过程 被 专用 于 LeakCanary 为 堆分析。
-
//你应该 不 初始化您的应用程序 在 此过程中。
-
返回;
-
}
-
LeakCanary.install(本);
-
}
-
}
可以看出,最关键的就是LeakCanary.install(this); 这么一句话,正式开启了LeakCanary的大门,未来它会自动帮我们检测内存泄漏,并在发生泄漏是弹出通知信息。
从LeakCanary.install(this); 开始
下面我们来看下它做了些什么?
-
公共静态 RefWatcher安装(应用程序应用程序){ 返回 安装(应用程序,DisplayLeakService.class,
-
AndroidExcludedRefs.createAppDefaults()建立())。
-
} 公共静态 RefWatcher安装(应用程序的应用程序,
-
类<?扩展AbstractAnalysisResultService> listenerServiceClass,
-
ExcludedRefs excludedRefs){if(isInAnalyzerProcess(application)){ return RefWatcher.DISABLED;
-
}
-
enableDisplayLeakActivity(应用程序);
-
HeapDump.Listener heapDumpListener = new ServiceHeapDumpListener(application,listenerServiceClass);
-
RefWatcher refWatcher = androidWatcher(application,heapDumpListener,excludedRefs);
-
ActivityRefWatcher.installOnIcsPlus(application,refWatcher); 返回 refWatcher;
-
}
首先,我们先看最重要的部分,就是:
-
RefWatcher refWatcher = androidWatcher(application,heapDumpListener,excludedRefs);
-
ActivityRefWatcher.installOnIcsPlus(application,refWatcher);
先生成了一个RefWatcher,这个东西非常关键,从名字可以看出,它是用来观看Reference的,也就是用来一个监控引用的工具。然后再把refWatcher和我们自己提供的应用程序传入到ActivityRefWatcher。 installOnIcsPlus(application,refWatcher); 这句里面,继续看。
-
公共静态 无效installOnIcsPlus(应用程序的应用程序,RefWatcher refWatcher){
-
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application,refWatcher);
-
activityRefWatcher.watchActivities();
-
}
创建了一个ActivityRefWatcher,大家应该能感受到,这个东西就是用来监控我们的活动泄漏状况的,它调用watchActivities()方法,就可以开始进行监控了。下面就是它监控的核心原理:
-
public void watchActivities(){
-
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
-
}
它向应用里注册了一个ActivitylifecycleCallbacks的回调函数,可以用来监听应用整个生命周期所有Activity的生命周期事件再看下这个lifecycleCallbacks是什么?
-
私人最终Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks(){@Override public void onActivityCreated(Activity activity,Bundle savedInstanceState){
-
} @Override public void onActivityStarted(Activity activity){
-
} @Override public void onActivityResumed(Activity activity){
-
} @Override public void onActivityPaused(Activity activity){
-
} @Override public void onActivityStopped(Activity activity){
-
} @Override public void onActivitySaveInstanceState(Activity activity,Bundle outState){
-
} @Override public void onActivityDestroyed(Activity activity){
-
ActivityRefWatcher.this.onActivityDestroyed(活性);
-
}
-
};
原来它只监听了所有Activity的onActivityDestroyed事件,当Activity被Destory时,调用ActivityRefWatcher.this.onActivityDestroyed(activity); 函数。
猜测下,正常情况下,当一个这个函数应该活动被Destory时,那这个活动对象应该变成null才是正确的。如果没有变成null,那么就意味着发生了内存泄漏。
因此我们向,这个函数ActivityRefWatcher.this.onActivityDestroyed(activity); 应该是用来监听活动对象是否变成了null。继续看。
-
void onActivityDestroyed(Activity activity){
-
refWatcher.watch(活性);
-
}
-
RefWatcher refWatcher = androidWatcher(application,heapDumpListener,excludedRefs);
可以看出,这个函数把目标活动对象传给了RefWatcher,让它去监控这个活动是否被正常回收了,若未被回收,则意味着发生了内存泄漏。
RefWatcher如何监控活动是否被正常回收呢?
我们先来看看这个RefWatcher究竟是个甚么东西?
-
公共静态 RefWatcher androidWatcher(上下文上下文,HeapDump.Listener heapDumpListener,
-
ExcludedRefs excludedRefs){
-
AndroidHeapDumper heapDumper = new AndroidHeapDumper(context,leakDirectoryProvider);
-
heapDumper.cleanup(); int watchDelayMillis = 5000;
-
AndroidWatchExecutor executor = new AndroidWatchExecutor(watchDelayMillis); 返回 新的RefWatcher(执行器,debuggerControl ,GcTrigger,DEFAULT ,heapDumper,
-
heapDumpListener,excludedRefs);
-
}
这里面涉及到两个新的对象:AndroidHeapDumper和AndroidWatchExecutor,前者用来转储堆内存状态的,后者则是用来观看一个引用的监听器。具体原理后面再看。总之,这里已经生成好了一个RefWatcher对象了。
现在再看上面onActivityDestroyed(Activity activity)里调用的refWatcher.watch(activity); ,下面来看下这个最为核心的watch(activity)方法,了解它是如何监控activity是否被回收的。
-
private final Set <String> retainKeys; public void watch(Object activity,String referenceName){
-
String key = UUID.randomUUID()。toString();
-
retainedKeys。加(键); final KeyedWeakReference reference = new KeyedWeakReference(activity, key ,referenceName,queue);
-
-
watchExecutor。execute (new Runnable(){@Override public void run(){
-
ensureGone(reference,watchStartNanoTime);
-
}
-
});
-
} final类KeyedWeakReference继承WeakReference <Object> { public final String key ; 公共 最终字符串 名称;
-
}
可以看到,它首先把我们传入的活动包装成了一个KeyedWeakReference(可以暂时看成一个普通的WeakReference),然后watchExecutor会去执行一个Runnable,这个Runnable会调用ensureGone(reference,watchStartNanoTime)函数。
看这个函数之前猜测下,我们知道watch函数本身就是用来监听activity是否被正常回收,这就涉及到两个问题:
-
何时去检查它是否回收?
-
如何有效地检查它真的被回收?
所以我们觉得ensureGone函数本身要做的事正如它的名字,就是确保参考被回收掉了,否则就意味着内存泄漏。
核心函数:ensureGone(reference)检测回收
下面来看这个函数实现:
-
void ensureGone(KeyedWeakReference reference,long watchStartNanoTime){
-
removeWeaklyReachableReferences(); if(gone(reference)|| debuggerControl.isDebuggerAttached()){ return ;
-
}
-
gcTrigger.runGc();
-
removeWeaklyReachableReferences(); 如果(!去(参考)){
-
文件heapDumpFile = heapDumper.dumpHeap();
-
heapdumpListener.analyze(新HEAPDUMP(heapDumpFile,参考关键,参考名称,excludedRefs,watchDurationMs,
-
gcDurationMs,heapDumpDurationMs));
-
}
-
}私人布尔没了(KeyedWeakReference引用){ return !retainedKeys。含有(参考键);
-
} private void removeWeaklyReachableReferences(){
-
KeyedWeakReference ref; while((ref =(KeyedWeakReference)queue.poll())!= null ){
-
保留钥匙(参考钥匙);
-
}
-
}
这里先来解释下WeakReference和ReferenceQueue的工作原理。
弱引用WeakReference
被强引用的对象就算发生OOM也永远不会被垃圾回收机回收;被弱引用的对象,只要被垃圾回收器发现就会立即被回收;被软引用的对象,具备内存敏感性,只有内存不足时才会被回收,常用来做内存敏感缓存器;虚引用则任意时刻都可能被回收,使用较少。
2.引用队列ReferenceQueue
我们常用一个WeakReference <Activity> reference = new WeakReference(activity); ,这里我们创建了一个参考来弱引用到某个活动,当这个活动被垃圾回收器回收后,这个参考会被放入内部的ReferenceQueue中。也就是说,从队列ReferenceQueue取出来的所有参考,它们指向的真实对象都已经成功被回收了。
然后再回到上面的代码。
在一个活动传给RefWatcher时会创建一个唯一的密钥对应这个activity,该key存入一个集合retainKeys中。也就是说,所有我们想要观测的activity对应的唯一key都会被放入retainKeys集合中。
基于我们对ReferenceQueue的了解,只要把队列中所有的参考取出来,并把对应retainKeys里的key移除,剩下的key对应的对象都没有被回收。
-
ensureGone首先调用removeWeaklyReachableReferences把已被回收的对象的key从retainKeys移除,剩下的key都是未被回收的对象;
-
if(gone(reference))用来判断某个引用的key是否仍在retainKeys里,若不在,表示已回收,否则继续;
-
gcTrigger.runGc(); 手动出发GC,立即把所有WeakReference引用的对象回收;
-
removeWeaklyReachableReferences(); 再次清理retainKeys,如果该参考还在remainingKeys里(if(!gone(reference))),表示泄漏;
-
利用heapDumper把内存情况dump成文件,并调用heapdumpListener进行内存分析,进一步确认是否发生内存泄漏。
-
如果确认发生内存泄漏,调用DisplayLeakService发送通知。
至此,核心的内存泄漏检测机制便看完了。
内存泄漏检测小结
从上面我们大概了解了内存泄漏检测机制,大概是以下几个步骤:
-
利用application.registerActivityLifecycleCallbacks(lifecycleCallbacks)来监听整个生命周期内的Activity onDestoryed事件;
-
当某个活动被destory后,将它传给RefWatcher去做观测,确保其后续会被正常回收;
-
RefWatcher首先把活动使用KeyedWeakReference引用起来,并使用一个ReferenceQueue来记录该KeyedWeakReference指向的对象是否已被回收;
-
AndroidWatchExecutor会在5s后,开始检查这个弱引用内的Activity是否被正常回收。判断条件是:若Activity被正常回收,那么引用它的KeyedWeakReference会被自动放入ReferenceQueue中。
-
判断方式是:先看活动对应的KeyedWeakReference是否已经放入ReferenceQueue中;如果没有,则手动GC:gcTrigger.runGc(); ;然后再一次判断ReferenceQueue是否已经含有对应的KeyedWeakReference。若还未被回收,则认为可能发生内存泄漏。
-
利用HeapAnalyzer对转储的内存情况进行分析并进一步确认,若确定发生泄漏,则利用DisplayLeakService发送通知。
探讨一些关于LeakCanary有趣的问题
在学习了LeakCanary的源码之后,我想再提几个有趣的问题做些探讨。
LeakCanary项目目录结构为什么这样分?
下面是整个LeakCanary的项目结构:

对于开发者而言,只需要使用到LeakCanary.install(this); 这一句即可。那整个项目为什么要分成这么多个模块呢?
实际上,这里面每一个module都有自己的角色。
-
leakcanary-watcher:这是一个通用的内存检测器,对外提供一个RefWatcher#watch(Object watchedReference),可以看出,它不仅能够检测Activity,还能监测任意常规的Java Object的泄漏情况。
-
leakcanary-android:这个模块是与Android的世界的接入点,用来专门监测活动的泄漏情况,内部使用了应用程序#registerActivityLifecycleCallbacks方法来监听onDestory事件,然后利用leakcanary-watcher来进行弱引用+手动GC机制进行监控。
-
leakcanary-analyzer:这个模块提供了HeapAnalyzer,用来对转储出来的内存进行分析,并返回内存分析结果AnalysisResult,内部包含了泄漏发生的路径等信息供开发者寻找定位。
-
leakcanary-android-no-op:这个模块是专门给发行的版本用的,内部只提供了两个完全空白的类LeakCanary和RefWatcher,这两个类不会做任何内存泄漏相关的分析。为什么?因为LeakCanary本身会由于不断gc影响到应用程序本身的运行,而且主要用于开发阶段的内存泄漏检测。因此在release中可以禁用所有泄漏分析。
-
leakcanary-sample:这个很简单,就是提供了一个用法示例。
当活动被destory后,LeakCanary多久后会去进行检查其是否泄漏呢?
在源码中可以看到,LeakCanary并不会在destory后立即去检查,而是让一个AndroidWatchExecutor去进行检查。它会做什么呢?
-
@Override public void execute (final Runnable command){if(isOnMainThread()){
-
executeDelayedAfterIdleUnsafe(命令);
-
} else {
-
mainHandler.post(new Runnable(){@Override public void run(){
-
executeDelayedAfterIdleUnsafe(命令);
-
}
-
});
-
}
-
}空隙executeDelayedAfterIdleUnsafe(最终可运行可运行){//这需要 到 被称为 从 主线程。
-
Looper.myQueue()。addIdleHandler(new MessageQueue.IdleHandler(){@ Override public boolean queueIdle(){
-
backgroundHandler.postDelayed(runnable,delayMillis); 返回false ;
-
}
-
});
-
}
它可以看到,它首先会向主线程的MessageQueue添加一个IdleHandler。
什么是IdleHandler?我们知道Looper会不断从MessageQueue里取出消息并执行。当没有新的消息执行时,Looper进入空闲状态时,就会取出IdleHandler来执行。
换句话说,IdleHandler就是优先级别较低的消息,只有当Looper没有消息要处理时才得到处理。而且,内部的queueIdle()方法若返回true,表示该任务一直存活,每次Looper进入Idle时就执行;反正,如果返回false,则表示只会执行一次,执行完后丢弃。
那么,这件优先级较低的任务是什么呢?backgroundHandler.postDelayed(runnable,delayMillis); ,runnable就是之前ensureGone()。
也就是说,当主线程空闲了,没事做了,开始向后台线程发送一个延时消息,告诉后台线程,5s(delayMillis)后开始检查Activity是否被回收了。
所以,当活发生destory后,首先要等到主线程空闲,然后再延时5s(delayMillis),才开始执行泄漏检查。
知识点:
1.如何创建一个优先级低的主线程任务,它只会在主线程空闲时才执行,不会影响到应用程序的性能?
-
新的MessageQueue.IdleHandler(){@覆盖 公共 布尔queueIdle(){/ /任务
-
返回false ; // 只有 一次
-
}
-
});
2.如何快速创建一个主/子线程处理程序?
-
//主线程handlermainHandler = new Handler(Looper.getMainLooper()); //子线程handlerHandlerThread handlerThread = new HandlerThread(“子线程任务”);
-
handlerThread.start();
-
Handler backgroundHandler = new Handler(handlerThread.getLooper());
3.如何快速判断当前是否运行在主线程?
-
Looper.getMainLooper()。getThread()== Thread.currentThread();
System.gc()可以触发立即gc吗?如果不行那怎么才能触发即时gc呢?
在LeakCanary里,需要立即触发gc,并在之后立即判断弱引用是否被回收。这意味着该gc必须能够立即同步执行。
常用的触发gc方法是System.gc(),那它能达到我们的要求吗?
我们来看下其实现方式:
-
/ **
-
*表示 到 虚拟机,这将是一个很好的 时间来 运行
-
* 垃圾收集器。请注意,这 是 一个暗示 只。有 是无 保证
-
*垃圾收集器将实际运行。
-
* / public static void gc(){boolean shouldRunGC; 同步(锁定){
-
shouldRunGC = justRanFinalization; if(shouldRunGC){
-
justRanFinalization = false ;
-
} else {
-
runGC = true ;
-
}
-
} if(shouldRunGC){
-
调用Runtime.getRuntime()GC();
-
}
-
}
注意里清楚说了,System.gc()只是建议垃圾回收器来执行回收,但是不能保证真的去回收。从代码也能看出,必须先判断shouldRunGC才能决定是否真的要gc。
知识点:
那要怎么实现即时GC呢?
LeakCanary参考了一段AOSP的代码
-
// System.gc()的确实 不是 垃圾收集每一个 时间。Runtime.gc() 是//更可能 向 。perfom一个gc.Runtime.getRuntime()GC();
-
enqueueReferences();
-
System.runFinalization(); 公共静态 无效enqueueReferences(){/ *
-
*黑客。我们没有一个编程的方式 来 等待 的 参考队列
-
*守护进程 来移动引用到 合适的队列。
-
* /
-
尝试{
-
了Thread.sleep(100);
-
catch(InterruptedException e){抛出新的AssertionError();
-
}
-
}
可以怎样来改造LeakCanary呢?
忽略某些已知泄漏的类或活性
LeakCanary提供了ExcludedRefs类,可以向里面添加某些主动忽略的类。比如已知的Android源代码里有某些内存泄漏,不属于我们应用的泄漏,那么就可以排除掉。
另外,如果不想监控某些特殊的活动,那么可以在onActivityDestroyed(活动性)里,过滤掉特殊的活性,只对其它活动调用refWatcher.watch(活性)监控。
把内存泄漏数据上传至服务器
在LeakCanary提供了AbstractAnalysisResultService,它是一个intentService,接收到的intent内包含了HeapDump数据和AnalysisResult结果,我们只要继承这个类,实现自己的listenerServiceClass,就可以将堆数据和分析结果上传到我们自己的服务器上。
小结
本文通过源代码分析了LeakCanary的原理,并提出了一些有趣的问题,学习了一些实用的知识点。希望对读者有所启发,欢迎与我讨论。
Android开发带你学开源项目:LeakCanary-如何检测活动是否泄漏
转载https://www.codesocang.com/appboke/38275.html