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。
我们之后在系统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方法中即可。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有