文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android 进阶实现性能优化之OOM与Leakcanary详解原理

2024-04-02 19:55

关注

本文主要探讨以下几个问题:

Android内存泄漏常见场景以及解决方案

资源性对象未关闭

对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。

注册对象未注销

例如BraodcastReceiver、EventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。

类的静态变量持有大数据

对象尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。

单例造成的内存泄漏

优先使用Application的Context,如需使用Activity的Context,可以在传入Context时使用弱引用进行封装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。

非静态内部类的静态实例

该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置空让GC可以回收,否则还是会内存泄漏。

Handler临时性内存泄漏

Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引用,Message在Queue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息,当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏。解决方案如下所示:
1. 使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这样在回收时,也可以回收Handler持有的对象。
2. 在Activity的Destroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中有待处理的消息需要处理。需要注意的是,AsyncTask内部也是Handler机制,同样存在内存泄漏风险,但其一般是临时性的。对于类似AsyncTask或是线程造成的内存泄漏,我们也可以将AsyncTask和Runnable类独立出来或者使用静态内部类。

容器中的对象没清理造成的内存泄漏

在退出程序之前,将集合里的东西clear,然后置为null,再退出程序

WebView

WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

使用ListView时造成的内存泄漏

在构造Adapter时,使用缓存的convertView。

Leakcanary

leakcanary 导入


//  leakcanary 添加支持库即可,只在debug下使用
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'

leakcanary 是如何安装的

leakcanary 不需要初始化,用的是 ContentProvider!

ContentProvider.onCreate 方法比 Application.onCreate 更早执行。LeakCanary 源码的 Manifest.xml 里有声明ContentProvider,apk打包流程中会把所有的Manifest合并到app 的 Manifest 里,即APP就有了ContentProvider。


//	package="com.squareup.leakcanary.leaksentry"
  <application>
    <provider
        android:name="leakcanary.internal.LeakSentryInstaller"
        android:authorities="${applicationId}.leak-sentry-installer"
        android:exported="false"/>
  </application>

下面是初始化的代码


internal class LeakSentryInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    CanaryLog.logger = DefaultCanaryLog()
    val application = context!!.applicationContext as Application
     // 进行初始化工作,核心
    InternalLeakSentry.install(application)
    return true
  }

监听实现


  fun install(application: Application) {
    CanaryLog.d("Installing LeakSentry")
    // 只能在主线程调用,否则会抛出异常
    checkMainThread()
    if (this::application.isInitialized) {
      return
    }
    InternalLeakSentry.application = application

    val configProvider = { LeakSentry.config }
    // 监听 Activity.onDestroy()
    ActivityDestroyWatcher.install(
        application, refWatcher, configProvider
    )
    // 监听 Fragment.onDestroy()
    FragmentDestroyWatcher.install(
        application, refWatcher, configProvider
    )
    // Sentry 哨兵
    listener.onLeakSentryInstalled(application)
  }

leakcanary 如何监听Activity、Fragment销毁

在了解监听过程前有必要了解下 ActivityLifecycleCallbacks 与 FragmentLifeCycleCallbacks


// ActivityLifecycleCallbacks 接口
public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity var1, Bundle var2);

    void onActivityStarted(Activity var1);

    void onActivityResumed(Activity var1);

    void onActivityPaused(Activity var1);

    void onActivityStopped(Activity var1);

    void onActivitySaveInstanceState(Activity var1, Bundle var2);

    void onActivityDestroyed(Activity var1);
}
// FragmentLifecycleCallbacks 接口
public abstract static class FragmentLifecycleCallbacks {

    public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {}

    public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {}

    public void onFragmentDestroyed(FragmentManager fm, Fragment f) {}

    // 省略其他的生命周期 ...
  }

Application 类提供了 registerActivityLifecycleCallbacks 和 unregisterActivityLifecycleCallbacks 方法用于注册和反注册 Activity 的生命周期监听类,这样我们就能在 Application 中对所有的 Activity 生命周期回调中做一些统一处理。同理,FragmentManager 类提供了 registerFragmentLifecycleCallbacks 和 unregisterFragmentLifecycleCallbacks 方法用户注册和反注册 Fragment 的生命周期监听类,这样我们对每一个 Activity 进行注册,就能获取所有的 Fragment 生命周期回调。

下面是 ActivityDestroyWatcher 的实现,refWatcher 监听 activity 的 onActivityDestroyed


