iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >详解LeakCanary分析内存泄露如何实现
  • 197
分享到

详解LeakCanary分析内存泄露如何实现

LeakCanary分析内存泄露LeakCanary 内存泄露 2022-12-08 20:12:14 197人浏览 安东尼
摘要

目录前言LeakCanary的使用LeakCanary原理源码浅析初始化使用总结前言 平时我们都有用到LeakCanary来分析内存泄露的情况,这里可以来看看LeakCanary是如

前言

平时我们都有用到LeakCanary来分析内存泄露的情况,这里可以来看看LeakCanary是如何实现的,它的内部又有哪些比较有意思的操作。

LeakCanary的使用

官方文档:square.GitHub.io/leakcanary/…

引用方式

dependencies {
    // debugImplementation because LeakCanary should only run in debug builds. 
    debugImplementation 'com.squareup.leakcanary:leakcanary-Android:2.10'
}

可以看到LeakCanary的新版本中依赖非常简单,甚至不需要你做什么就可以直接使用。

LeakCanary原理

LeakCanary的封装主要是利用ContentProvider,LeakCanary检测内存泄漏主要是监听Activity和Fragment、view的生命周期,配合弱引用和ReferenceQueue。

源码浅析

初始化

首先debugImplementation只是在Debug的包会依赖,在正式包不会把LeakCanary的内容打进包中。

LeakCanary的初始化是使用了ContentProvider,ContentProvider的onCreate会在Application的onCreate之前,它把ContentProvider写在自己的AndroidMainifest中,打包时会进行合并,所以这整个过程都不需要接入端做初始化操作。

<manifest xmlns:android="Http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.objectwatcher" >
    <uses-sdk android:minSdkVersion="14" />
    <application>
        <provider
            android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
            android:authorities="${applicationId}.leakcanary-installer"
            android:enabled="@bool/leak_canary_watcher_auto_install"
            android:exported="false" />
    </application>
</manifest>

这是它在AndroidManifest所定义的,打包的时候会合并所有的AndroidManifest

这就是它自动初始化的操作,也比较明显了,不用过多解释。

使用

先看看它要监测什么,因为LeakCanary 2.x的代码都是Kotlin写的,所以这里得分析kotlin,如果不熟悉kt的朋友,我只能说尽量讲慢一些,因为我想看旧版本的能不能用java来分析,但是简单看了下源码上是有一定的差别,所以还是要分析2.x。

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

从这里看到他主要分析Activity、Fragment和Fragment的View、RootView、Service。

看Activity的监听ActivityWatcher

监听Activity调用Destroy时会调用reachabilityWatcher的expectWeaklyReachable方法。
这里可以看看旧版本的做法(正好以前有记录)

private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
    new ActivityLifecycleCallbacksAdapter() {
      @Override public void onActivityDestroyed(Activity activity) {
        refWatcher.watch(activity);
      }
    };

旧版本是调用refWatcher的watch,虽然代码不同,但是思想一样,再看看旧版本的Fragment

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
    new FragmentManager.FragmentLifecycleCallbacks() {
      @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
        View view = fragment.getView();
        if (view != null) {
          refWatcher.watch(view);
        }
      }
      @Override
      public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
        refWatcher.watch(fragment);
      }
    };

这里监听了Fragment和Fragment的View,所以相比于新版本,旧版本只监听Activity、Fragment和Fragment的View

再回到新版本,分析完Activity的监听之后看看Fragment的

最终Destroy之后也是调用到reachabilityWatcher的expectWeaklyReachable。然后看看RootViewWatcher的操作

private val listener = OnRootViewAddedListener { rootView ->
  val trackDetached = when(rootView.windowType) {
    PHONE_WINDOW -> {
      when (rootView.phoneWindow?.callback?.wrappedCallback) {
        is Activity -> false
        is Dialog -> {
          ......
        }
        else -> true
      }
    }
    POPUP_WINDOW -> false
    TOOLTIP, TOAST, UNKNOWN -> true
  }
  if (trackDetached) {
    rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
      val watchDetachedView = Runnable {
        reachabilityWatcher.expectWeaklyReachable(
          rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
        )
      }
        ......
    })
  }
}

