前言
上一篇我们讲了内存泄漏的情况和解决方案
(原创)内存泄漏分析解决方案(一):内存泄漏的几种情况解决方式
但是在实际开发中,我们是很难通过肉眼去直观定位内存泄漏问题的
这时候就需要用到一些工具
这次讲到的,就是一款名为LeakCanary的工具
什么是LeakCanary
LeakCanary是一个用来检测Android内存泄漏的框架,
作为一个实用工具,我们可以用它来定位和收集项目中存在的内存泄漏问题
LeakCanary更新到现在,目前的集成和使用已经比较简单了。
下面开始介绍它的使用方法。
LeakCanary使用
正式开始使用前,需要导入依赖,目前我使用的是2.6版本
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}
老版本的LeakCanary还需要在application里面一顿配置
但是新版本已经不需要了,直接依赖就完成啦!
导入依赖完成后,运行我们的项目,然后查看log台如下
1代表我们要过滤的是LeakCanary的日志信息
2代表我们要查找的标记字段Leaking
3代表LeakCanary现在开始运行了
运行开始后,我们去使用我们的项目
可以随意进入一些页面再退出
或者如果想专门查看某一个Activity是否有泄漏
就多次进入这个Activity再退出
过一会后,会弹出一个这样的toast
另外,如果没有主动弹出
我们也可以通过在我们模拟器桌面上多出的一个Leaks入口
进去入口后点击这个Dump Heap Now按钮,也可以立马执行内存分析
这时候,我们去查看我们的日志台
主要关注Leaking字段
Leaking有三个状态,代表不同的含义
Leaking:YES:发生了内存泄漏
Leaking:NO:未发生内存泄漏
Leaking:UNKNOW:未知,可能发生泄漏
这里我们的截图显示
在MainActivity2发生了一处内存泄漏
具体位置是在MyHandler
通过这个日志台,我们就可以清楚的知道内存泄漏的情况和具体未知了
另外,进入我们模拟器的桌面上的Leaks入口也可以看到这些日志信息
LeakCanary实战小例子
接下来,我们自己来写一个小例子,然后看LeakCanary能否检测到我们的内存泄漏
我们用java写一个单例模式
public class SingerDemo {
private Context context;
private static SingerDemo instance;
private SingerDemo(Context context) {
this.context = context;
}
public static SingerDemo getInstance(Context context) {
if (instance == null) {
instance = new SingerDemo(context);
}
return instance;
}
}
然后我们把MainActivity2作为参数传入进去
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
tv=findViewById(R.id.textView)
//传入activity对象
SingerDemo.getInstance(this)
}
因为单例持有了MainActivity2的引用,所以我们在MainActivity里多次进入MainActivity2然后退出
MainActivity2就会在内存中存在多个对象无法释放,从而发生了内存泄漏
我们用LeakCanary调试,log台得到如下结果
====================================
HEAP ANALYSIS RESULT
====================================
1 APPLICATION LEAKS
References underlined with "~~~" are likely causes.
Learn more at https://squ.re/leaks.
80373 bytes retained by leaking objects
Signature: 5021752607a5418ce492c6d6258a4a05358ce1
┬───
│ GC Root: Local variable in native code
│
├─ android.os.HandlerThread instance
│ Leaking: NO (PathClassLoader↓ is not leaking)
│ Thread name: 'LeakCanary-Heap-Dump'
│ ↓ Thread.contextClassLoader
├─ dalvik.system.PathClassLoader instance
│ Leaking: NO (SingerDemo↓ is not leaking and A ClassLoader is never leaking)
│ ↓ ClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ Leaking: NO (SingerDemo↓ is not leaking)
│ ↓ Object[].[667]
├─ com.example.memorytest.util.SingerDemo class
│ Leaking: NO (a class is never leaking)
│ ↓ static SingerDemo.instance
│ ~~~~~~~~
├─ com.example.memorytest.util.SingerDemo instance
│ Leaking: UNKNOWN
│ Retaining 80.4 kB in 1278 objects
│ context instance of com.example.memorytest.MainActivity2 with mDestroyed = true
│ ↓ SingerDemo.context
│ ~~~~~~~
╰→ com.example.memorytest.MainActivity2 instance
Leaking: YES (ObjectWatcher was watching this because com.example.memorytest.MainActivity2 received
Activity#onDestroy() callback and Activity#mDestroyed is true)
Retaining 80.4 kB in 1277 objects
key = 37b6654c-f318-41c1-b29f-63979a1fcc85
watchDurationMillis = 216967
retainedDurationMillis = 211965
mApplication instance of android.app.Application
mBase instance of android.app.ContextImpl
====================================
0 LIBRARY LEAKS
A Library Leak is a leak caused by a known bug in 3rd party code that you do not have control over.
See https://square.github.io/leakcanary/fundamentals-how-leakcanary-works/#4-categorizing-leaks
====================================
0 UNREACHABLE OBJECTS
我们找到关键的Leaking:YES
得到如下信息
果然定位到了由单例导致的内存泄漏
接下来我们只需要对我们的单例这样做修改
private SingerDemo(Context context) {
// this.context = context;
this.context = context.getApplicationContext();
}
再去查看内存检测结果
发现已经没有内存泄漏了
这样就解决了单例导致的内存泄漏问题
最后,关于LeakCanary的源码的解读和原理的分析
这里也有一篇博客作为参考,感兴趣的可以看下
【带着问题学】关于LeakCanary2.0你应该知道的知识点
LeakCanary介绍和使用就讲到这里了。