硬核程序员 发表于 2021-9-17 21:15:16

LeakCanary源码分析

LeakCanary利用

LeakCanary是一个用于Android的内存泄漏检测库.本文从如下四点分析源码

[*]查抄哪些内存泄漏
[*]查抄内存泄漏的机遇
[*]怎样判定内存泄漏
[*]怎样分析内存泄漏(只有一点点,大概跟没有一样)
[*]内存泄漏误报
1.查抄哪些内存泄漏

https://p5.toutiaoimg.com/large/pgc-image/35c23cd4e42643ff828017a8bbf66bd8
AppWatcherInstaller继续于ContentProvider,调用机遇是介于Application的attachBaseContext(Context)和 onCreate() 之间.通过这种方式初始化.
方法2manualInstall实现了默认参数watchersToInstall,通过这个方法我们看到Activity,FragmentAndViewModel,RootView,Service四个观察者
fun appDefaultWatchers(application: Application,reachabilityWatcher: ReachabilityWatcher = objectWatcher): List {return listOf(    ActivityWatcher(application, reachabilityWatcher),    FragmentAndViewModelWatcher(application, reachabilityWatcher),    RootViewWatcher(reachabilityWatcher),    ServiceWatcher(reachabilityWatcher))}2.查抄内存泄漏的机遇

2.1 ActivityWatcher

activity触发OnDestory查抄是否采取Activity实例
private val lifecycleCallbacks =object : Application.ActivityLifecycleCallbacks by noOpDelegate() {    override fun onActivityDestroyed(activity: Activity) {      reachabilityWatcher.expectWeaklyReachable(      activity, "${activity::class.java.name} received Activity#onDestroy() callback"      )    }}2.2 FragmentAndViewModelWatcher

fragment触发onFragmentDestroyed或onFragmentViewDestroyed查抄是否可以采取Fragment实例
viewModel触发onClear查抄是否可以采取ViewModel实例
https://p9.toutiaoimg.com/large/pgc-image/93f0558f736e4d9b9175bd1bddf685d6
2.2.1 查抄哪些Fragment

由于Android如今有三种Fragment
androidx.fragment.app
android.app.fragment
android.support.v4.app.Fragment
leakCanary通过反射先去查抄是否引入上面三种Fragment,如果有就反射创建对应的watcher参加到 fragmentDestroyWatchers中
private fun getWatcherIfAvailable(fragmentClassName: String,watcherClassName: String,reachabilityWatcher: ReachabilityWatcher): ((Activity) -> Unit)? {return if (classAvailable(fragmentClassName) &&    classAvailable(watcherClassName)) {    val watcherConstructor =      Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)    @Suppress("UNCHECKED_CAST")    watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit} else {    null}}2.2.2 Fragment内存泄漏查抄机遇

