Handler造成内存泄漏的原因以及解决方案

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);
    }
}
  • 11
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值