https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/
一旦安装了LeakCanary,它就会通过4个步骤自动检测并报告内存泄漏:
- 检测保留的对象。
- 转储堆。
- 分析堆。
- 分类泄漏。
1.检测残留物
LeakCanary可以挂接到Android生命周期中,以自动检测活动和片段何时被破坏并应进行垃圾收集。这些已销毁的对象将传递到ObjectWatcher
,其中包含对它们的弱引用。LeakCanary自动检测以下对象的泄漏:
- 销毁
Activity
实例 - 销毁
Fragment
实例 - 破坏片段
View
实例 - 清除
ViewModel
实例
您可以观看不再需要的任何对象,例如分离视图或损坏的演示者:
<pre id="__code_0" style="box-sizing: inherit; color: var(--md-code-fg-color); font-feature-settings: "kern"; font-family: "Roboto Mono", SFMono-Regular, Consolas, Menlo, monospace; direction: ltr; position: relative; margin: 1em 0px; line-height: 1.4;">AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
</pre>
如果等待5秒钟并运行垃圾回收ObjectWatcher
后未清除由持有的弱引用,则视为被监视的对象已保留,并且有可能泄漏。LeakCanary将此记录到Logcat:
<pre id="__code_1" style="box-sizing: inherit; color: var(--md-code-fg-color); font-feature-settings: "kern"; font-family: "Roboto Mono", SFMono-Regular, Consolas, Menlo, monospace; direction: ltr; position: relative; margin: 1em 0px; line-height: 1.4;">`D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
(Activity received Activity#onDestroy() callback)
... 5 seconds later ...
D LeakCanary: Scheduling check for retained objects because found new object
retained` </pre>
LeakCanary在转储堆之前等待保留对象的数量达到阈值,并显示具有最新数量的通知。

图1. LeakCanary找到了4个保留的对象。
<pre id="__code_2" style="box-sizing: inherit; color: var(--md-code-fg-color); font-feature-settings: "kern"; font-family: "Roboto Mono", SFMono-Regular, Consolas, Menlo, monospace; direction: ltr; position: relative; margin: 1em 0px; line-height: 1.4;">D LeakCanary: Rescheduling check for retained objects in 2000ms because found only 4 retained objects (< 5 while app visible)
</pre>
信息
默认阈值5保留的对象时,应用程序是可见的,和1保留对象时,应用程序是不可见的。如果您看到保留对象的通知,然后将应用程序置于后台(例如,通过按“主页”按钮),则阈值将从5更改为1,LeakCanary会在5秒钟内转储堆。点击通知将强制LeakCanary立即转储堆。
2.转储堆
当保留对象的数量达到阈值时,LeakCanary将Java堆转储到存储在Android文件系统上的.hprof
文件(堆转储)中(请参阅LeakCanary在何处存储堆转储?)。转储堆会使应用程序冻结一小段时间,在此期间LeakCanary会显示以下吐司:

图2. LeakCanary显示了在堆放时的吐司。
3.分析堆
LeakCanary .hprof
使用Shark解析文件,并在该堆转储中找到保留的对象。

图3. LeakCanary在堆转储中查找保留的对象。
对于每个保留的对象,LeakCanary会查找引用路径,以防止对该保留的对象进行垃圾回收:其泄漏跟踪。您将在下一部分中学习分析泄漏跟踪:修复内存泄漏。

图4. LeakCanary计算每个保留对象的泄漏跟踪。
分析完成后,LeakCanary将显示带有摘要的通知,并在Logcat中打印结果。请注意下面的4个保留对象如何被分组为2个不同的泄漏。LeakCanary 为每个泄漏跟踪创建一个签名,并将具有相同签名的泄漏(即,由同一bug引起的泄漏)组合在一起。

图5. 4条泄漏迹线变成2个不同的泄漏特征。
<pre id="__code_3" style="box-sizing: inherit; color: var(--md-code-fg-color); font-feature-settings: "kern"; font-family: "Roboto Mono", SFMono-Regular, Consolas, Menlo, monospace; direction: ltr; position: relative; margin: 1em 0px; line-height: 1.4;">`====================================
HEAP ANALYSIS RESULT
====================================
2 APPLICATION LEAKS
Displaying only 1 leak trace out of 2 with the same signature
Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
┬───
│ GC Root: Local variable in native code
│
...` </pre>
轻触通知将启动提供更多详细信息的活动。稍后点击LeakCanary启动器图标再次回到它:

图6. LeakCanary为安装的每个应用程序添加一个启动器图标。
每行对应一组具有相同签名的泄漏。LeakCanary 在应用程序首次使用该签名触发泄漏时将行标记为“ 新建 ”。

图7.将4个泄漏分组为2行,每个泄漏标记对应一个。
点击泄漏以打开带有泄漏轨迹的屏幕。您可以通过下拉菜单在保留的对象及其泄漏跟踪之间切换。

