当前位置:首页 > 安卓源码 > 技术博客 >

Android开发带你学开源项目:LeakCanary-如何检测活动是否泄漏

时间:2018-01-08 17:31 来源:互联网 作者:源码搜藏 浏览: 收藏 挑错 推荐 打印

OOM是Android开发中常见的问题,而内存泄漏往往是罪魁祸首。 Square开源了LeakCanary,它可以实时监测活动是否发生了泄漏,一旦发现就会自动弹出提示及相关的泄漏信息供分析。 本文的目的是试图通过分析LeakCanary源码来探讨它的Activity泄漏检测机制。 Leak

OOM是Android开发中常见的问题,而内存泄漏往往是罪魁祸首。

Square开源了LeakCanary,它可以实时监测活动是否发生了泄漏,一旦发现就会自动弹出提示及相关的泄漏信息供分析。

本文的目的是试图通过分析LeakCanary源码来探讨它的Activity泄漏检测机制。

LeakCanary使用方式

为了将LeakCanary引入到我们的项目里,我们只需要做以下两步:


  1. 依赖{ 
  2.  debugCompile'c​​om.squareup.leakcanary  :leakcanary-android:1.5.1' 
  3.  releaseCompile'c​​om.squareup.leakcanary  :leakcanary-android-no-op:1.5.1' 
  4.  testCompile'c​​om.squareup.leakcanary  :leakcanary-android-no-op:1.5.1' } public  class ExampleApplication extends Application {@Override  public  void onCreate(){super.onCreate(); 如果(LeakCanary.isInAnalyzerProcess(本)){//这过程   专用   LeakCanary   堆分析。 
  5.       //你应该   初始化您的应用程序   此过程中。 
  6.       返回
  7.     } 
  8.     LeakCanary.install(本); 
  9.   } 

可以看出,最关键的就是LeakCanary.install(this); 这么一句话,正式开启了LeakCanary的大门,未来它会自动帮我们检测内存泄漏,并在发生泄漏是弹出通知信息。

从LeakCanary.install(this); 开始

下面我们来看下它做了些什么?


  1. 公共静态 RefWatcher安装(应用程序应用程序){   返回 安装(应用程序,DisplayLeakService.class,  
  2.       AndroidExcludedRefs.createAppDefaults()建立())。 
  3. } 公共静态 RefWatcher安装(应用程序的应用程序,  
  4.     类<?扩展AbstractAnalysisResultService> listenerServiceClass, 
  5.     ExcludedRefs excludedRefs){if(isInAnalyzerProcess(application)){     return  RefWatcher.DISABLED; 
  6.   } 
  7.   enableDisplayLeakActivity(应用程序); 
  8.   HeapDump.Listener heapDumpListener = new ServiceHeapDumpListener(application,listenerServiceClass); 
  9.   RefWatcher refWatcher = androidWatcher(application,heapDumpListener,excludedRefs); 
  10.   ActivityRefWatcher.installOnIcsPlus(application,refWatcher);  返回 refWatcher; 

首先,我们先看最重要的部分,就是:


  1. RefWatcher refWatcher = androidWatcher(application,heapDumpListener,excludedRefs); 
  2. ActivityRefWatcher.installOnIcsPlus(application,refWatcher); 

先生成了一个RefWatcher,这个东西非常关键,从名字可以看出,它是用来观看Reference的,也就是用来一个监控引用的工具。然后再把refWatcher和我们自己提供的应用程序传入到ActivityRefWatcher。 installOnIcsPlus(application,refWatcher); 这句里面,继续看。


  1. 公共静态 无效installOnIcsPlus(应用程序的应用程序,RefWatcher refWatcher){  
  2.     ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application,refWatcher); 
  3.     activityRefWatcher.watchActivities(); 

创建了一个ActivityRefWatcher,大家应该能感受到,这个东西就是用来监控我们的活动泄漏状况的,它调用watchActivities()方法,就可以开始进行监控了。下面就是它监控的核心原理:


  1. public  void watchActivities(){ 
  2.   application.registerActivityLifecycleCallbacks(lifecycleCallbacks); 

它向应用里注册了一个ActivitylifecycleCallbacks的回调函数,可以用来监听应用整个生命周期所有Activity的生命周期事件再看下这个lifecycleCallbacks是什么?


  1. 私人最终Application.ActivityLifecycleCallbacks lifecycleCallbacks = new Application.ActivityLifecycleCallbacks(){@Override  public  void onActivityCreated(Activity activity,Bundle savedInstanceState){ 
  2.       } @Override  public  void onActivityStarted(Activity activity){ 
  3.       } @Override  public  void onActivityResumed(Activity activity){ 
  4.       } @Override  public  void onActivityPaused(Activity activity){ 
  5.       } @Override  public  void onActivityStopped(Activity activity){ 
  6.       } @Override  public  void onActivitySaveInstanceState(Activity activity,Bundle outState){ 
  7.       } @Override  public  void onActivityDestroyed(Activity activity){ 
  8.         ActivityRefWatcher.this.onActivityDestroyed(活性); 
  9.       } 
  10.     }; 

原来它只监听了所有Activity的onActivityDestroyed事件,当Activity被Destory时,调用ActivityRefWatcher.this.onActivityDestroyed(activity); 函数。

猜测下,正常情况下,当一个这个函数应该活动被Destory时,那这个活动对象应该变成null才是正确的。如果没有变成null,那么就意味着发生了内存泄漏。

因此我们向,这个函数ActivityRefWatcher.this.onActivityDestroyed(activity); 应该是用来监听活动对象是否变成了null。继续看。


  1. void onActivityDestroyed(Activity activity){ 
  2.   refWatcher.watch(活性); 
  3. RefWatcher refWatcher = androidWatcher(application,heapDumpListener,excludedRefs); 

可以看出,这个函数把目标活动对象传给了RefWatcher,让它去监控这个活动是否被正常回收了,若未被回收,则意味着发生了内存泄漏。

RefWatcher如何监控活动是否被正常回收呢?

我们先来看看这个RefWatcher究竟是个甚么东西?


  1. 公共静态 RefWatcher androidWatcher(上下文上下文,HeapDump.Listener heapDumpListener,  
  2.     ExcludedRefs excludedRefs){ 
  3.   AndroidHeapDumper heapDumper = new AndroidHeapDumper(context,leakDirectoryProvider); 
  4.   heapDumper.cleanup();  int  watchDelayMillis = 5000; 
  5.   AndroidWatchExecutor executor = new AndroidWatchExecutor(watchDelayMillis);  返回 新的RefWatcher(执行器,debuggerControl ,GcTrigger,DEFAULT ,heapDumper, 
  6.       heapDumpListener,excludedRefs); 

这里面涉及到两个新的对象:AndroidHeapDumper和AndroidWatchExecutor,前者用来转储堆内存状态的,后者则是用来观看一个引用的监听器。具体原理后面再看。总之,这里已经生成好了一个RefWatcher对象了。

现在再看上面onActivityDestroyed(Activity activity)里调用的refWatcher.watch(activity); ,下面来看下这个最为核心的watch(activity)方法,了解它是如何监控activity是否被回收的。


  1. private final  Set <String> retainKeys; public  void watch(Object activity,String referenceName){ 
  2.   String  key  = UUID.randomUUID()。toString(); 
  3.   retainedKeys。); final KeyedWeakReference reference = new KeyedWeakReference(activity,  key ,referenceName,queue); 
  4.  
  5.   watchExecutor。execute (new Runnable(){@Override  public  void run(){ 
  6.       ensureGone(reference,watchStartNanoTime); 
  7.     } 
  8.   }); 
  9. } final类KeyedWeakReference继承WeakReference <Object> {   public  final String  key ;  公共 最终字符串  名称

可以看到,它首先把我们传入的活动包装成了一个KeyedWeakReference(可以暂时看成一个普通的WeakReference),然后watchExecutor会去执行一个Runnable,这个Runnable会调用ensureGone(reference,watchStartNanoTime)函数。

看这个函数之前猜测下,我们知道watch函数本身就是用来监听activity是否被正常回收,这就涉及到两个问题:

  1. 何时去检查它是否回收?
  2. 如何有效地检查它真的被回收?

所以我们觉得ensureGone函数本身要做的事正如它的名字,就是确保参考被回收掉了,否则就意味着内存泄漏。

核心函数:ensureGone(reference)检测回收

下面来看这个函数实现:


  1. void ensureGone(KeyedWeakReference reference,long watchStartNanoTime){ 
  2.   removeWeaklyReachableReferences(); if(gone(reference)|| debuggerControl.isDebuggerAttached()){     return
  3.   } 
  4.   gcTrigger.runGc(); 
  5.   removeWeaklyReachableReferences(); 如果(!去(参考)){ 
  6.     文件heapDumpFile = heapDumper.dumpHeap(); 
  7.     heapdumpListener.analyze(新HEAPDUMP(heapDumpFile,参考关键,参考名称,excludedRefs,watchDurationMs, 
  8.             gcDurationMs,heapDumpDurationMs)); 
  9.   } 
  10. }私人布尔没了(KeyedWeakReference引用){   return  !retainedKeys。含有(参考); 
  11. } private void removeWeaklyReachableReferences(){ 
  12.   KeyedWeakReference ref; while((ref =(KeyedWeakReference)queue.poll())!=  null ){ 
  13.     保留钥匙(参考钥匙); 
  14.   } 

这里先来解释下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对应的对象都没有被回收。

  1. ensureGone首先调用removeWeaklyReachableReferences把已被回收的对象的key从retainKeys移除,剩下的key都是未被回收的对象;
  2. if(gone(reference))用来判断某个引用的key是否仍在retainKeys里,若不在,表示已回收,否则继续;
  3. gcTrigger.runGc(); 手动出发GC,立即把所有WeakReference引用的对象回收;
  4. removeWeaklyReachableReferences(); 再次清理retainKeys,如果该参考还在remainingKeys里(if(!gone(reference))),表示泄漏;
  5. 利用heapDumper把内存情况dump成文件,并调用heapdumpListener进行内存分析,进一步确认是否发生内存泄漏。
  6. 如果确认发生内存泄漏,调用DisplayLeakService发送通知。

至此,核心的内存泄漏检测机制便看完了。

内存泄漏检测小结

从上面我们大概了解了内存泄漏检测机制,大概是以下几个步骤:

  1. 利用application.registerActivityLifecycleCallbacks(lifecycleCallbacks)来监听整个生命周期内的Activity onDestoryed事件;
  2. 当某个活动被destory后,将它传给RefWatcher去做观测,确保其后续会被正常回收;
  3. RefWatcher首先把活动使用KeyedWeakReference引用起来,并使用一个ReferenceQueue来记录该KeyedWeakReference指向的对象是否已被回收;
  4. AndroidWatchExecutor会在5s后,开始检查这个弱引用内的Activity是否被正常回收。判断条件是:若Activity被正常回收,那么引用它的KeyedWeakReference会被自动放入ReferenceQueue中。
  5. 判断方式是:先看活动对应的KeyedWeakReference是否已经放入ReferenceQueue中;如果没有,则手动GC:gcTrigger.runGc(); ;然后再一次判断ReferenceQueue是否已经含有对应的KeyedWeakReference。若还未被回收,则认为可能发生内存泄漏。
  6. 利用HeapAnalyzer对转储的内存情况进行分析并进一步确认,若确定发生泄漏,则利用DisplayLeakService发送通知。

探讨一些关于LeakCanary有趣的问题

在学习了LeakCanary的源码之后,我想再提几个有趣的问题做些探讨。

LeakCanary项目目录结构为什么这样分?

下面是整个LeakCanary的项目结构:

Android开发带你学开源项目: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去进行检查。它会做什么呢?


  1. @Override  public  void  execute (final Runnable command){if(isOnMainThread()){ 
  2.     executeDelayedAfterIdleUnsafe(命令); 
  3.   }  else  { 
  4.     mainHandler.post(new Runnable(){@Override  public  void run(){ 
  5.         executeDelayedAfterIdleUnsafe(命令); 
  6.       } 
  7.     }); 
  8.   } 
  9. }空隙executeDelayedAfterIdleUnsafe(最终可运行可运行){//这需要   被称为   主线程。 
  10.   Looper.myQueue()。addIdleHandler(new MessageQueue.IdleHandler(){@   Override public boolean queueIdle(){ 
  11.       backgroundHandler.postDelayed(runnable,delayMillis);      返回false  
  12.     } 
  13.   }); 

它可以看到,它首先会向主线程的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.如何创建一个优先级低的主线程任务,它只会在主线程空闲时才执行,不会影响到应用程序的性能?


  1. 新的MessageQueue.IdleHandler(){@覆盖  公共 布尔queueIdle(){/ /任务 
  2.         返回false ; //  只有 一次  
  3.       } 
  4.     }); 

2.如何快速创建一个主/子线程处理程序?


  1. //主线程handlermainHandler = new Handler(Looper.getMainLooper()); //子线程handlerHandlerThread handlerThread = new HandlerThread(“子线程任务”); 
  2. handlerThread.start(); 
  3. Handler backgroundHandler = new Handler(handlerThread.getLooper()); 

3.如何快速判断当前是否运行在主线程?


  1. Looper.getMainLooper()。getThread()== Thread.currentThread(); 

System.gc()可以触发立即gc吗?如果不行那怎么才能触发即时gc呢?

在LeakCanary里,需要立即触发gc,并在之后立即判断弱引用是否被回收。这意味着该gc必须能够立即同步执行。

常用的触发gc方法是System.gc(),那它能达到我们的要求吗?

我们来看下其实现方式:


  1. / ** 
  2.  *表示   虚拟机,这将是一个很好的  时间 运行  
  3.  * 垃圾收集器。请注意,这   一个暗示  有   保证  
  4.  *垃圾收集器将实际运行。 
  5.  * / public static  void gc(){boolean shouldRunGC; 同步(锁定){  
  6.         shouldRunGC = justRanFinalization; if(shouldRunGC){ 
  7.             justRanFinalization =  false
  8.         }  else  { 
  9.             runGC =  true
  10.         } 
  11.     } if(shouldRunGC){ 
  12.         调用Runtime.getRuntime()GC(); 
  13.     } 

注意里清楚说了,System.gc()只是建议垃圾回收器来执行回收,但是不能保证真的去回收。从代码也能看出,必须先判断shouldRunGC才能决定是否真的要gc。

知识点:

那要怎么实现即时GC呢?

LeakCanary参考了一段AOSP的代码


  1. // System.gc()的确实  不是 垃圾收集每一个  时间Runtime.gc()  //更可能   。perfom一个gc.Runtime.getRuntime()GC(); 
  2. enqueueReferences(); 
  3. System.runFinalization(); 公共静态 无效enqueueReferences(){/ *  
  4.      *黑客。我们没有一个编程的方式   等待   参考队列 
  5.      *守护进程  移动引用 合适的队列。    
  6.      * / 
  7.     尝试{ 
  8.         了Thread.sleep(100); 
  9.     catch(InterruptedException e){抛出新的AssertionError(); 
  10.     } 

可以怎样来改造LeakCanary呢?

忽略某些已知泄漏的类或活性

LeakCanary提供了ExcludedRefs类,可以向里面添加某些主动忽略的类。比如已知的Android源代码里有某些内存泄漏,不属于我们应用的泄漏,那么就可以排除掉。

另外,如果不想监控某些特殊的活动,那么可以在onActivityDestroyed(活动性)里,过滤掉特殊的活性,只对其它活动调用refWatcher.watch(活性)监控。

把内存泄漏数据上传至服务器

在LeakCanary提供了AbstractAnalysisResultService,它是一个intentService,接收到的intent内包含了HeapDump数据和AnalysisResult结果,我们只要继承这个类,实现自己的listenerServiceClass,就可以将堆数据和分析结果上传到我们自己的服务器上。

小结

本文通过源代码分析了LeakCanary的原理,并提出了一些有趣的问题,学习了一些实用的知识点。希望对读者有所启发,欢迎与我讨论。

Android开发带你学开源项目:LeakCanary-如何检测活动是否泄漏 转载https://www.codesocang.com/appboke/38275.html

技术博客阅读排行

最新文章