LeakCanary介绍:
leakCanary是一个android内存泄漏检测工具,帮助开发者区解决OOM带来的Crash。
github地址:github.com/square/leak…
LeakCanary接入:
在module的build.gradle中添加一行
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
没错,只需要添加这一行就完事了,其他的初始化操作会在LeakCanary库中自动进行。
Leak Canary的使用:
leakCanary启动起来后在Logcat里会有一行日志
D LeakCanary: LeakCanary is running and ready to detect leaks
一旦LeakCanary被安装,他会通过如下四个步骤自动检测和上报内存泄漏。
- 检测保留的对象,
- dump堆栈
- 分析堆栈
- 将泄漏分类
1.检测保留的对象
LeakCanary hook了Android生命周期,从而实现了内存泄露的自动检测,当Activity和Fragment被销毁需要被回收的时候,这些销毁的对象被传递给了ObjectWatcher,
ObjectWatcher持有这些对象的弱引用,LeakCanary自动检测如下对象是否发生泄露.
- (1).已经销毁的Activity实例
- (2).已经销毁的Fragment实例
- (3).已经销毁的小块View实例
- (4).已经清空的ViewModel实例
2.dump这个堆
当retained的对象到达临界值,LeakCanary dumps这个java堆到一个.hprof文件中,Dump 堆的时候会短暂冻结app,这个时候会展示一个Toast,
3.分析这个堆
LeakCanary解析这个.hprof 文件通过Shark,在堆中定位retained堆对象。
4.将泄露分类
当分析完毕的时候,LeakCanary展示一个概览通知,leakCanary给每个泄漏创建了一个签名,同一组的泄漏有同样的签名,他们的泄漏是由同样的问题造成的,被归为同一类。
修复内存泄漏:
遵循以下四步去解决内存泄漏:
- 找到内存泄漏踪迹
- 缩小疑似的引用范围
- 找到导致泄漏的引用
- 修复泄漏
LeakCanary可以帮助你完成前两步,后两步得靠自己。
1.寻找泄漏踪迹
内存泄漏路径是一个从垃圾回收根节点,到retained的对象的引用路径。这个引用在内存中持有一个对象,因此阻止了垃圾回收器回收它。
例如,让我们来保存一个healper的单例在一个静态变量中。
然后告诉LeakCanary 这个单例被期望被回收,当然实际上是无法被回收的,所以就伪造出了一个内存泄露。
AppWatcher.objectWatcher.watch(Utils.helper)
`class Helper {
}
class Utils {
public static Helper helper = new Helper();
}`
这个单例的泄露路径像这样
┬───
│ GC Root: Local variable in native code
│
├─ dalvik.system.PathClassLoader instance
│ ↓ PathClassLoader.runtimeInternalObjects
├─ java.lang.Object[] array
│ ↓ Object[].[43]
├─ com.example.Utils class
│ ↓ static Utils.helper
╰→ java.example.Helper
让我们来分析一下它,在顶部,OathClassLoader实例被GC Root持有,GCroot是个总是可达的特殊对象,它们不能被回收,这里有四种主要的GC Root
- 线程栈中的本地变量
- 活跃的线程对象
- 不能被卸载的系统类
- 本地代码引用
以├─开头的行代表java对象,以│ ↓ 开头的行代表指向下一行java对象的引用。
PathClassLoader 有一个runtimeInternalObjects属性,这是一个对象数组
这个数组的第43个引用指向了Utils 类
╰→开头的行指向了泄露对象,
这个Utils有一个静态helper指向了一个泄露的对象。
2.缩小可疑引用范围
泄露踪迹就是一个引用路径,路径中的多有路径都被怀疑引起了这个泄露LeakCanary可以自动缩小可以范围,从而能让我们快速找到导致内存泄露的原因。
有下面一段代码
`class ExampleApplication : Application() {
val leakedViews = mutableListOf()
}
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
val textView = findViewById<View>(R.id.helper_text)
val app = application as ExampleApplication
// This creates a leak, What a Terrible Failure!
app.leakedViews.add(textView)
复制代码
}
}`
LeakCanary提供了如下的泄露路径
┬───
│ GC Root: System class
│
├─ android.provider.FontsContract class
│ ↓ static FontsContract.sContext
├─ com.example.leakcanary.ExampleApplication instance
│ ↓ ExampleApplication.leakedViews
├─ java.util.ArrayList instance
│ ↓ ArrayList.elementData
├─ java.lang.Object[] array
│ ↓ Object[].[0]
├─ android.widget.TextView instance
│ ↓ TextView.mContext
╰→ com.example.leakcanary.MainActivity instance
来分析这个路径:
这个FontsContract类是一个系统类,拥有一个sContext静态属性,指向了ExampleApplication对象,这个对象里有一个leakedViews指向了一个ArrayList,里面有个对象引用TextView,里面持有一个Context指向了一个已经destory的activity
在android app中,Application是一个不会被回收的单例,
3.找到造成此次泄露的引用
根据之前的分析,找到导致此次泄露的引用是因为Application持有了已经销毁的activity中的View
4.解决内存泄露
根据实际情况酌情修改。