Android使用Handler造成内存泄露的分析及解决方法

1. Handler为什么会造成内存泄漏?

这就需要从消息机制的原理说起。因为在之前的博客,或者网上有很多介绍这部分原理,所以这里就直接进入主题。

我们知道在消息机制中的流程为handler最终通过enqueueMessage将一个Message添加到消息队列MessageQueue中,然后每个线程的Looper会循环的判断是否有消息,如果存在就从消息队列中取出消息。由于每个消息对象中都关联了发送消息的Handler实例对象target,那么在消息队列中取出Message的对象的时候,可以通过target获取到Handler对象,最终通过Handler的dispatchMessage来进行消息的发送。因为Handler实例化在主线程中,所以这里可以完成子线程和主线程的一次数据传递。

那么,这个过程在什么地方会导致内存泄漏呢?

这里直接给出答案,因为如果消息Message通过when字段来进行比较,最终添加到一个有序的链表MessageQueue中。当我们的Activity退出了,但是在消息队列中仍然有当前Activity中的Handler未处理完毕的消息的时候,就会导致内存泄漏!这是为什么呢?不妨看看我们通常非静态Handler是怎么写的:

Handler handler = new Handler(Looper.getMainLooper()){
    @Override
    public void handleMessage(@NonNull Message msg) {
        super.handleMessage(msg);
    }
};

因为在Handler中handleMessage方法是一个空实现,比如:

public void handleMessage(@NonNull Message msg) {
}

而我们在处理消息的时候,就需要自己去实现这个方法。故而使用前面的匿名内部类方式来创建一个Handler的示例。

我们知道,匿名内部类会隐式的持有外部类的示例对象,故而在这个Handler的示例对象中,其实会持有已经销毁的Activity的示例,而在消息队列中还有待处理的消息的时候,那么这个Handler也就不会被回收。那么将导致这个本该被销毁的Activity不会被回收。也就是内存泄漏。

2. 预防Handler内存泄漏?

我们通过上面已经知道了原因,那么对应的可以有如下的几种解决方法。

2.1 使用静态Handler

由于静态的Handler的生命周期为应用的生命周期,那么当我们使用静态的Handler的时候,其实并不需要为每个Activity都创建一个静态的Handler,因为这样就不太合理了。在Java语言中,默认创建的实例为强引用对象,也就是说如果不置为null,那么这个对象就永远不会被回收,除非程序终止。

那么,毫无疑问这样将导致很多空间的浪费。且在Message中提供了很多字段来标识消息的类别,故而应该将Handler复用,而不是创建多个。且创建过程需要分配空间,其实会消耗一定的效率。

所以,处于效率和空间的考虑,在使用静态Handler的时候,还是尝试使用单例模式来做。比如下面的案例:

public class MyHandler {
    // 锁对象
    private static final Object mObject = new Object();
    private static Handler mHandler;

    private MyHandler(){}

    public static Handler getHandler(IHandler iHandler){
        if(mHandler == null){
            synchronized (mObject){
                if(mHandler == null){
                    mHandler = new Handler(Looper.getMainLooper()){
                        @Override
                        public void handleMessage(@NonNull Message msg) {
                            iHandler.handleMessage(msg);
                        }
                    };
                }
            }
        }
        return mHandler;
    }
	
	// 定义一个回调接口
    public interface IHandler{
        void handleMessage(@NonNull Message msg);
    }
}

然后再使用的时候,比如:

public class ThreeActivity extends AppCompatActivity implements MyHandler.IHandler {
    private ProgressBar progressbar;
    private Handler handler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_three);
        progressbar = findViewById(R.id.progressbar);
        progressbar.setMax(100);

        handler = MyHandler.getHandler(this);

        new Thread(new Runnable() {
            @Override
            public void run() {
                Message msg = new Message();
                msg.obj = 50;
                msg.what = 1;
                handler.sendMessage(msg);
            }
        }).start();
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
        if(msg.what == 1){
            progressbar.setProgress((int) msg.obj);
        }
    }
}

测试结果:

2.2 清空消息队列

前面我们知道当消息队列中还有未处理的消息的时候,才会导致Activity的内存泄漏,故而可以在这个Activity退出的时候,清空当前的消息队列可以达到预防的目的。

即:在每个Activity的onDestroy生命周期方法中调用removeCallbacksAndMessages函数,比如:

@Override
protected void onDestroy() {
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
}

2.3 使用静态Handler+弱引用WeakReference

前面提到过使用静态Handler的各种好处,这里继续使用静态Handler来实现,只是在其中使用弱引用。

private static class StaticHandler extends Handler{
 private WeakReference<Activity> mWeakReference;
    public StaticHandler(Activity activity){
        mWeakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
        Activity activity = mWeakReference.get();
        if(activity == null) return;
        // todo 处理消息
    }
}

这个代码,参考自博客:Android使用Handler造成内存泄露的分析及解决方法。

有种豁然开朗的感觉,因为在handleMessage中判断一次,就可以做到避免。且本身来说定义为static,那么不存在匿名内部类隐式持有问题,而且在这个静态类中,将传入的Activity实例对象使用弱引用来修饰,那么可以在GC的时候被回收,然后及时的在handleMessag时候判断一次,更加安全可靠。

3. 最终版本

/**
 * Date: 2021年9月28日 10:11:52
 * Author: 梦否
 * Example:
 * MyHandler myHandler = MyHandler.getHandler(this);
 * myHandler.setActivityReference(this);
 * handler = myHandler.getHandler();
 */
public class MyHandler {
    // 锁对象
    private static final Object mObject = new Object();
    private Handler mHandler;
    private WeakReference<Activity> mWeakReference;
    private static MyHandler mMyHandler;

    private MyHandler(IHandler iHandler){
        mHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                Activity activity = mWeakReference.get();
                if(activity != null){
                    iHandler.handleMessage(msg);
                }
            }
        };
    }

    /**
     * 虚引用,为了完成逻辑:当前activity不存在时候不处理
     * @param activity
     */
    public void setActivityReference(Activity activity){
        mWeakReference = new WeakReference<>(activity);
    }

    public Handler getHandler(){
        return this.mHandler;
    }

    /**
     * 单例,为了复用
     * @param iHandler
     * @return
     */
    public static MyHandler getHandler(IHandler iHandler){
        if(mMyHandler == null){
            synchronized (mObject){
                if(mMyHandler == null){
                    mMyHandler = new MyHandler(iHandler);
                }
            }
        }
        return mMyHandler;
    }

    public interface IHandler{
        void handleMessage(@NonNull Message msg);
    }
}

Thanks

  • Android使用Handler造成内存泄露的分析及解决方法
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值