android 内存检测框架,Android内存泄漏框架LeakCanary源码分析

LeakCanary源码分析

LeakCanary是一个内存泄漏检测的框架,默认只会检测Activity的泄漏,如果需要检测其他类,可以使用LeakCanary.install返回的RefWatcher,调用RefWatcher.watch(obj)就可以观测obj对象是否出现泄漏。

从install方法开始:

public static RefWatcher install(Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall();

}

最终返回了一个RefWatcher对象:

public RefWatcher buildAndInstall() { RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { LeakCanary.enableDisplayLeakActivity(context); ActivityRefWatcher.install((Application) context, refWatcher); } return refWatcher;

}

DisplayLeakService

继承自AbstractAnalysisResultService,它是一个IntentService,用来对泄漏数据进行分析处理。

public abstract class AbstractAnalysisResultService extends IntentService { ... @Override protected final void onHandleIntent(Intent intent) { HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA); AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA); try { onHeapAnalyzed(heapDump, result); } finally { //noinspection ResultOfMethodCallIgnored heapDump.heapDumpFile.delete(); } }

}

在AbstractAnalysisResultService的实现DisplayLeakService中,会开启一个DisplayLeakActivity活动进行泄漏的显示:

public class DisplayLeakService extends AbstractAnalysisResultService { @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) { String leakInfo = leakInfo(this, heapDump, result, true); CanaryLog.d("%s", leakInfo); boolean resultSaved = false; boolean shouldSaveResult = result.leakFound || result.failure != null; if (shouldSaveResult) { heapDump = renameHeapdump(heapDump); resultSaved = saveResult(heapDump, result); //保存heapdump到本地 } PendingIntent pendingIntent; String contentTitle; String contentText; if (!shouldSaveResult) { contentTitle = getString(R.string.leak_canary_no_leak_title); contentText = getString(R.string.leak_canary_no_leak_text); pendingIntent = null; } else if (resultSaved) { //heapdump文件保存成功才开启活动 pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey); if (result.failure == null) { String size = formatShortFileSize(this, result.retainedHeapSize); String className = classSimpleName(result.className); if (result.excludedLeak) { contentTitle = getString(R.string.leak_canary_leak_excluded, className, size); } else { contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size); } } else { contentTitle = getString(R.string.leak_canary_analysis_failed); } contentText = getString(R.string.leak_canary_notification_message); } else { contentTitle = getString(R.string.leak_canary_could_not_save_title); contentText = getString(R.string.leak_canary_could_not_save_text); pendingIntent = null; } // New notification id every second. int notificationId = (int) (SystemClock.uptimeMillis() / 1000); showNotification(this, contentTitle, contentText, pendingIntent, notificationId); afterDefaultHandling(heapDump, result, leakInfo); //最后调用一个扩展的接口,我们可以重写这个方法来实现heapdump上传到服务器的功能

}

}

RefWatch

RefWatch是最主要的类:

我们看一下watch方法:

public void watch(Object watchedReference, String referenceName) { if (this == DISABLED) { //首先判断是否禁用了 return; } checkNotNull(watchedReference, "watchedReference"); checkNotNull(referenceName, "referenceName"); final long watchStartNanoTime = System.nanoTime(); String key = UUID.randomUUID().toString(); //为这个引用添加键值 retainedKeys.add(key); //retainedKeys存放未被GC回收的被观察对象 final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); //给被观测的对象创建一个弱引用 ensureGoneAsync(watchStartNanoTime, reference); //开始异步对目标进行观察

}

加入一个任何到执行器中:

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { return ensureGone(reference, watchStartNanoTime); } });

}

检查引用是否有效;GC;再次检查引用是否有效。

