LeakCanary从入门到源码分析
来源:互联网 发布:js的缺点 编辑:程序博客网 时间:2024/06/14 05:21
“A small leak will sink a great ship.” - Benjamin Franklin
千里之堤, 毁于蚁穴。 – 《韩非子·喻老》
LeakCanary是什么?可以从LeakCanary的github很容易的得到定义:
Android|Java的内存检测库
更多使用方法:https://www.liaohuqiu.net/cn/posts/leak-canary-read-me/
简单集成
在的build.gradle
:
dependencies { 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' }
在你的Application
class:
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this); // Normal app init code... }}
然后什么都不用做,当出现内存泄漏的时候LeakCanary就会在你的通知栏发送一条,点进去就是泄漏日志。
Dump泄漏的hprof文件
从初始化入口LeakCanary.install(this)
可以很清楚的看到得到了一个AndroidRefWatcherBuilder
对象
public static RefWatcher install(Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); } /** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */ public static AndroidRefWatcherBuilder refWatcher(Context context) { return new AndroidRefWatcherBuilder(context); }
而这个对象extends RefWatcherBuilder
进入RefWatcherBuilder
可以看出这是一个采用构造者设计模式所以直接进入build方法进行查看
/** Creates a {@link RefWatcher}. */ public final RefWatcher build() { if (isDisabled()) { return RefWatcher.DISABLED; } ExcludedRefs excludedRefs = this.excludedRefs; if (excludedRefs == null) { excludedRefs = defaultExcludedRefs(); } HeapDump.Listener heapDumpListener = this.heapDumpListener; if (heapDumpListener == null) { heapDumpListener = defaultHeapDumpListener(); } DebuggerControl debuggerControl = this.debuggerControl; if (debuggerControl == null) { debuggerControl = defaultDebuggerControl(); } HeapDumper heapDumper = this.heapDumper; if (heapDumper == null) { heapDumper = defaultHeapDumper(); } WatchExecutor watchExecutor = this.watchExecutor; if (watchExecutor == null) { watchExecutor = defaultWatchExecutor(); } GcTrigger gcTrigger = this.gcTrigger; if (gcTrigger == null) { gcTrigger = defaultGcTrigger(); } return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener, excludedRefs); }
HeapDumper
可以从名字我们可以猜测用于dump hprof文件的、一样的我们点进去这个类发现是一个接口,所以找到他的实现类AndroidHeapDumper
@Override public File dumpHeap() { File heapDumpFile = leakDirectoryProvider.newHeapDumpFile(); if (heapDumpFile == RETRY_LATER) { return RETRY_LATER; } FutureResult<Toast> waitingForToast = new FutureResult<>(); showToast(waitingForToast); if (!waitingForToast.wait(5, SECONDS)) { CanaryLog.d("Did not dump heap, too much time waiting for Toast."); return RETRY_LATER; } Toast toast = waitingForToast.get(); try { Debug.dumpHprofData(heapDumpFile.getAbsolutePath()); cancelToast(toast); return heapDumpFile; } catch (Exception e) { CanaryLog.d(e, "Could not dump heap"); // Abort heap dump return RETRY_LATER; } }
稍微提及一下LeakDirectoryProvider
这个类主要用于初始化SD卡存放LeakCanary生成的文件目录,大概位置于/sdcard/download/leakcanary-你的包名
回到我们的AndroidHeapDumper
类、我们只需要关心一行代码
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
采用Android自带的Debug
来来doump hprof文件然后保存到LeakDirectoryProvider
初始化的文件目录下
什么时候Dump
回到LeakCanary
类中的buildAndInstall
方法
public RefWatcher buildAndInstall() { RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { LeakCanary.enableDisplayLeakActivity(context); ActivityRefWatcher.install((Application) context, refWatcher); } return refWatcher; }
主要做两步
第一步:enableDisplayLeakActivity
进入这个方法把DisplayLeakActivity
这个Activity组件恢复正常使用
public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) { ComponentName component = new ComponentName(appContext, componentClass); PackageManager packageManager = appContext.getPackageManager(); int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED; // Blocks on IPC. packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP); }
第二步也是主要步骤进入ActivityRefWatcher
类初始化Application.ActivityLifecycleCallbacks
监听
private final 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); } };
对的,就是监听当一个Activity执行onDestory的时候进行监听它
void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }
自然而然我们需要进入RefWatcher.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); final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue); ensureGoneAsync(watchStartNanoTime, reference); }
很巧妙的使用KeyedWeakReference
弱引用,为了确保弱引用能被回收“偷偷”的跑了Runtime.getRuntime().gc()
来实现对弱引用的回收
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); 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(); removeWeaklyReachableReferences(); if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); 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)); } return DONE; }
我们再watch方法中每次都会把当前需要检测的对象或者说是Activity组件加入Set<String> retainedKeys
这个Set
容器中,然后系统就会调用gcTrigger.runGc()
来回收KeyedWeakReference
弱引用
GcTrigger DEFAULT = new GcTrigger() { @Override public void runGc() { // Code taken from AOSP FinalizationTest: // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/ // java/lang/ref/FinalizationTester.java // System.gc() does not garbage collect every time. Runtime.gc() is // more likely to perfom a gc. Runtime.getRuntime().gc(); enqueueReferences(); System.runFinalization(); } private void enqueueReferences() { // Hack. We don't have a programmatic way to wait for the reference queue daemon to move // references to the appropriate queues. try { Thread.sleep(100); } catch (InterruptedException e) { throw new AssertionError(); } } };
那怎么确定这个Activity发生泄漏了呢?
首先我们需要明白一个道理,每次初始化传入了一个ReferenceQueue
这个队列是用来存放每当当前的弱引用被GC回收了,那么当前这个弱引用对象就会被存入到这个Queue中去
,所以每次只要能从retainedKeys
把当前的KeyedWeakReference
弱引用对应的key移除那么就证明没有发生泄漏,而当泄漏的话queue
就没有供retainedKeys
移除的key值
private void removeWeaklyReachableReferences() { // 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. KeyedWeakReference ref; while ((ref = (KeyedWeakReference) queue.poll()) != null) { retainedKeys.remove(ref.key); } } private boolean gone(KeyedWeakReference reference) { return !retainedKeys.contains(reference.key); }
可以从这两个方法很好的看出来就是这么确认是否发生了泄漏。
一旦发生了泄漏就开始Dump hprof文件了
if (!gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); 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)); }
如何解析hprof
当发生了泄漏就会生成HeapDump
对象然后就会进入下面这个方法去启动HeapAnalyzerService
Service来进行分析
@Override public void analyze(HeapDump heapDump) { checkNotNull(heapDump, "heapDump"); HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass); }
然后这边想一提带过就好,因为这边使用的是Square haha
库来进行解析dump下来的hprof文件
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) { long analysisStartNanoTime = System.nanoTime(); if (!heapDumpFile.exists()) { Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile); return failure(exception, since(analysisStartNanoTime)); } try { HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile); HprofParser parser = new HprofParser(buffer); Snapshot snapshot = parser.parse(); deduplicateGcRoots(snapshot); Instance leakingRef = findLeakingReference(referenceKey, snapshot); // False alarm, weak reference was cleared in between key check and heap dump. if (leakingRef == null) { return noLeak(since(analysisStartNanoTime)); } return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef); } catch (Throwable e) { return failure(e, since(analysisStartNanoTime)); } }
想要了解更多可以去查看haha更多的使用方法。
经过解析之后机会把数据传递到DisplayLeakService
这是个Service会根据传入进来的数据发送通知栏通知,然后并存入数据,当你点击对应的通知进入DisplayLeakActivity
界面就能显示泄漏日志了。
差不多这就是LeakCanary
如何监听泄漏、Dump泄漏、分析泄漏、显示泄漏的主要流程了,当然更重要的是发生了泄漏要懂得修复才是硬道理。
- LeakCanary从入门到源码分析
- LeakCanary源码分析
- LeakCanary源码分析
- LeakCanary源码分析
- LeakCanary源码分析
- LeakCanary源码分析第一讲
- 内存泄露检测神器 -- LeakCanary源码分析
- TensorFlow 从入门到精通(二):MNIST 例程源码分析
- TensorFlow 从入门到精通(三):ImageNet 例程源码分析
- TensorFlow 从入门到精通(四):CIFAR10 多 GPU 版本例程源码分析
- UiAutomator从入门到原理(源码)
- Vue源码 --- 从入门到放弃
- Picasso从入门到源码解析
- Rxjava2从入门到源码(一)
- Rxjava2从入门到源码(二)
- [源码]LeakCanary
- Django Signals 从实践到源码分析
- python 从SocketServer到 WSGIServer 源码分析、
- AJAX跨域的常见方法
- [C++] 实现二叉搜索树
- Android开源项目推荐之「图片加载到底哪家强」
- 【选择排序】和【堆排序】
- 设计模式-结构型软件设计模式(一)
- LeakCanary从入门到源码分析
- Cocos2d-x 之动作 Action
- Python的排序功能
- popupwindow弹出的editTextView
- Oracle数据库基本了解
- CookieSyncManager与CookieManager
- 交易背书的基本工作流程
- 单设备登录遇到并发出现的问题
- 编程之战第十四章 迷路的牛仔