最终也是调用到reachabilityWatcher的expectWeaklyReachabl。最后再看看Service的。

这边因为只是做浅析,不是源码详细分析,所以我这边就不去一个个分析是如何调用到销毁的这个方法的,我们通过上面的方法得到一个结论,Activity、Fragment和Fragment的View、RootView、Service,他们几个,在销毁时都会调用到reachabilityWatcher的expectWeaklyReachabl。所以这些地方就是检测对象是否泄漏的入口。

然后我们来看看expectWeaklyReachable方法

@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any,
  description: String
) {
  // 先从queue中移除一次已回收对象
  removeWeaklyReachableObjects()
  // 生成随机数当成key
  val key = UUID.randomUUID().toString()
  val watchUptimeMillis = clock.uptimeMillis()
  // 创建弱引用关联ReferenceQueue
  val reference =
    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
  ......
  // 把reference和key 添加到一个Map中
  watchedObjects[key] = reference
  // 下一步
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}

你们运气真好,我正好以前也有记录旧版本的refWatcher的watch方法

public void watch(Object watchedReference, String referenceName) {
        ......
        // 生成随机数当成key
        String key = UUID.randomUUID().toString();
        // 把key 添加到一个Set中
        this.retainedKeys.add(key);
        // 创建弱引用关联ReferenceQueue
        KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this.queue);
        // 下一步
        this.ensureGoneAsync(watchStartNanoTime, reference);
}

通过对比发现,模板的流程是一样的,但是细节不一样,以前是用Set,现在是用Map,这就是我觉得不能拿旧版本代码来分析的原因。

文章写到这里,突然想到一个很有意思的东西,你要是面试时,面试官看过新版本的代码,你看的是旧版本的代码,结果如果问到一些比较深入的细节,你答出来的和他所理解的不同,那就尴尬了,所以面试时得先说清楚你是看过旧版本的代码

看到用一个弱引用生成一个key和对象绑定起来。然后调用ensureGoneAsync方法

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    this.watchExecutor.execute(new Retryable() {
        public Result run() {
            return RefWatcher.this.ensureGone(reference, watchStartNanoTime);
        }
    });
}

execute里面会调用到waitForIdle方法。

我们再回到新版本的代码中

checkRetainedExecutor.execute其实是会执行到这里(kt里面的是写得简单,但是不熟的话可以先别管怎么执行的,只要先知道反正执行到这个地方就行)

这里是做了一个延时发送消息的操作,延时5秒,具体代码在这里

写到这里我感觉有点慌了,因为如果不熟kt的朋友可能真会看困,其实如果看不懂这个代码的话没关系,只要我圈出来的地方,我觉是大概能看懂的,然后流程我会说,我的意思是没必要深入去看每一行是什么意思,我们的目的是找出大概的流程(用游戏的说法,我们是走主线任务,不是要全收集)

延迟5秒后会调回到前面的moveToRetained(key)。那不好意思各位,我又要拿旧版本来对比了,因为细节不同。

private void waitForIdle(final Retryable retryable, final int failedAttempts) {
  // 使用IdleHandler来实现在闲时才去执行后面的流程
  Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override public boolean queueIdle() {
      postToBackgroundWithDelay(retryable, failedAttempts);
      return false;
    }
  });
}

使用IdleHandler来完成闲时触发,我不记得很早之前的版本是不是也用的IdleHandler,这里使用IdleHandler只能说有好有坏吧,好处是闲时触发确实是一个很好的操作,不好的地方是如果一直有异步消息,就一直不会触发后面的流程。

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
  long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
  long delayMillis = initialDelayMillis * exponentialBackoffFactor;
  // 根据上下文去计算,这里是5秒
  backgroundHandler.postDelayed(new Runnable() {
    @Override public void run() {
      Retryable.Result result = retryable.run();
      if (result == RETRY) {
        postWaitForIdle(retryable, failedAttempts + 1);
      }
    }
  }, delayMillis);
}

