内存泄漏本身并不会造成程序异常,但是随着量的增加会导致其他各种并发症:OOM,UI卡顿
Activity承担了与用户交互的责任,因此内部需要持有大量的资源引用以及与系统交互的Context ,这会导致一个Activity对象的retained size特别大。一旦Activity因为被外部系统所持有而导致内存泄漏,被牵连导致其他对象的内存泄漏也会非常多。
造成Activity内存泄漏的场景主要有一下几种:
View默认会持有一个Context的引用,如果将其置为static将会造成View在方法区中无法被快速回收,最终导致内存泄漏。 private static ImageView imageView 。imageView会造成所在类无法被GC回收。
在Activity中可能会注册各种系统监听器,比如广播
private Handler handler = new Handler(){
@Override
public void handlerMessage(Message msg){
super.handleMesage(msg);
}
};
上述代码中的Handler也会造成此Activity内存泄漏,一般需要将其置为static,然后内部持有一个Activity的弱引用来避免内存泄漏。
经常使用第三方库时,有些库的初始化需要传入一个Context对象。但是第三方库中有可能一直持有此Context引用。如:三方库中将传入的context重新置为一个static类型。这种很难察觉,平时使用尽量使用Context.getApplicationContext,不要直接将Activity传递给其他组件 。在自己实现SDK时,可以使用context.getApplicationContext,即使外部传入是Activity,也不会造成内存泄漏context.getApplication();
可以使用AndroidStudio查看Activity是否存在内存泄漏,并结合MAT来查看发生内存泄露的对象。这个今后和Android性能调优放一起学习。
除了AS之外,还有一个工具就是LeakCanary,这是一个开源库。通过它可以在APP运行时检测内存泄漏,当内存泄漏时会生成发生泄露对象的引用链,并通知程序开发人员。
可以看到LeakCanary主要分2大核心部分:
Java中WeakReference是弱引用类型,每当发生GC时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收。如下:
public class WealRefDemo{
public static void main(...){
WeakReference<BigObject> reference = new WeakReference<>(new BigObject());
println("before GC,reference get is " + reference.get());
System.gc();
Thread.sleep(1000);
println("after GC,reference get is " + reference.get());
}
}
结果如下
before gc,reference get is ...
after gc,reference get is null
WeakReference的构造函数可以传入ReferenceQueue,当WeakReference指向的对象被GC回收时,会把WeakReference放入ReferenceQueue中。比如上述代码中,调用WeakReference构造器时,传入一个自定义的ReferenceQueue,如下:
ReferenceQueue<BigObject> queue = new ReferenceQueue<>();
WeakReference<BigObject> reference = new WeakReference<>(new BigObject(),queue);
println("before GC,queue is " + queue.poll());
println("after GC,queue is " + queue.poll());
加上打印后
before gc,queue is null
after gc,queue is ...
可以看出,当BigObject被回收之后,WeakReference会被添加到所传入的ReferenceQueue中。此时模拟一个内存泄漏。
BigObject bigObject = new BigObject();
WeakReference<BigObject> reference = new WeakReference<>(bigObject,queue);
bigObject是一个强引用,导致new BigObejct()的内存空间不会被GC回收。打印如下:
before gc,reference get is ...
queue is null
after gc,reference get is ...
queue is null
LeakCanary中对内存泄漏检测的核心原理就是基于WeakReference和ReferenceQueue实现的。
经过上面3步之后,还保留在Set中的就是:应当被GC回收,但是实际还保留在内存中的对象,也就是发生泄漏的对象
在上面原理介绍的例子中,我们知道一个可回收对象在System.gc()之后就应该被GC回收。可是在Android APP中,我们并不清楚何时系统会回收Activity。但是,按照正常流程,当Activity调用onDestroy()时就说明这个Activity就已经处理无用状态了。因此我们需要监听到每一个Activity的onDestroy()方法的调用 。
LeakCanary中监听Activity生命周期是由ActivityRefWatch来负责的,主要是通过注册Android系统提供的ActivitLifecycleCallbacks,来监听Activity的声明周期方法。
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
当监听到Activity的onDestroy()方法后,会将其传给RefWatcher的watch方法
它是LeakCanary的一个核心类,用来检测一个对象是否会发生内存泄漏。主要实现是在watch()方法中
String key = UUID.randowUUID.toString();
retainedKeys.add(key);
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference,key,referenceName,queue);
ensureGoneAsync(watchStartNamoTime,reference);
具体就在ensureGoneAsync中实现内存泄漏的检测
内存泄漏的检测与分析是比较消耗性能的,因此为了尽量不影响UI线程的渲染,LeakCanary也做了些优化操作。在ensureGoneAsync中调用了WatchExecutor的execute方法来执行检测操作
实际上是向主线程MessageQueue中插入了一个idleHandler,idleHandler只会在主线程空闲时才会被Looper从队列中取出,并执行。因此能够有效避免内存泄露检测工作占用UI渲染时间。
通过idleHandler也经常做APP启动优化,比如在Application的onCreate()方法中经常做3方库的初始化工作。可以将优先级较低,暂时使用不到的3方库的初始化操作放到idleHandler中,从而加快Application的启动过程。不过个人感觉方法名应该叫addidleMessage更合适,因为向MessageQueue插入的都是Message对象
LeakCanary默认只能检测Activity的泄漏,但是RefWatcher的watch()方法传入的参数实际是Object,所以理论上可以检测任何类。LeakCanary的install方法会返回一个RefWatcher对象,我们只需要在Application中保存此RefWatcher对象,然后将需要被检测的对象传给watch方法即可。