第三方-LeakCanary

LeakCanary

遵循以下四步去解决内存泄漏:

  1. 找到内存泄漏踪迹
  2. 缩小疑似的引用范围
  3. 找到导致泄漏的引用
  4. 修复泄漏

LeakCanary可以完成前两步

LeakCanary的使用

引入 module/build.gradle中添加以下内容即可

1
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

LeakCanary hookAndroid生命周期,从而实现了内存泄露的自动检测,当ActivityFragment被销毁需要被回收的时候,这些销毁的对象被传递给了ObjectWatcherObjectWatcher持有这些对象的弱引用,LeakCanary自动检测如下对象是否发生泄露.

  • 已经销毁的Activity实例
  • 已经销毁的Fragment实例
  • 已经销毁的小块View实例
  • 已经清空的ViewModel实例
  • 以├─开头的行代表java对象,
  • 以│ ↓ 开头的行代表指向下一行java对象的引用
1
2
3
4
5
6
7
8
9
10
┬───
│ 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

PathClassLoader 有一个runtimeInternalObjects属性,这是一个对象数组 这个数组的第43个引用指向了Utils 类 ╰→开头的行指向了泄露对象, 这个Utils有一个静态helper指向了一个泄露的对象。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ExampleApplication : Application() {
val leakedViews = mutableListOf<View>()
}

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)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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指向了一个已经destoryactivity

android app中,Application是一个不会被回收的单例,

找到造成此次泄露的引用

根据之前的分析,找到导致此次泄露的引用是因为Application持有了已经销毁的activity中的View