This Handler class should be static or leaks might occur
一、现象
当使用内部类(包括匿名内部类)来创建 Handler 时,我们会看到一条警告信息:This Handler class should be static or leaks might occur
。翻译过来大概是:这个 Handler 对象应该是静态的,否则有可能造成内存泄露。
二、原理
内存泄露是什么
Java 有自己的垃圾回收机制(gc),gc 会检查内存中的对象(何时检查由 gc 自己决定),如果对象满足下列两种情况之一,gc 就会回收该对象:
- 该对象未被任何其他对象持有(即其他对象没有该对象的引用)
- 该对象在的一组对象中,该组对象只有相互之间的引用
而内存泄露就是 gc 未能成功回收某些应该被回收的对象,导致内存池中对象越来越多,最终突破 Android 对单个 app 的内存限制(一般是 24m),造成内存溢出,也就是我们常说的 OOM(out of memory)。
Handler 为何会造成内存泄露
1.Handler 与 Thread 配合实现异步任务
比如我们通过 Handler 和 Thread 实现一个网络请求,在请求的过程中,Activity 被关闭了(比如用户按了返回键),那么在 gc 检查时这个 Activity 是应该被回收掉的,但是你的网络请求(Thread)并未结束,也就是 Thread 持有 Handler,而 Handler 又持有 Activity,从而形成了一条 Thread→Handler→Activity 的链,导致 Activity 未能被成功回收,这就出现了内存泄露。
2.Handler 调用 postDelay()方法
postDealy()方法会把你的 Handler 对象装入一个 Message 对象中,然后将这个 Message 对象推入 MessageQueue 队列中,假如你的 Activity 关闭时,delay 的时间还没到,就会形成一条 MessageQueue→Message→Handler→Activity 的链,导致 gc 不能成功回收 Activity,从而造成内存泄露。
三、解决方案
1.完善程序逻辑
在 Activity 被关闭(onDestory()方法)时关闭或移除某些对象,从而避免对 Activity 的引用链。
- 使用 Handler 和 Thread,在 Activity 被关闭时将网络连接切断即可。
- 使用 postDealy 方法,在 Activity 被关闭时调用相应 Handler 的 removeCallbacks()方法将 Message 从 MessageQueue 中移除即可。
2.将 Handler 声明为静态类
静态类本身不会持有外部类对象,也就是说,在 Activity 中的 Handler 内部类如果被声明为静态的,就不会持有外部的 Activity 对象了。然而,这样一来 Handler 同时也不能像平常一样操作 Activity 中的那些非静态对象了,解决方法是在静态 Handler 类中声明一个对外部 Activity 的弱引用(关于弱引用的解释在文章底部),实现代码如下:
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
四、什么是弱引用(WeakReference)
弱引用 WeakReference,与强引用(即我们常说的引用)相对,它的特点是,gc 在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向,该对象就会在被 gc 检查到时回收掉。因此,在 Handler 中声明一个对 Activity 的弱引用,既可以操作 Activity 中的非静态对象,又可以避免因 Handler 对 Activity 的强引用造成的内存泄露。