您现在的位置是:首页 > 正文

Android性能优化之-LeakCannary

2024-02-01 00:36:42阅读 2

LeakCanary使用

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

  • 检查哪些内存泄漏
  • 检查内存泄漏的时机
  • 如何判定内存泄漏
  • 如何分析内存泄漏(只有一点点,可能跟没有一样)
  • 内存泄漏误报

1.检查哪些内存泄漏

AddWatchers.png

AppWatcherInstaller继承于ContentProvider,调用时机是介于Application的attachBaseContext(Context)和 onCreate() 之间.通过这种方式初始化.

方法2manualInstall实现了默认参数watchersToInstall,通过这个方法我们看到Activity,FragmentAndViewModel,RootView,Service四个观察者

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  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实例

123.png

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)当监听到activity被创建时,获取该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实例

//AndroidXFragmentDestroyWatcher
override fun invoke(activity: Activity) {
  if (activity is FragmentActivity) {
    val supportFragmentManager = activity.supportFragmentManager
    supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
    ViewModelClearedWatcher.install(activity, reachabilityWatcher)
  }
}
复制代码

(2)监听当fragment被创建时,绑定一个间谍viewmodel实例

//AndroidXFragmentDestroyWatcher##fragmentLifecycleCallbacks
override 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)

//ViewModelClearedWatcher
override 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.

//RootViewWatcher
rootView.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.如何判定内存泄漏

234.png

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[key] = reference
  checkRetainedExecutor.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.如何分析内存泄漏

image.png

利用Shark分析工具分析hprof文件
(8)这里通过解析hprof文件生成heapAnalysis对象.SharkLog打印并存入数据库

override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
  SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }

  val db = LeaksDbHelper(application).writableDatabase
  val 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不释放是对的. 只不过这种实现不是内存最佳罢了.

<img src="https://hnxx.oss-cn-shanghai.aliyuncs.com/official/1704935888404.jpg?t=0.014764499839815759" style="margin: auto" />

网站文章

  • Linux 学习记录

    Linux 学习记录

    > 开口朝哪里,哪里的东西输出到另一面,只要有输出就能用管道灌过去 ,>>不覆盖原来内容,追加输入,|还未了解。:为了避免文件行数太多,部分展示,可以用回车一页一页的翻,可以按q中途退出,或者空格全部...

    2024-02-01 00:36:33
  • Python编程基础

    Python编程基础

    Python编程基础主要包括基本语法、内建数据结构、函数以及文件操作。一篇文章马上懂基础python使用、文件写入读出、csv文件处理,函数使用、列表全方位处理等操作。必会包会

    2024-02-01 00:36:06
  • git如何回滚到历史某个版本

    git如何回滚到历史某个版本

    问题:比如某员工误操作提交到了公司比较重要的develop分支,如何恢复到之前的某一个版本呢,git如何回滚到历史某个版本 方法一:idea里面git操作版本 1、 2、选中某一个版本,右键copy版本号 3、如何指针再指向本地的head版本号 4、提交到远程。push,会报错。应该加上-f 属性,推送ch成功。push的时候有一个小按钮 推送成功。 参考链接:https...

    2024-02-01 00:36:00
  • NOIP(CSP-J)信息学奥赛_普及组第十三课--嵌套循环-循环中的循环

    目录NOIP(CSP-J)信息学奥赛_普及组第十二课--嵌套循环-循环中的循环????先看一个案例二、 请写出如下程序的输出结果,深入理解嵌套循环三、嵌套循环的基本结构:四、嵌套循环图形题五、常见的数...

    2024-02-01 00:35:53
  • 蓝桥杯2019 第十届 c/c++ c组 编程大题 第四题 第2019个质数

    题目:结果填空,找质数,3是第一个质数 5是第二个质数,7是第三个质数,呢么第2019个质数是多少在绝望中水博客,这题我看成了2019以内的质数有多少个,估计也就我会看叉成这样吧,祝大家拿省一喽,心痛。代码:#include <iostream>#include <math.h>using namespace std;int main(){ int num...

    2024-02-01 00:35:16
  • 作业3F

    Problem F: 成绩的等级 Time Limit: 1 Sec Memory Limit: 2 MB Submit: 18628 Solved: 7766 [Submit][Status][We...

    2024-02-01 00:34:57
  • ios app拉起小程序

    ios app拉起小程序

    之前公司要求写一个sdk,里面封装好微信拉起小程序让集成方不用再集成具体实现:1、 创建类 WXApiManager2、 集成微信SDK 如图3、添加需要的库文件 如图4、 在WXApiManage...

    2024-02-01 00:34:50
  • 逆波兰计算器

    Java实现逆波兰计算器

    2024-02-01 00:34:20
  • asp.net mvc Controller Factory

    asp.net mvc Controller Factory

    此文摘要小妞之路,记录一下,方便自己查看学习Controller Factory控制器工厂:一般在实际项目中的用法:使用内置的Controller Factory,叫DefaultControllerFactory。当DefaultControllerFactory 类接收到一个 controller 实例的请求时,在DefaultControllerFactory 类...

    2024-02-01 00:34:13
  • 有1,2,....一直到n的无序数组,求排序算法,要求时间复杂度为O(n),空间复杂度O(1)

    1、有1,2,....一直到n的无序数组,求排序算法,并且要求时间复杂度为O(n),空间复杂度O(1),使用交换,而且一次只能交换两个数。 #include void sort(int *a,int len) { int temp; for(int i = 0; i &lt; len; ) { temp = a[a[i] - 1]; a[a[i] - 1] = a[i]; a[i]

    2024-02-01 00:34:06