Handler内存泄漏
场景1
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
// 处理数据
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 后台耗时任务
threadWork();
}
private void threadWork() {
// 耗时任务
// ...
// 发送数据
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
分析:当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束。
场景2
如果你调用 Handler 的 postDelay() 方法执行了延时任务, 该方法会将你的Handler 装入一个 Message,并把这条 Message 推到 MessageQueue 中,那么在你设定的 delay 到达之前,会有一条 MessageQueue -> Message -> Handler -> Activity 的链,导致你的 Activity 被持有引用而无法被回收。
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
}
},5000);
public final boolean postDelayed(Runnable r, long delayMillis)
{
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// Message持有Handler的引用
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Message 会持有一个对 Handler 的引用,当这个 Handler 是非静态内部类的时候,又会持有一个对外部类的引用(比如Activity)。如果发送一条延时的 Message,由于这个 Message 会长期存在于队列中,就会导致 Handler 长期持有对 Activity 的引用,从而引起视图和资源泄漏。当你发送一条延时的 Mesaage,并且把这个 Message 保存在某些地方(例如静态结构中)备用,内存泄漏会变得更加严重。
解决 Handler导致的内存泄漏
方法 1、静态内部类 + 弱引用
既然非静态内部类持有外部类的引用,那么可以将 Handler 声明为静态内部类,Handler 也就不再持有 Activity 的引用,所以 Activity 可以随便被回收。代码如下:
static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
// ...
}
}
此时 Handler 不再持有 Activity 的引用,导致 Handler 无法操作 Activity 中对象,所以可以在 Handler 中添加一个对 Activity 的弱引用( WeakReference ):
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) {
//...
}
}
}
弱引用的特点: 在垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。 所以用户在关闭 Activity 之后,就算后台线程还没结束,但由于仅有一条来自 Handler 的弱引用指向 Activity,Activity 也会被回收掉。这样,内存泄露的问题就不会出现了。
方法2: 通过程序逻辑来进行保护
在 Activity 被销毁时及时清除消息,从而及时回收 Activity,避免内存泄漏问题。
@Override
protected void onDestroy() {
super.onDestroy();
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
}