弱引用的关联引用队列:当弱引用的对象,弱引用会放入关联的引用队列queue中。

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); //gc开始时间 long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); //已经观察的时间 removeWeaklyReachableReferences(); //把有效引用(线程可达)移出弱引用集合 if (debuggerControl.isDebuggerAttached()) { // The debugger can create false leaks. return RETRY; } if (gone(reference)) { //如果弱引用队列里面已经没有被观测对象的引用了,表示这个对象已经被回收。 return DONE; } gcTrigger.runGc(); //执行GC,并且等待100ms removeWeaklyReachableReferences(); //再次移出有效引用 if (!gone(reference)) { //如果这时候被观测对象的引用不等于null,也就是它仍然被线程可达,说明已经发生了内存泄漏 long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); //计算gc所用时间 File heapDumpFile = heapDumper.dumpHeap(); //把堆栈信息存到本地 if (heapDumpFile == RETRY_LATER) { // Could not dump the heap. return RETRY; } long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); heapdumpListener.analyze( new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); //最后交给heapdump监听器来处理 } return DONE;

}

heapdump信息最终会传递给继承自IntentService的listenerServiceClass方法处理:

public static void runAnalysis(Context context, HeapDump heapDump, Class extends AbstractAnalysisResultService> listenerServiceClass) { Intent intent = new Intent(context, HeapAnalyzerService.class); intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName()); intent.putExtra(HEAPDUMP_EXTRA, heapDump); context.startService(intent);

}

AndroidWatchExecutor

我们来看一下内存泄漏检查的执行器:

public AndroidWatchExecutor(long initialDelayMillis) { mainHandler = new Handler(Looper.getMainLooper()); //主线程Handler HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME); //新建一个带消息循环Looper的子线程 handlerThread.start(); backgroundHandler = new Handler(handlerThread.getLooper()); //后台Handler this.initialDelayMillis = initialDelayMillis; maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;

}

执行检查任务:

@Override public void execute(Retryable retryable) { if (Looper.getMainLooper().getThread() == Thread.currentThread()) { waitForIdle(retryable, 0); //主线程 } else { postWaitForIdle(retryable, 0); //非主线程 }

最终是在主线程调用waitForIdle:

void waitForIdle(final Retryable retryable, final int failedAttempts) { // This needs to be called from the main thread. Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { //添加一个闲置处理器(在主线程消息队列闲置的时候会执行queueIdle) @Override public boolean queueIdle() { postToBackgroundWithDelay(retryable, failedAttempts); return false; } });

} void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) { long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor); long delayMillis = initialDelayMillis * exponentialBackoffFactor; backgroundHandler.postDelayed(new Runnable() { //把任务放到后台线程的消息队列中 @Override public void run() { Retryable.Result result = retryable.run(); //这时候才会执行针对对象的内存泄漏检查 if (result == RETRY) { postWaitForIdle(retryable, failedAttempts + 1); } } }, delayMillis);

}

ActivityRefWatcher

这个类是用来监听一个应用内的所有活动的生命周期,当活动执行了onDestory的时候,开始对Activity内存泄漏的检查。

void onActivityDestroyed(Activity activity) { refWatcher.watch(activity);

}

总结

整个Activity内存泄漏的检测从Activity执行了onDestory后开始:

1. Activity执行onDestory

2. refWatcher把该Activity添加到弱引用列表里面

3. 然后使用IdelHandler机制,在主线程空闲的时候延时 5 秒发送一个Runnable到后台线程HandlerThread的消息队列里

4. HandlerThread取出消息,移除被回收对象的弱引用,然后查找活动是否还存在于弱引用列表中,如果不在,则说明没有发生内存泄漏

5. 如果在,则调用一次gc,等待100ms,再次移除被回收对象的弱引用,查找活动是否还存在弱引用列表里面,如果在,则说明发生了内存泄漏

6. 然后就执行dumpheap,把堆栈信息用Intent的方式发送给IntentService

7. IntentService拿到dumheap之后,保存heapdump到本地,在通知栏上显示内存泄漏的通知,然后执行afterDefaultHandling(我们可以重写)

8. 最后删除本地的heapdump文件

几个问题

1. 为什么要使用idlehandler? 在主线程空闲的时候才把任务放到子线程中执行

2. HandlerThread会退出吗?

改进,用HandlerThread设置为守护线程,当主线程退出时,该线程也就死亡了:

handlerThread.setDaemon(true);

3. 线程安全吗?安全retainedKeys = new CopyOnWriteArraySet<>();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值