看到旧版本是先用IdelHanlder,在闲时触发的情况下再去延时5秒,而新版本是直接延时5秒,不使用IdelHandler,我没看过这块具体的文档描述,我猜是为了防止饿死,如果用IdelHanlder的话可能会出现一直不触发的情况。

返回看新版本的moveToRetained

@Synchronized private fun moveToRetained(key: String) {
  // 从ReferenceQueue中拿出对象移除
  removeWeaklyReachableObjects()
  // 经过上一步之后判断Map中还有没有这个key,有的话进入下一步操作
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) {
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}
private fun removeWeaklyReachableObjects() {
  // 从ReferenceQueue中拿出对象,然后从Map中移除
  var ref: KeyedWeakReference?
  do {
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}

moveToRetained主要是从ReferenceQueue中找出弱引用对象,然后移除Map中相应的弱引用对象。弱引用+ReferenceQueue的使用,应该不用多说吧,如果弱引用持有的对象被回收,弱引用会添加到ReferenceQueue中。所以watchedObjects代表的是应该将要被回收的对象,queue表示已经被回收的对象,这步操作就是从queue中找出已经回收的对象,然后从watchedObjects移除相应的对象,剩下的的就是应该被回收却没被回收的对象。如果对象被正常回收,那这整个流程就走完了,如果没被回收,会执行到onObjectRetained(),之后就是Dump操作了,之后的就是内存分析、弹出通知那堆操作了,去分析内存的泄漏这些,因为内容比较多,这篇先大概就先到这里。

总结

浅析,就是只做了简单分析LeakCanary的整个工作过程和工作原理。

原理就是用弱引用和ReferenceQueue去判断应该被回收的对象是否已经被回收。大致的工作流程是:监听Activity、Fragment和Fragment的View、RootView、Service对象的销毁,然后将这些对象放入“应该被回收”的容器中,然后5秒后通过弱引用和ReferenceQueue去判断对象是否已被回收,如果被回收则从容器中删除对应的对象,否则进行内存分析。

至于是如何判断不同对象的销毁和如何分析内存情况找出泄漏的引用链,这其中也是细节满满,但是我个人LeakCanary应该是看过两三次源码了,从一开始手动初始化,到旧版本java的实现方式,到现在用kt去实现,能发现它的核心思想其实是一样的,只不过在不断的优化一些细节和不断的扩展可以监测的对象。

以上就是详解LeakCanary分析内存泄露如何实现的详细内容,更多关于LeakCanary分析内存泄露的资料请关注编程网其它相关文章!

--结束END--

本文标题: 详解LeakCanary分析内存泄露如何实现

本文链接: https://www.lsjlt.com/news/174407.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 详解LeakCanary分析内存泄露如何实现
    目录前言LeakCanary的使用LeakCanary原理源码浅析初始化使用总结前言 平时我们都有用到LeakCanary来分析内存泄露的情况,这里可以来看看LeakCanary是如...
    99+
    2022-12-08
    LeakCanary分析内存泄露 LeakCanary 内存泄露
  • vue:内存泄露详解
    什么是内存泄露?内存泄露是指new了一块内存,但无法被释放或者被垃圾回收。new了一个对象之后 ,它申请占用了一块堆内存,当把这个对象指针置为null时或者离开作用域导致被销毁,...
    99+
    2024-04-02
  • Java内存泄露问题实例分析
    本篇内容介绍了“Java内存泄露问题实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Java内存泄露问题所谓内存泄露就是指一个不再被程...
    99+
    2023-06-29
  • C#内存泄露问题分析
    这篇文章主要介绍“C#内存泄露问题分析”,在日常操作中,相信很多人在C#内存泄露问题分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C#内存泄露问题分析”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!今天...
    99+
    2023-06-17
  • win11内存泄露如何解决
    这篇文章主要介绍“win11内存泄露如何解决”,在日常操作中,相信很多人在win11内存泄露如何解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”win11内存泄露如何解决”的疑惑有所帮助!接下来,请跟着小编...
    99+
    2023-06-30
  • Java 内存泄露问题详解
    目录 1、什么是内存泄露? 2、Java 中可能导致内存泄露的场景 3、长生命周期对象持有短生命周期对象引用造成的内存泄露问题示例 4、静态集合类持有对象引用造成内存泄露问题的示例 1、什么是内存泄露?         内存泄露指的是程...
    99+
    2023-09-08
    Java 内存泄露
  • Python实现内存泄露排查的示例详解
    一般情况下只有需要长期运行的项目才会去关注内存的增长情况,即使是很小部分的内存泄露经过长期的运行仍然会产生很大的隐患。 python本身也是支持垃圾的自动回收的,但是在特定的情况下也...
    99+
    2023-01-28
    Python内存泄露排查 Python内存泄露 Python内存
  • ehcache内存泄露如何解决
    解决Ehcache内存泄漏的问题可以尝试以下几个方法:1. 升级Ehcache版本:确保使用的是最新的Ehcache版本,因为较新的...
    99+
    2023-09-13
    ehcache
  • Java 中出现内存泄露如何解决
    这期内容当中小编将会给大家带来有关Java 中出现内存泄露如何解决,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。首先,我用下面的命令监视进程:while ( sleep 1&...
    99+
    2023-06-17
  • 如何定位内存泄露
    这篇文章主要讲解了“如何定位内存泄露”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何定位内存泄露”吧!生产-消费者模式简介上一节中我们尝试了多种多线程方案,总会有各种各样奇怪的问题。于是最...
    99+
    2023-06-15
  • Flex中出现内存泄露如何解决
    Flex中出现内存泄露如何解决,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Flex内存泄露举例:引用泄露:对子对象的引用,外部对本对象或子对象的引用都需要置null;系统类泄...
    99+
    2023-06-17
  • 怎么实现Samba UAF和内存泄露漏洞的分析
    怎么实现Samba UAF和内存泄露漏洞的分析,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。0x00 事件描述       &...
    99+
    2023-06-19
  • Java内存溢出和内存泄露的示例分析
    这篇文章给大家分享的是有关Java内存溢出和内存泄露的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、为什么要了解内存泄露和内存溢出?内存泄露一般是代码设计存在缺陷导致的,通过了解内存泄露的场景,可以避...
    99+
    2023-05-30
    java
  • 内存泄漏检测工具LeakCanary源码解析
    目录前言使用源码解析LeakCanary自动初始化如何关闭自动初始化LeakCanary初始化做了什么ActivityWatcherFragmentAndViewModelWatch...
    99+
    2023-01-28
    内存泄漏检测LeakCanary 内存泄漏LeakCanary
  • 分析Android常见的内存泄露和解决方案
    目录一、前言二、Android 内存泄露场景2.1、非静态内部类的静态实例2.2、多线程相关的匿名内部类/非静态内部类2.3、Handler 内存泄露2.4、静态 Activity ...
    99+
    2024-04-02
  • php内存泄露如何排查
    要排查PHP内存泄露问题,可以采取以下几个步骤:1. 使用垃圾回收机制:PHP的垃圾回收机制会自动释放不再使用的内存,可以通过在代码...
    99+
    2023-09-26
    php
  • java内存管理关系及内存泄露的原理分析
    目录java内存管理关系及内存泄露原理java对象和内存的关系创建对象null的作用内存泄露检测内存泄露的原理java内存管理关系及内存泄露原理 这可能是最近写的博客中最接近底层的了...
    99+
    2024-04-02
  • C++中如何检查内存泄露
    这篇文章给大家介绍C++中如何检查内存泄露,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、前言在Linux平台上 有valgrind可以非常方便的帮助我们定位内存泄漏,因为Linux在开发领域的使用场景大多是跑服务器...
    99+
    2023-06-17
  • Flex内存优化原则和内存泄露的示例分析
    这篇文章将为大家详细讲解有关Flex内存优化原则和内存泄露的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Flex性能优化常用手法众所周知,目前国内的宽带应用并不是像很多发达国家发达,个人应用带宽...
    99+
    2023-06-17
  • 详解Android内存泄露及优化方案
    目录一、常见的内存泄露应用场景?1、单例的不恰当使用 2、静态变量导致内存泄露 3、非静态内部类导致内存泄露 4、未取消注册或回调导致内存泄露 5、定时器Timer 和 TimerT...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作