图8.屏幕显示了3个按其常见泄漏特征分组的泄漏。
的泄漏签名是每个级联的散列参考怀疑导致泄漏,即,每个参考与红色下划线显示:

图9.带有3个可疑参考的泄漏跟踪。
~~~
当泄漏跟踪以文本形式共享时,这些相同的可疑引用都带有下划线:
<pre id="__code_4" style="box-sizing: inherit; color: var(--md-code-fg-color); font-feature-settings: "kern"; font-family: "Roboto Mono", SFMono-Regular, Consolas, Menlo, monospace; direction: ltr; position: relative; margin: 1em 0px; line-height: 1.4;">... │ ├─ com.example.leakcanary.LeakingSingleton class │ Leaking: NO (a class is never leaking) │ ↓ static LeakingSingleton.leakedViews │ ~~~~~~~~~~~ ├─ java.util.ArrayList instance │ Leaking: UNKNOWN │ ↓ ArrayList.elementData │ ~~~~~~~~~~~ ├─ java.lang.Object[] array │ Leaking: UNKNOWN │ ↓ Object[].[0] │ ~~~ ├─ android.widget.TextView instance │ Leaking: YES (View.mContext references a destroyed activity) ...
</pre>
在上面的示例中,泄漏的签名将计算为:
<pre id="__code_5" style="box-sizing: inherit; color: var(--md-code-fg-color); font-feature-settings: "kern"; font-family: "Roboto Mono", SFMono-Regular, Consolas, Menlo, monospace; direction: ltr; position: relative; margin: 1em 0px; line-height: 1.4;">val leakSignature = sha1Hash( "com.example.leakcanary.LeakingSingleton.leakedView" + "java.util.ArrayList.elementData" + "java.lang.Object[].[x]" ) println(leakSignature) // dbfa277d7e5624792e8b60bc950cd164190a11aa
</pre>
4.对泄漏进行分类
LeakCanary将在应用程序中发现的泄漏分为两类:应用程序泄漏和库泄漏。一个库泄漏是由3个已知的bug泄漏RD党的代码,你没有控制权。此泄漏正在影响您的应用程序,但是很遗憾,修复泄漏可能不受您的控制,因此LeakCanary会将其分离出来。
在Logcat中打印的结果中将这两类分开:
<pre id="__code_6" style="box-sizing: inherit; color: var(--md-code-fg-color); font-feature-settings: "kern"; font-family: "Roboto Mono", SFMono-Regular, Consolas, Menlo, monospace; direction: ltr; position: relative; margin: 1em 0px; line-height: 1.4;">`====================================
HEAP ANALYSIS RESULT
====================================
0 APPLICATION LEAKS
====================================
1 LIBRARY LEAK
...
┬───
│ GC Root: Local variable in native code
│
...` </pre>
LeakCanary 在其泄漏列表中将一行标记为库泄漏:

图10. LeakCanary发现了Library Leak。
LeakCanary附带了一个已知泄漏的数据库,它可以通过对引用名称进行模式匹配来识别。例如:
<pre id="__code_7" style="box-sizing: inherit; color: var(--md-code-fg-color); font-feature-settings: "kern"; font-family: "Roboto Mono", SFMono-Regular, Consolas, Menlo, monospace; direction: ltr; position: relative; margin: 1em 0px; line-height: 1.4;">Leak pattern: instance field android.app.Activity$1#this$0 Description: Android Q added a new IRequestFinishCallback$Stub class [...] ┬─── │ GC Root: Global variable in native code │ ├─ android.app.Activity$1 instance │ Leaking: UNKNOWN │ Anonymous subclass of android.app.IRequestFinishCallback$Stub │ ↓ Activity$1.this$0 │ ~~~~~~ ╰→ com.example.MainActivity instance
</pre>
我做了什么导致这种泄漏?
没有什么不对!您按预期的方式使用了API,但是实现中存在一个导致此泄漏的错误。
有什么我可以防止的吗?
也许!可以使用反射来修复某些库泄漏,而其他一些泄漏则可以通过执行使泄漏消失的代码路径来解决。这种类型的修复程序往往很容易破解,所以要当心!最好的选择是查找错误报告或提交错误报告,并坚持认为该错误已得到修复。
由于我不能对此泄漏做太多事情,有没有办法让LeakCanary忽略它?
LeakCanary无法在转储并分析堆之前知道泄漏是否是库泄漏。如果在找到“库泄漏”时LeakCanary没有显示结果通知,那么您将开始怀疑在倾倒烤面包后LeakCanary分析发生了什么。
您可以在AndroidReferenceMatchers类中查看已知泄漏的完整列表。如果发现无法识别的Android SDK泄漏,请报告。您还可以自定义已知库泄漏的列表。
网友评论