如何预防Handler内存泄漏?

昨天面试爱奇艺二面的时候,面试官提到了使用静态Handler。而实际中自己在工程中确实没有想到这个方法。所以这里来再次总结下为了防止Handler内存泄漏有哪些手段?

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

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

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

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

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

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

因为在HandlerhandleMessage方法是一个空实现,比如:

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退出的时候,清空当前的消息队列可以达到预防的目的。

即:在每个ActivityonDestroy生命周期方法中调用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

Guess you like

Origin blog.csdn.net/qq_26460841/article/details/120518083