Handler内存泄漏原理解析

35 篇文章 1 订阅

1. 简介

在写Android应用程序时,Handler应该是很常见的一个类。我们一般在使用该类时有采用如下方法:

// 1.新建一个匿名的Handler内部类,并重写handleMessage
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                //4.在收到传递来的消息时,可以更新UI
                Log.e("handler", "case 1");
                break;
            default:
                Log.e("handler", "default");
                break;
        }
    }
};

private void doInBackground() {
    new Thread(){
        @Override
        public void run() {
            //2.在工作线程中完成具体的事情
            try {
                // TODO doSomething();
                sleep(6000);
            } catch (Exception e) {

            }
            //3.事情完成后,通过mHandler线程间消息传递给主线程的Handler进行更新操作
            mHandler.sendEmptyMessage(1);
        }
    }.start();
}

上面新建Handler时也可以采用如下方法:

private Handler mHandler;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.about_and_feedback);
    mContext = this;
    mHandler = new MyHandler();
}
//采用匿名内部类方式,匿名内部类默认持有外部类引用。
private class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case 1:
                //主线程可以更新UI
                Log.e("handler", "case 1");
                break;
            default:
                Log.e("handler", "default");
                break;
        }
    }
}

以上两种方式都是新建了一个匿名的内部类,并直接new给了mHandler变量。采用如上方式使用Handler时会有内存泄漏的风险。

2. 原理剖析

2.1 Android的规定
1.在Android中主线程Looper对象的生命周期是和应用程序的生命周期是一样的,这也就意味在主线程的Looper是一直存在的。
2.在Java中非静态内部类或者匿名内部类都是默认持有外部类的引用的,这两个内部类的存在是依赖于外部类的对象的。比如如下例子:

//OutClass.java
public class OutClass {
    public class InnerClass {
        //内部类可以随意使用外部类的方法和属性,不论是否是private的
    }
}
//Client.java
public class Client {
    public static void main(String[] args) {
        //要想new一个InnerClass,必须先new一个OutClass
        //OutClass.InnerClass inn = new OutClass.InnerClass();//这句代码是错误的,会编译不过
        OutClass out = new OutClass();
        OutClass.InnerClass inn = out.new InnerClass();//这句可以编译过,这里就新建了一个依赖于out这个变量的内部类
    }
}

上面的out.new InnerClass()很奇怪,但是是正确的。单独new InnerClass()是编译不过的,非静态内部类或者匿名内部类是依赖于外部类的,默认是持有外部类的引用的。

2.2 具体原理分析
我们在通过mHandler.sendEmptyMessage(1)时,消息Message会被发送的MessageQueue里面,也就是Looper里面的mQueue,消息Message如果有延迟处理时,会留在MessageQueue里面,而Message是持有mHandler的引用,而mHandler是通过匿名内部类的形式创建的,会默认持有外部类Activity的引用。而我们的Looper对象的生命周期和应用程序生命周期一样长。这样在GC垃圾回收机制进行回收时发现这个Activity居然还有其他引用存在,因而就不会去回收这个Activity。

3. 解决方案

3.1 在Activity结束时将Handler里面的Message清空
由于在Activity结束后,Handler里面的消息还没有被处理导致,消息不处理完Handler的引用就一直存在。因而我们可以在onDestroy中将Handler里面的消息给清空了,这样就不会有消息引用Handler了,也就不会因为Handler引用Activity导致Activity无法释放了。具体代码如下:

@Override
protected void onDestroy() {
    super.onDestroy();
    //将Handler里面消息清空了。
    mHandler.removeCallbacksAndMessages(null);
}

3.2 静态内部类+弱引用
为了避免非静态内部类&匿名内部类持有外部类引用可以采用静态内部类或者直接在外部编写该Handler的继承类。如果该类需要使用Activity相关变量,可以采用弱引用的方式将Activity的变量传过去。在获取Activity的时候还可以加上判断当前Activity是不是isFinishing的代码,避免因为当前Activity已经进入了finish状态,还去引用这个Activity。具体代码如下:

    private static class MyHandler extends Handler{
        private WeakReference<MainActivity> reference;
        public MyHandler(MainActivity activity){
            reference = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(reference.get() != null){
                reference.get().btn.setText("...");
            }
        }
    }

静态内部类作用:如果是普通内部类handler会持有外部类的引用
弱引用作用:可以在GC时回收handler持有的外部对象

4. 总结

Handler导致内存泄漏的原因也就是因为在Looper的MessageQueue里面有还未处理的Message,而该Message又引用了Handler,如果该Handler采用的是非静态内部类或匿名内部类则该Handler又持有外部类Activity的引用,导致了Activity无法是否,因而产生了内存泄漏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值