LeakCanary 与 鹅场Matrix ResourceCanary对比分析

匿名 (未验证) 提交于 2019-12-02 23:47:01

推荐阅读:

滴滴Booster移动App质量优化框架-学习之旅 一

Android 模块Api化演练

不一样视角的Glide剖析(一)

1、组件启动

LeakCanary自动注册启动

原理:专门定制了一个ContentProvider,来注册启动LeakCanary

实现如下:

/**  * Content providers are loaded before the application class is created. [LeakSentryInstaller] is  * used to install [leaksentry.LeakSentry] on application start.  */ internal class LeakSentryInstaller : ContentProvider() {    override fun onCreate(): Boolean {     CanaryLog.logger = DefaultCanaryLog()     val application = context!!.applicationContext as Application     InternalLeakSentry.install(application)     return true   }      ... }

public class MatrixApplication extends Application {     ...     @Override     public void onCreate() {         super.onCreate();         ...         ResourcePlugin resPlugin = null;         if (matrixEnable) {            resPlugin = new ResourcePlugin(new ResourceConfig.Builder()                     .dynamicConfig(dynamicConfig)                     .setDumpHprof(false)                     .setDetectDebuger(true)     //only set true when in sample, not in your app                     .build())             //resource             builder.plugin(resPlugin );             ResourcePlugin.activityLeakFixer(this);             ...         }          Matrix.init(builder.build());         if(resPlugin != null){             resPlugin.start();          }      }    }

2、watcher范围和自动wacherd的对象

class RefWatcher{     fun watch(watchedInstance: Any) {...}     fun watch( watchedInstance: Any,name: String) {...} }

支持自动watcher Activity、Fragment、Fragment.View对象

1.自动watcher Activity

internal class ActivityDestroyWatcher { private val lifecycleCallbacks =     object : Application.ActivityLifecycleCallbacks by noOpDelegate() {       override fun onActivityDestroyed(activity: Activity) {         if (configProvider().watchActivities) {             refWatcher.watch(activity)         }       }     }    companion object {     fun install(... ) {       val activityDestroyWatcher =         ActivityDestroyWatcher(refWatcher, configProvider)  application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)     }   } }

ActivityDestroyWatcher.install在LeakSentryInstaller.onCreate间接调用,注册ActivityLifecycleCallbacks 监听Activity的生命周期,从而实现自动watcher Activity对象。

2.自动watcher Fragment、Fragment.View

//子类有 //SupportFragmentDestroyWatcher //AndroidOFragmentDestroyWatcher internal interface FragmentDestroyWatcher {    fun watchFragments(activity: Activity)    companion object {     ...     fun install(... ) {           ...       application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {         override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) {           for (watcher in fragmentDestroyWatchers) {             watcher.watchFragments(activity)           }         }       })     }     } }

internal class XXXFragmentDestroyWatcher(...) : FragmentDestroyWatcher {    private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {      override fun onFragmentViewDestroyed(       fm: FragmentManager,       fragment: Fragment     ) {       val view = fragment.view       if (view != null && configProvider().watchFragmentViews) {          //watcher view          refWatcher.watch(view)       }     }      override fun onFragmentDestroyed(       fm: FragmentManager,       fragment: Fragment     ) {       if (configProvider().watchFragments) {         //watcher fragment         refWatcher.watch(fragment)       }     }   }       //AndroidOFragmentDestroyWatcher   override fun watchFragments(activity: Activity) {     val fragmentManager = activity.fragmentManager     fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)   }     //SupportFragmentDestroyWatcher   override fun watchFragments(activity: Activity) {     if (activity is FragmentActivity) {       val supportFragmentManager = activity.supportFragmentManager       supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)     }   } }

Replugin 只有一个ActivityRefWatcher,只支持watcher Activity,也是通过注册ActivityLifecycleCallbacks 监听Activity的生命周期,从而实现自动watcher Activity对象。

public class ActivityRefWatcher extends FilePublisher implements Watcher {      @Override     public void start() {         stopDetect();         final Application app = mResourcePlugin.getApplication();         if (app != null) {             app.registerActivityLifecycleCallbacks(mRemovedActivityMonitor);             //轮询检测是否发生溢出             scheduleDetectProcedure();                     }     }
private final Application.ActivityLifecycleCallbacks mRemovedActivityMonitor = new ActivityLifeCycleCallbacksAdapter() {     @Override    public void onActivityDestroyed(Activity activity) {        //push mDestroyedActivityInfos集合中,通过轮询检测对mDestroyedActivityInfos进行处理        pushDestroyedActivityInfo(activity);        synchronized (mDestroyedActivityInfos) {            mDestroyedActivityInfos.notifyAll();        }    }};
 

3、检测泄露实现

1.检测线程

LeakCanay检测实现,旧版本是在一个HandlerThread 轮询检测,现在发生改变,先在主线程中触发检测,由RefWatcher.watch主动触发,对activity,Fragment,Fragment.view的检测,即由生命周期触发,然后在

非主线程中进行真正的check

现在主线中被动触发检测依据如下:

class RefWatcher{      fun watch( watchedInstance: Any,name: String) {     ...     watchedInstances[key] = reference     checkRetainedExecutor.execute {       moveToRetained(key)     }    } }   internal object InternalLeakSentry {    ...   private val checkRetainedExecutor = Executor {     mainHandler.postDelayed(it, LeakSentry.config.watchDurationMillis)   }   val refWatcher = RefWatcher(       clock = clock,       checkRetainedExecutor = checkRetainedExecutor,       onInstanceRetained = { listener.onReferenceRetained() },       isEnabled = { LeakSentry.config.enabled }   ) ... }

从moveToRetained调用,最终辗转到HeapDumpTrigger的方法scheduleRetainedInstanceCheck方法,然后在非主线中进行真正check,代码如下:

internal class HeapDumpTrigger() {  private fun scheduleRetainedInstanceCheck(reason: String) {     if (checkScheduled) {       CanaryLog.d("Already scheduled retained check, ignoring ($reason)")       return     }     checkScheduled = true     //非主线程hanlder     backgroundHandler.post {       checkScheduled = false       checkRetainedInstances(reason)     }   } ... }

ResourcePlugin参考LeakCanary旧版本,采用线程轮询检测,依据如下:

 

//ActivityRefWatcher.start
private void scheduleDetectProcedure() {


  mDetectExecutor.executeInBackground(mScanDestroyedActivitiesTask);
}

class RetryableTaskExecutor{      private void postToBackgroundWithDelay(final RetryableTask task, final int failedAttempts) {          //非主线程 handler          mBackgroundHandler.postDelayed(new Runnable() {             @Override             public void run() {                 RetryableTask.Status status = task.execute();                 if (status == RetryableTask.Status.RETRY) {                     postToBackgroundWithDelay(task, failedAttempts + 1);                 }             }         }, mDelayMillis);     } }

原理:VM会将可回收的对象加入 WeakReference 关联的 ReferenceQueue

 var retainedReferenceCount = refWatcher.retainedInstanceCount      if (retainedReferenceCount > 0) {       gcTrigger.runGc()       retainedReferenceCount = refWatcher.retainedInstanceCount     }

if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {       showRetainedCountWithDebuggerAttached(retainedReferenceCount)       scheduleRetainedInstanceCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)       return     }

 val heapDumpFile = heapDumper.dumpHeap()  if (heapDumpFile == null) {     showRetainedCountWithHeapDumpFailed(retainedReferenceCount)     return}

在旧版本中,在个别系统上可能存在误报,原因大致如下:

  • VM 并没有提供强制触发 GC 的 API ,通过 System.gc()Runtime.getRuntime().gc()只能“建议”系统进行 GC ,如果系统忽略了我们的 GC 请求,可回收的对象就不会被加入 ReferenceQueue

  • 将可回收对象加入 ReferenceQueue 需要等待一段时间,LeakCanary 采用延时 100ms 的做法加以规避,但似乎并不绝对管用

  • 监测逻辑是异步的,如果判断 Activity 是否可回收时某个 Activity 正好还被某个方法的局部变量持有,就会引起误判

  • 若反复进入泄漏的 Activity ,LeakCanary 会重复提示该 Activity 已泄漏

现在这个2.0-alpha-2版本也没有进行排重,当然这个也不好说,假如一个Activity有多处泄露,且泄露原因不同,排重 就会导致漏报。

原理:直接通过WeakReference.get()来判断对象是否已被回收,避免因延迟导致误判

1)判断当前mDestroyedActivityInfos是否空,为空的话,就没必要泄露,因为是轮询,所以要防止CPU空转,浪费电

// If destroyed activity list is empty, just wait to save power. while (mDestroyedActivityInfos.isEmpty()) {     synchronized (mDestroyedActivityInfos) {         try {                mDestroyedActivityInfos.wait();         } catch (Throwable ignored) {            // Ignored.         }     } }

2)根据配置开关和是否在Debug调试,如果配置开关开启且在调试,跳过此次check,等待下次轮询,调试结束

// Fake leaks will be generated when debugger is attached. if (Debug.isDebuggerConnected() && !mResourcePlugin.getConfig().getDetectDebugger()) {         MatrixLog.w(TAG, "debugger is connected, to avoid fake result, detection was delayed.");         return Status.RETRY; }

3)增加一个一定能被回收的“哨兵”对象,用来确认系统确实进行了GC,没有进行GC,则跳过此次check,等待下次轮询

final WeakReference<Object> sentinelRef = new WeakReference<>(new Object()); triggerGc(); if (sentinelRef.get() != null) {    // System ignored our gc request, we will retry later.    MatrixLog.d(TAG, "system ignore our gc request, wait for next detection.");    return Status.RETRY; }

4)对已判断为泄漏的Activity,记录其类名,避免重复提示该Activity已泄漏,有效期一天

final DestroyedActivityInfo destroyedActivityInfo = infoIt.next(); if (isPublished(destroyedActivityInfo.mActivityName)) {     MatrixLog.v(TAG, "activity with key [%s] was already published.", destroyedActivityInfo.mActivityName);     infoIt.remove();     continue; }

前面已经提过排重还是有缺陷的,比如一个Activity有多处泄露,且泄露原因不同,排重 就会导致漏报

5)若发现某个Activity无法被回收,再重复判断3次,且要求从该Activity被记录起有2个以上的Activity被创建才认为是泄漏,以防在判断时该Activity被局部变量持有导致误判

++destroyedActivityInfo.mDetectedCount; long createdActivityCountFromDestroy = mCurrentCreatedActivityCount.get() - destroyedActivityInfo.mLastCreatedActivityCount; if (destroyedActivityInfo.mDetectedCount < mMaxRedetectTimes                     || (createdActivityCountFromDestroy < CREATED_ACTIVITY_COUNT_THRESHOLD && !mResourcePlugin.getConfig().getDetectDebugger())) {     // Although the sentinel tell us the activity should have been recycled,     // system may still ignore it, so try again until we reach max retry times.    continue; }

6.根据是否设置了mHeapDumper(即配置快关),若设置了,进行dumpHeap,然后开启服务CanaryWorkerService,进行shrinkHprofAndReport,否则进行简单的onDetectIssue

if (mHeapDumper != null) {     final File hprofFile = mHeapDumper.dumpHeap();     if (hprofFile != null) {         markPublished(destroyedActivityInfo.mActivityName);         final HeapDump heapDump = new HeapDump(hprofFile, destroyedActivityInfo.mKey, destroyedActivityInfo.mActivityName);         mHeapDumpHandler.process(heapDump);         infoIt.remove();     } else {          infoIt.remove();      } } else {                            markPublished(destroyedActivityInfo.mActivityName);        if (mResourcePlugin != null) {              ...                         mResourcePlugin.onDetectIssue(new Issue(resultJson));                           } }

4、Hprof裁剪和分析(暂时不详细分析)

LeakCanary没有对Hprof文件进行shrink裁剪,使用haha进行解析,分析出其泄露对象的GC Root引用链,把检测和分析都放在客户端。

ResourcePlugin只有检测和Hprof文件shrink功能,不支持在客户端Hprof文件,需要利用其提供的jar单独对进行分析Hprof,在分析过程中也可以把找出冗余Bitmap的GC ROOT链。

裁剪Hprof文件源码见:HprofBufferShrinker().shrink

冗余Bitmap分析器:DuplicatedBitmapAnalyzer

Activity泄露分析器:ActivityLeakAnalyzer

Hprof 文件的大小一般约为 Dump 时的内存占用大小,Dump 出来的 Hprof 大则一百多M,,如果不做任何处理直接将此 Hprof 文件上传到服务端,一方面会消耗大量带宽资源,另一方面服务端将 Hprof 文件长期存档时也会占用服务器的存储空间。

通过分析 Hprof 文件格式可知,Hprof 文件中 buffer 区存放了所有对象的数据,包括字符串数据、所有的数组等,而我们的分析过程却只需要用到部分字符串数据和 Bitmap 的 buffer 数组,其余的 buffer 数据都可以直接剔除,这样处理之后的 Hprof 文件通常能比原始文件小 1/10 以上。

LeakCanary 中的引用链查找算法都是针对单个目标设计的,ResourceCanary 中查找冗余 Bitmap 时可能找到多个结果,如果分别对每个结果中的 Bitmap 对象调用该算法,在访问引用关系图中的节点时会遇到非常多的重复访问的节点,降低了查找效率。为此我们修改了 LeakCanary 的引用链查找算法,使其在一次调用中能同时查找多个目标到 GC Root 的最短引用链。

如果您对博主的更新内容持续感兴趣,请关注公众号!


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!