首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >笔记13 - Android中的内存泄漏如何优化

笔记13 - Android中的内存泄漏如何优化

作者头像
码农帮派
发布2021-01-28 10:07:53
1.3K0
发布2021-01-28 10:07:53
举报
文章被收录于专栏:码农帮派码农帮派

Activity内存泄漏的预防

Activity承载了应用和用户交互的任务,在Activity中有大量的资源引用和上下文Context这样占用内存较大的资源对象,因为Activity一旦因为外部变量的持有,就会造成比较严重的内存泄漏。造成Activity内存泄漏的场景有以下:

1. 将Context或者View设置为static

View会默认持有一个Context的引用,如果将View设置为static会导致View在方法区无法被快速回收,从而造成Activity的内存泄漏:

上面代码中,由于imageView被设置为static,会导致ActivityB无法被回收。

2. 未解注册的各种Listener

我们在Activity中会注册各种系统监听器,比如广播:

当我们退出ActivityC,系统Destroy Activity的时候,会提示有内存泄漏:

3. 非静态Handler导致Activity泄漏

上面代码中的Handler会在一定情况下造成Activity的内存泄漏,我们知道Handler的执行需要借助于Looper和MessageQueue,当我们退出Activity,而MessageQueue中还有未被执行的任务,此时退出Activity,GC并不会立即回收Handler,而Handler持有外部Activity的引用,这样就会导致Activity无法被回收,还表现为该Activity的onDestroy方法未被执行。

所以在Activity中的Handler一般我们需要将其设置为static,然后在Handler内部持有一个Activity的弱引用,以此来避免内存泄漏。

4. 第三方库使用Context

我们项目中使用的第三方库的初始化有时候需要依赖Context对象,而且初始化的Context可能一直被持有,假如我们在初始化的时候传入了Activity的Context,就会导致Activity在退出之后无法被回收造成内存泄漏。为了防止这种情况,我们应该使用Application的Context进行初始化。

另外要是我们自己开发第三方库,在初始化前也应该开发者传入的Context进行判断,获取ApplicationContext:

Context appContext = context.getApplicationContext();

这样即使开发者传入了Activity,也可以通过该Activity获取到ApplicationContext,避免出现Activity泄漏的情况。

内存泄漏的检查

LeakCanary是Square公司提供的,可以检测App运行过程中内存泄漏的工具,当内存发生泄漏的时候,LeakCanary会生成内存泄漏对象的引用链,并可以通知到开发人员。

如何检测内存泄漏

Java中的WeakReference是弱引用类型,每次GC的时候,弱引用持有的对象如果没有被强引用持有,那么GC会回收它所持有的对象:

上述代码中构建了一个BigObject对象,但这个对象并没有被强引用持有,在System.gc()之后,该对象就会被回收。

before gc, reference.get is com.xxxxx.WeakRefDemo$BigObject@7852e922
after gc, reference.get is null

WeakReference的构造函数允许我们传入一个ReferenceQueue,当WeakReference持有的对象被GC回收的时候,会将WeakReference放入到这个ReferenceQueue中:

运行代码之后,可以看到,在触发GC之后BigObject被成功回收,而且一个WeakReference对象也被放入到了ReferenceQueue中。

before gc, reference.get is com.xxxxx.WeakRefDemo$BigObject@7852e922
before gc, queue is null
after gc, reference.get is null
after gc, queue is java.lang.ref.WeakReference@4e25154f

下面我们使用一个强引用来持有BigObject,看看GC是否可以回收对象:

上面的代码中BigOBject对象被一个强引用持有,即使bigObject对象被WeakReference修饰,GC之后并没有回收这个对象,所以不会将这个对象的WeakReference放入到ReferenceQueue中。

LeakCanary的实现思路

LeakCanary堆内存泄漏检测的核心是WeakReference和ReferenceQueue。

  • 1. 当一个Activity需要被回收的时候,会将其包装成一个WeakReference,并在WeakReference的构造函数中传入一个ReferenceQueue;
  • 2. 给包装后的WeakReference多一个标记Key,并且在一个强引用Set中添加这个Key的记录;
  • 3. 主动触发GC,GC之后遍历ReferenceQueue中的所有记录,将ReferenceQueue中有记录的Reference从Set中删除

我们之后在系统GC之后,没有被强引用持有的弱引用对象会被回收,回收之后的WeakReference会被放入到ReferenceQueue中,这样要是我们记录的应该被回收的对象清单Set中,除了ReferenceQueue中存在的已被回收的对象之外,剩余的就是应该被回收但并没有被成功回收的,这些对象就是发生了内存泄漏。

LeakCanary源码分析

从上面的分析可以知道利用WeakReference和ReferenceQueue可以实现内存泄漏的监控,但是如何知道一个Activity应该被回收了呢,一般情况下,当一个Activity的onDestroy被调用的时候,我们就认为一个Activity处于无用状态可以被回收了,因此我们需要监听每个Activity的onDestroy的调用情况。

LeakCancary中监听Activity生命周期是由ActivityRefWatch完成的,通过注册Android系统提供的ActivityLifecycleCallbacks。来监听Activity的生命周期:

上面LeakCanary通过registerActivityLifecycleCallbacks方法注册了对Activity生命周期的监听,传入了一个lifecycleCallbacks:

可以看到LeakCanary对Activity的onDestroy进行了监听,在Activity调用了onDestroy的时候,将这个Activity通知到RefWatcher中。

RefWatcher是LeakCanary的核心类,用来监控一个Activity是否发生了内存泄漏。在RefWatcher中会将即将被回收的Activity用WeakReference封装,并为它生成一个UUID,记录到retainedKeys中,用来保存应该被回收的Activity的记录。

接着Leakcanary会遍历ReferenceQueue中被回收对象,并将遍历到的对象的Key从retainedKeys中删除,剩余的长时间存在retainedKeys中的就是发生了内存泄漏未被回收的对象,LeakCanary会生成内存泄漏报告进行上报。

内存泄漏检查的时机

内存泄漏检测是比较耗时的,LeakCanary为了减少内存泄漏检查对UI渲染、以及业务逻辑的影响,使用了idleHandler。

我们知道Activity在启动之后会通过Looper.loop()阻塞的读取消息,当Looper的MessageQueue中没有消息的时候,线程会处于阻塞休眠的状态,我们如何知道主线程的Looper中没有消息可处理了呢,这个地方就要使用idleHandler了,LeakCanary会向主线程的MessageQueue中插入一个idleHandler,idleHandler只有在主线程处于空闲休眠的时候,才会被Looper从MessageQueue中取出执行,LeakCanary利用idleHandler有效的避免了占用主线程渲染时间。

LeakCanary检测其他类型的对象

LeakCanary默认只检测Activity的泄漏,但是RefWatcher的watch方法允许传入一个Object,这样LeakCanary实际上是可以检测任意类型对象的内存泄漏的。LeakReference的install方法会返回一个RefWatcher对象,我们可以将这个对象保存在Application中,然后将我们需要进行内存泄漏监控的对象传入到RefWatcher的watch方法中即可。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农帮派 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档