LZ-Says:此生入鸡门,此生无憾~ 感谢阳阳当年在廊坊将我挖出来,谢谢~
❈
前言
最近在群里看到有人在讨论有关内存分析的话题,比较好奇,Enmmm,也就有了今天这篇博文。
一起学习,一起进步吧~
一、LeakCanary 简介
LeakCanary:用于检测所有内存泄漏,适用于 Android 和 Java 的内存泄漏检测库。
为毛要叫做这个呢?
LeakCanary 这个名称是煤矿中金丝雀描述,因为 LeakCanary 类似一个用于通过提前预警危险来检测风险的哨兵。
1. 官方述说,为毛我们要使用 LeakCanary?
原文博客地址见文末,有兴趣可自行查阅,这里列举部分内容。
The First:
没有人喜欢OutOfMemoryError崩溃
在Square Register中,我们在 bitmaps 缓存上绘制客户的签名。此 bitmaps 是设备屏幕的大小,创建它时我们有大量的内存不足(OOM)导致崩溃。
我们尝试了几种方法,但都没有解决问题:
We were looking at it the wrong way
The bitmap size was not a problem. When the memory is almost full, an OOM can happen anywhere. It tends to happen more often in places where you create big objects, like bitmaps. The OOM is a symptom of a deeper problem: memory leaks.
bitmap 大小不是问题。当内存几乎已满时,OOM 可以在任何地方发生。它往往会在创建大对象(如 bitmap)的位置更频繁地发生。OOM 是一个更深层次问题的症状:内存泄漏。
什么是内存泄漏?
有些物体的寿命有限(在程序中,当某个对象已经使用完毕后,GC 则会对此进行回收)。当他们的工作完成后,他们将被当作垃圾回收。如果引用链在其预期生命周期结束后将对象保存在内存中,则会产生内存泄漏(也就是说,当 GC 回收时,由于某个对象依然具有将要回收值得引用,就会阻碍 GC 正常回收)。当这些泄漏累积时,应用程序则内存不足。
例如,在调用Activity.onDestroy()之后,Activity 其视图层次结构及其关联的位图应该都是可进行垃圾回收的。如果在后台运行的线程持有对活动的引用,则无法回收相应的内存。这最终导致 OutOfMemoryError ,以及最终的崩溃。
而我们又该如何收集内存泄漏?
收集并记录泄漏是一个手动过程,在Raizlabs的Wrangling Dalvik系列中有详细描述。
以下是关键步骤:
如果一个库可以在你进入OOM之前完成所有这些,并让你专注于修复内存泄漏怎么办?
这样岂不是让我们很爽么?
So,我们的 LeakCanary 应用而生了~
2. Enmmm,我怎么用它呢?
最简单的选择是调用 LeakCanary.install(this); ,它会安装一个 ActivityRefWatcher,从而自动检测 Activity 在 Activity.onDestroy() 被调用后是否泄漏。
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...
}
}
Enmmm,那假如我想监听:具有生命周期的对象,例如片段,服务,Dagger组件等怎么破?
很 Easy 啊,直接使用一个 RefWatcher 来监听应该进行垃圾回收的引用即可:
RefWatcher refWatcher = {...};
// We expect schrodingerCat to be gone soon (or not), let's watch it.
refWatcher.watch(schrodingerCat);
LeakCanary.install() 返回一个预先配置好的 RefWatcher,如下:
public class ExampleApplication extends Application {
public static RefWatcher getRefWatcher(Context context) {
ExampleApplication application = (ExampleApplication) context.getApplicationContext();
return application.refWatcher;
}
private RefWatcher refWatcher;
@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;
}
refWatcher = LeakCanary.install(this);
}
}
So,我们可以使用 RefWatcher 来监视 Fragment 泄漏:
public abstract class BaseFragment extends Fragment {
@Override public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
3. 它又是如何工作的?
4. 官方不好用,我要自定义
这里首先要注意:
使用 no-op 依赖
确保发布版本的 leakcanary-android-no-op 依赖项仅包含 LeakCanary 和 RefWatcher类。 如果开始自定义 LeakCanary,需要确保自定义仅在调试版本中发生,因为它可能会引用 leakcanary-android-no-op 依赖项中不存在的类异常。
假设发布版本在 AndroidManifest.xml 中声明了一个 ExampleApplication 类,并且调试版本声明了一个扩展 ExampleApplication 的 DebugExampleApplication,那么,在 Application 中,你应该进行如下操作:
public class ExampleApplication extends Application {
public static RefWatcher getRefWatcher(Context context) {
ExampleApplication application = (ExampleApplication) context.getApplicationContext();
return application.refWatcher;
}
private RefWatcher refWatcher;
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// 此过程专用于 LeakCanary 进行堆分析。
// 不应该在此过程中初始化应用。
return;
}
refWatcher = installLeakCanary();
}
protected RefWatcher installLeakCanary() {
return RefWatcher.DISABLED;
}
}
如果你想在 Debug 模式下操作,那么你应该注意如下:
public class DebugExampleApplication extends ExampleApplication {
@Override protected RefWatcher installLeakCanary() {
// 构建自定义的RefWatcher
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.watchDelay(10, TimeUnit.SECONDS)
.buildAndInstall();
return refWatcher;
}
}
这样,除了 leakcanary-android-no-op 依赖项中存在的两个空类之外,发布代码将不包含对 LeakCanary 的引用。
Step 1:我想修改图标和提示怎么办?
DisplayLeakActivity 附带一个默认图标和提示,可以通过提供 R.drawable.leak_canary_icon 和 R.string.leak_canary_display_activity_label 在应用中更改:
res/
drawable-hdpi/
leak_canary_icon.png
drawable-mdpi/
leak_canary_icon.png
drawable-xhdpi/
leak_canary_icon.png
drawable-xxhdpi/
leak_canary_icon.png
drawable-xxxhdpi/
leak_canary_icon.png
以及对应提示:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="leak_canary_display_activity_label">MyLeaks</string>
</resources>
Step 2:我想修改存储泄漏痕迹数量怎么办?
由于 LeakCanary 最多可以保存 7 个堆转储信息。So,如果改变这种情况,按照如下姿势即可:
public class DebugExampleApplication extends ExampleApplication {
protected RefWatcher installLeakCanary() {
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.maxStoredHeapDumps(42)
.buildAndInstall();
return refWatcher;
}
}
Step 3:我想修改将这些信息上传服务器怎么办?
创建自己的 AbstractAnalysisResultService。最简单的方法是在调试源中扩展 DisplayLeakService:
public class LeakUploadService extends DisplayLeakService {
@Override protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
if (!result.leakFound || result.excludedLeak) {
return;
}
myServer.uploadLeakBlocking(heapDump.heapDumpFile, leakInfo);
}
}
在调试应用程序类中构建自定义 RefWatcher:
public class DebugExampleApplication extends ExampleApplication {
@Override protected RefWatcher installLeakCanary() {
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.listenerServiceClass(LeakUploadService.class);
.buildAndInstall();
return refWatcher;
}
}
不要忘记在 AndroidManifest.xml 中注册该服务:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<application android:name="com.example.DebugExampleApplication">
<service android:name="com.example.LeakUploadService" />
</application>
</manifest>
Step 4:我想修改忽略已知内存泄漏的引用怎么办?
可以创建自己的 ExcludedRefs 版本,以忽略知道导致泄漏的特定引用,但我们仍然要进行如下设置:
public class DebugExampleApplication extends ExampleApplication {
@Override protected RefWatcher installLeakCanary() {
ExcludedRefs excludedRefs = AndroidExcludedRefs.createAppDefaults()
.instanceField("com.example.ExampleClass", "exampleField")
.build();
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.excludedRefs(excludedRefs)
.buildAndInstall();
return refWatcher;
}
}
Step 5:我不想看特定的 Activity 类怎么办?
默认情况下安装 ActivityRefWatcher 并监视所有活动。当然可以自定义安装步骤以使用不同的东西:
public class DebugExampleApplication extends ExampleApplication {
@Override protected RefWatcher installLeakCanary() {
LeakCanary.enableDisplayLeakActivity(this);
RefWatcher refWatcher = LeakCanary.refWatcher(this)
// Notice we call build() instead of buildAndInstall()
.build();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
public void onActivityDestroyed(Activity activity) {
if (activity instanceof ThirdPartyActivity) {
return;
}
refWatcher.watch(activity);
}
// ...
});
return refWatcher;
}
}
Step 6:我想在运行时打开和关闭 LeakCanary 怎么办?
自定义RefWatcher的创建方式,并为其提供有时候会执行 no-op 的 HeapDumper。
public class DebugExampleApplication extends ExampleApplication {
TogglableHeapDumper heapDumper;
@Override protected RefWatcher installLeakCanary() {
LeakDirectoryProvider leakDirectoryProvider = new DefaultLeakDirectoryProvider(context);
AndroidHeapDumper defaultDumper = new AndroidHeapDumper(context, leakDirectoryProvider);
heapDumper = new TogglableHeapDumper(defaultDumper);
RefWatcher refWatcher = LeakCanary.refWatcher(this)
.heapDumper(heapDumper)
.buildAndInstall();
return refWatcher;
}
public static class TogglableHeapDumper implements HeapDumper {
private final HeapDumper defaultDumper;
private boolean enabled = true;
public TogglableHeapDumper(HeapDumper defaultDumper) {
this.defaultDumper = defaultDumper;
}
public void toggle() {
enabled = !enabled;
}
@Override public File dumpHeap() {
return enabled? defaultDumper.dumpHeap() : HeapDumper.RETRY_LATER;
}
}
}
5. 如何挖掘泄漏痕迹?
有时泄漏跟踪是不够的,还需要使用 MAT 或 YourKit 挖掘堆转储。以下是在堆转储中找到泄漏实例的方法:
6. 如何在测试中禁用 LeakCanary?
要在单元测试中禁用 LeakCanary,请将以下内容添加到 build.gradle 即可:
// Ensure the no-op dependency is always used in JVM tests.
configurations.all { config ->
if (config.name.contains('UnitTest')) {
config.resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'com.squareup.leakcanary' && details.requested.name == 'leakcanary-android') {
details.useTarget(group: details.requested.group, name: 'leakcanary-android-no-op', version: details.requested.version)
}
}
}
}
7. 常见异常以及解决方案
并且,我们需要注意:
LeakCanary 只应在调试版本中使用,并应在发布版本中禁用。 因为,专门为发布版本提供了一个特殊的空依赖项:leakcanary-android-no-op。
LeakCanary的完整版本更大,绝不应在发布版本中发布使用。
8. 发现彩蛋
如果找到新的问题,请创建问题并按照以下步骤操作:
二、来波实战~
添加依赖项:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
在 Application 中添加 LeakCanary:
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...
}
}
现在运行一波你的项目。
首先查看我们桌面:
接着打开 Apk,正常运行,发现如下弹框提示:
Enmmm,一般通知栏也会有提示信息(此处需要注意,有些设备隐藏在不重要通知中,需要单独点开查看):
接下来打开 Leaks 这个小程序:
Enmmm,发生泄漏了,好尴尬。。。
点击查看详情,查看泄漏堆栈信息:
三、关于内存泄漏了怎么办?
如上例子,我们可以从内存泄漏堆栈中发现,最终的泄漏源发生在腾讯 IM 中,那么针对这些第三方 SDK 导致泄漏,我们又该如何操作呢?
下面 LZ 简单附上几条建议:
结束语
最后,感谢各位观看~!!!
如有不足之处,欢迎沟通~~~
我是贺利权,为自己代言~
欢迎各位老铁关注~不定期发布~见证你我的成长路~!!!
觉得不错,动动小手,转发让更多人看到,3Q,比心~