internal class ActivityDestroyWatcher private constructor(
  private val refWatcher: RefWatcher,
  private val configProvider: () -> Config
) {

  private val lifecycleCallbacks = object : ActivityLifecycleCallbacksAdapter() {
    override fun onActivityDestroyed(activity: Activity) {
      if (configProvider().watchActivities) {
        // 监听到 onDestroy() 之后,通过 refWatcher 监测 Activity
        refWatcher.watch(activity)
      }
    }
  }

  companion object {
    fun install(
      application: Application,
      refWatcher: RefWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(refWatcher, configProvider)
      // 注册 Activity 生命周期监听
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

如此一来Activity、Fragment在调用onDestroy时我们都能知道。讲道理,如果在调用onDestroy时被GC是正常的,如果没有被回收则是发生了内存泄漏,这是我们要处理的。那 refWatcher.watch(activity) 监听到销毁后怎么处理?

RefWatcher 核心原理

在读这块代码前举个栗子比较好理解:比如我们去科技中心面试

RefWatcher 的实现原理跟上面的栗子神似:

咱们先看下 refWatcher.watch(activity) 的实现


  @Synchronized fun watch(
    watchedReference: Any,
    referenceName: String
  ) {
    if (!isEnabled()) {
      return
    }
    // 移除队列中将要被 GC 的引用
    removeWeaklyReachableReferences() 
    val key = UUID.randomUUID().toString()
    val watchUptimeMillis = clock.uptimeMillis()
    // 构建当前引用的弱引用对象,并关联引用队列 queue
    val reference = KeyedWeakReference(watchedReference, key, referenceName, watchUptimeMillis, queue)
    if (referenceName != "") {
      CanaryLog.d(
          "Watching instance of %s named %s with key %s", reference.className,
          referenceName, key
      )
    } else {
      CanaryLog.d(
          "Watching instance of %s with key %s", reference.className, key
      )
    }
    // 将引用存入 watchedReferences
    watchedReferences[key] = reference 
    checkRetainedExecutor.execute {
      // 如果当前引用未被移除,仍在 watchedReferences  队列中,
      // 说明仍未被 GC,移入 retainedReferences 队列中,暂时标记为泄露
      moveToRetained(key) 
    }
  }

分析上面这段代码都做了什么:

下面是细节分析 —》removeWeaklyReachableReferences() 逻辑


  private fun removeWeaklyReachableReferences() {
    // 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.
    // 弱引用一旦变得弱可达,就会立即入队。这将在 finalization 或者 GC 之前发生。
    var ref: KeyedWeakReference?
    do {
      // 队列 queue 中的对象都是会被 GC 的
      ref = queue.poll() as KeyedWeakReference? 
      //说明被释放了
      if (ref != null) {
        val removedRef = watchedReferences.remove(ref.key)//获取被释放的引用的key
        if (removedRef == null) {
          retainedReferences.remove(ref.key)
        }
        // 移除 watchedReferences 队列中的会被 GC 的 ref 对象,剩下的就是可能泄露的对象
      }
    } while (ref != null)
  }

removeWeaklyReachableReferences 函数会根据 ReferenceQueue 出来的 KeyedWeakReference 的 key 移除 watchedReferences(观察队列)和 retainedReferences(怀疑队列)中的引用,即把已经释放的移出,剩下的是内存泄漏的

moveToRetained(key) 逻辑实现


  @Synchronized private fun moveToRetained(key: String) {
    // 再次调用,防止遗漏
    removeWeaklyReachableReferences() 
    val retainedRef = watchedReferences.remove(key)
    //说明可能存在内存泄漏
    if (retainedRef != null) {
      retainedReferences[key] = retainedRef
      onReferenceRetained()
    }
  }

此函数的作用:

下面是 checkRetainedInstances 的具体实现


  private fun checkRetainedInstances(reason: String) {
    CanaryLog.d("Checking retained instances because %s", reason)
    val config = configProvider()
    // A tick will be rescheduled when this is turned back on.
    if (!config.dumpHeap) {
      return
    }

    var retainedKeys = refWatcher.retainedKeys

    // 当前泄露实例个数小于 5 个,不进行 heap dump
    if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return

    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      showRetainedCountWithDebuggerAttached(retainedKeys.size)
      scheduleRetainedInstanceCheck("debugger was attached", WAIT_FOR_DEBUG_MILLIS)
      CanaryLog.d(
          "Not checking for leaks while the debugger is attached, will retry in %d ms",
          WAIT_FOR_DEBUG_MILLIS
      )
      return
    }

    // 可能存在被观察的引用将要变得弱可达,但是还未入队引用队列。
    // 这时候应该主动调用一次 GC,可能可以避免一次 heap dump
    gcTrigger.runGc()

    retainedKeys = refWatcher.retainedKeys

    if (checkRetainedCount(retainedKeys, config.retainedVisibleThreshold)) return

    HeapDumpMemoryStore.setRetainedKeysForHeapDump(retainedKeys)

    CanaryLog.d("Found %d retained references, dumping the heap", retainedKeys.size)
    HeapDumpMemoryStore.heapDumpUptimeMillis = SystemClock.uptimeMillis()
    dismissNotification()
    val heapDumpFile = heapDumper.dumpHeap() // AndroidHeapDumper
    if (heapDumpFile == null) {
      CanaryLog.d("Failed to dump heap, will retry in %d ms", WAIT_AFTER_DUMP_FAILED_MILLIS)
      scheduleRetainedInstanceCheck("failed to dump heap", WAIT_AFTER_DUMP_FAILED_MILLIS)
      showRetainedCountWithHeapDumpFailed(retainedKeys.size)
      return
    }

    refWatcher.removeRetainedKeys(retainedKeys) // 移除已经 heap dump 的 retainedKeys

    HeapAnalyzerService.runAnalysis(application, heapDumpFile) // 分析 heap dump 文件
  }

流程图

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

到此这篇关于Android 进阶实现性能优化之OOM与Leakcanary详解原理的文章就介绍到这了,更多相关Android 性能优化内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