(1)application注册activity生命周期回调
(2)当监听到ctivity被创建时,获取该activity的对应的fragmentManager创建fragment的生命周期观察者
(3)当onFragmentViewDestroyed/onFragmentDestroyed触发时,遍历集合然后查抄是否可以采取Fragment实例
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {override fun onFragmentViewDestroyed(    fm: FragmentManager,    fragment: Fragment) {    val view = fragment.view    if (view != null) {      reachabilityWatcher.expectWeaklyReachable(      view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +      "(references to its views should be cleared to prevent leaks)"      )    }}override fun onFragmentDestroyed(    fm: FragmentManager,    fragment: Fragment) {    reachabilityWatcher.expectWeaklyReachable(      fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"    )}}2.2.3 查抄哪些ViewModel内存泄漏

既然fragment/activity被销毁了,fragment/activity对象被采取了,那么fragment/activity绑定的全部viewmodel实例也应该销毁,所以leakCanary增加了viewmodel的内存查抄
(1)监听当activity被创建时,绑定一个间谍viewmodel实例
//AndroidXFragmentDestroyWatcheroverride fun invoke(activity: Activity) {if (activity is FragmentActivity) {    val supportFragmentManager = activity.supportFragmentManager    supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)    ViewModelClearedWatcher.install(activity, reachabilityWatcher)}}(2)监听当fragment被创建时,绑定一个间谍viewmodel实例
//AndroidXFragmentDestroyWatcher##fragmentLifecycleCallbacksoverride fun onFragmentCreated(fm: FragmentManager,fragment: Fragment,savedInstanceState: Bundle?) {ViewModelClearedWatcher.install(fragment, reachabilityWatcher)}2.2.4 ViewModel内存泄漏查抄机遇

(1)利用反射获得fragment/activity绑定的viewModel集合
(2)当leakcanary绑定的viewmodel生命周期走到onCleared时,就去查抄全部viewmodel实例是否可以采取(这边就是为啥作者取名叫spy)
//ViewModelClearedWatcheroverride fun onCleared() {viewModelMap?.values?.forEach { viewModel ->    reachabilityWatcher.expectWeaklyReachable(      viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"    )}}2.3 RootViewWatcher

view触发onViewDetachedFromWindow查抄是否采取View实例
利用Curtains获得视图变化,查抄全部被添加到phoneWindow上面的,windowLayoutParams.title为Toast大概是Tooltip,大概除PopupWindow之外的全部view.
//RootViewWatcherrootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {val watchDetachedView = Runnable {    reachabilityWatcher.expectWeaklyReachable(      rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"    )}override fun onViewAttachedToWindow(v: View) {    WindowManager.LayoutParams.TYPE_PHONE    mainHandler.removeCallbacks(watchDetachedView)}override fun onViewDetachedFromWindow(v: View) {    mainHandler.post(watchDetachedView)}})2.4 ServiceWatcher

service触发onDestroy查抄是否采取Service实例
private fun onServiceDestroyed(token: IBinder) {servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->    serviceWeakReference.get()?.let { service ->      reachabilityWatcher.expectWeaklyReachable(      service, "${service::class.java.name} received Service#onDestroy() callback"      )    }}}3.怎样判定内存泄漏

https://p3.toutiaoimg.com/large/pgc-image/63dabe850c64481c9526961cb9bbc667
ReferenceQueue : 引用队列,在检测到恰当的可到达性更改后,垃圾采取器将已注册的引用对象添加到该队列中
(1)将待查抄对象参加到weakReference和watchedObjects中
@Synchronized override fun expectWeaklyReachable(watchedObject: Any,description: String) {if (!isEnabled()) {    return}removeWeaklyReachableObjects()val key = UUID.randomUUID()    .toString()val watchUptimeMillis = clock.uptimeMillis()val reference =    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)SharkLog.d {    "Watching " +      (if (watchedObject is Class) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +      (if (description.isNotEmpty()) " ($description)" else "") +      " with key $key"}watchedObjects = referencecheckRetainedExecutor.execute {    moveToRetained(key)}}(6)实行GC后,遍历ReferenceQueue,删除watchedObjects集合中保存的对象
private fun removeWeaklyReachableObjects() {// WeakReferences are enqueued as soon as the object to which they point to becomes weakly// reachable. This is before finalization or garbage collection has actually happened.var ref: KeyedWeakReference?do {    ref = queue.poll() as KeyedWeakReference?    if (ref != null) {      watchedObjects.remove(ref.key)    }} while (ref != null)}(3)判断watchedObjects长度是否发生改变,如果改变就认为内存泄漏
private fun checkRetainedCount(retainedKeysCount: Int,retainedVisibleThreshold: Int,nopeReason: String? = null): Boolean {val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount...if (retainedKeysCount < retainedVisibleThreshold) {    if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {      if (countChanged) {      onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))      }      showRetainedCountNotification(      objectCount = retainedKeysCount,      contentText = application.getString(          R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold      )      )      scheduleRetainedObjectCheck(      delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS      )      return true    }}return false}(10) 当查抄到5次内存泄漏就会天生hprof文件
override fun dumpHeap(): DumpHeapResult {...val durationMillis = measureDurationMillis {Debug.dumpHprofData(heapDumpFile.absolutePath)}...}4.怎样分析内存泄漏

https://p26.toutiaoimg.com/large/pgc-image/6bd26103581f4a3d8639d553d5eb918e
利用Shark分析工具分析hprof文件
(8)这里通过解析hprof文件天生heapAnalysis对象.SharkLog打印并存入数据库
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }val db = LeaksDbHelper(application).writableDatabaseval id = HeapAnalysisTable.insert(db, heapAnalysis)db.releaseReference()...}5.内存泄漏误报

Java虚拟机的主流垃圾采取器采取的是可达性分析算法, 可达性算法是通过从GC root往外遍历,如果从root节点无法遍历该节点表明该节点对应的对象处于可采取状态. 反之不会采取.
public class MainActivity2 extends FragmentActivity {    Fragment mFragmentA;    Fragment mFragmentB;    @Override    protected void onCreate(Bundle savedInstanceState) {      super.onCreate(savedInstanceState);      setContentView(R.layout.activity_main2);      mFragmentA = new FragmentA();      mFragmentB = new FragmentB();      findViewById(R.id.buttona).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                replaceFragment(mFragmentA);            }      });      findViewById(R.id.buttonb).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                replaceFragment(mFragmentB);            }      });    }    private void replaceFragment(Fragment fragment) {      getSupportFragmentManager().beginTransaction()                .replace(R.id.container, fragment).commit();    }}以fragment为例,leakcanary认为fragment走onDestory了,就应该释放fragment.但是这种环境真的是内存泄漏么?
    ├─ com.example.MainActivity2 instance    │    Leaking: NO (Activity#mDestroyed is false)    │    ↓ MainActivity2.mFragmentA    │                  ~~~~~~~~~~    ╰→ com.example.FragmentA instance         Leaking: YES (ObjectWatcher was watching this because com.example.FragmentA         received Fragment#onDestroy() callback and Fragment#mFragmentManager is null)         key = 216c8cf8-2cdb-4509-84e9-8404afefffeb         watchDurationMillis = 3804         retainedDurationMillis = -1         key = eaa41c88-bccb-47ac-8fb7-46b27dec0356         watchDurationMillis = 6113         retainedDurationMillis = 1112         key = 77d5f271-382b-42ec-904b-1e8a6d4ab097         watchDurationMillis = 7423         retainedDurationMillis = 2423         key = 8d79952f-a300-4830-b513-62e40cda8bba         watchDurationMillis = 15771         retainedDurationMillis = 10765    13858 bytes retained by leaking objects    Signature: f1d17d3f6aa4713d4de15a4f465f97003aa7根据堆栈信息,leakcanary认为fragmentA走了onDestory应该要采取这个fragmentA对象,但是发现还被MainActivity2对象持有无法采取,然后判定是内存泄漏. 放在我们这个逻辑里面,fragment不释放是对的. 只不外这种实现不是内存最佳罢了.
作者|no_one|掘金
页: [1]
查看完整版本: LeakCanary源码分析