使Handler内部类导致内存泄露的解决方法

之前用AndroidStudio自带的代码分析出现的警告,说的是Handler导致内存泄露
Handler reference leaks
the Handler should be static or leaks might occur (null)
出现这个提示的原因是有个Handler是内部类,并且是运行在主线程中的,我就是在Activity里有个内部类的Handler
因为调用Handler的sendMessage之类的一些方法会调用下面的代码
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;//Handler被保存进msg的target中
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);//msg又会进入队列
}
原因解释:msg的target会保存Handler对象的引用,并且放入Looper中,如果Looper所在线程为主线程,如果Handler一直在主线程的Looper中,而Handler因为是内部类,所以Handler又持有外部类的对象,那么外部类将一直无法被GC回收

而代码分析也给出了解决方法:
the Handler should be static,就是将Handler改为静态内部类的意思,这样Handler就不持有外部类的对象了
还有另一种方法是java中常用的解决内存泄露的方法是使用虚引用或弱引用
但是我们回到原点,如果Handler是内部类,并且Looper所在线程为主线程,那么一定会造成内存泄露么
其实还有一个条件,那么就是Handler必须长时间在主线程的Looper中,一旦Handler不在Looper中,实际上Handler还是可以被回收的
我们将使用Android Studio自带的查看内存的工具:


public class SecondActivity extends Activity implements View.OnClickListener{
    private static final int TEST = 1001;
    private static final int TEST_LOOP = 1002;
    private int [][] mFourMB = new int[4096][1024];

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        initEvent();
    }

    private void initEvent(){
        findViewById(R.id.bt_one).setOnClickListener(this);
        findViewById(R.id.bt_loop).setOnClickListener(this);
    }

    private Handler mUiHandler = new Handler() {
        @Override
        public void dispatchMessage(Message msg) {
            switch (msg.what) {
                case TEST:
                    Toast.makeText(SecondActivity.this, "测试 " + mFourMB.length, Toast.LENGTH_SHORT).show();
                    break;
                case TEST_LOOP:
                    Toast.makeText(SecondActivity.this, "测试 " + mFourMB.length, Toast.LENGTH_SHORT).show();
                    mUiHandler.sendEmptyMessageDelayed(TEST_LOOP, 5000);
                    break;
            }
        }
    };

    @Override
    public void onBackPressed() {
        this.finish();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.bt_one:
                mUiHandler.sendEmptyMessage(TEST);
                break;
            case R.id.bt_loop:
                mUiHandler.sendEmptyMessage(TEST_LOOP);
                break;
        }
    }
}
测试代码如上,主要是主要是使用发一次执行一次的
mUiHandler.sendEmptyMessage(TEST);
和发一次一直执行的:
mUiHandler.sendEmptyMessage(TEST_LOOP);

测试结果:
调用发送一次的执行一次的Handler时,内存从48M -> 16M,可顺利回收SecondActivity
调用发送一次的一直执行的Handler时,内存从48M -> 48M,不能顺利回收SecondActivity,

测试结果是符合预期的,那么,解决内部Handler类,导致的内存泄露也就很简单了,只需要在不需要Handler时,调用
mUiHandler.removeMessages(TEST_LOOP);
就可以了。
一般可以考虑放在Activity的onDestroy()方法内部。


如果不能及时removeMessage,那么还是尽量将MyHandler设置为静态内部类,并使用WeakReference的方式进行Activity的访问比较合理:
private static class MyHandler extends Handler{
        WeakReference<SecondActivity> secondActivityWeakReference;

        public MyHandler(SecondActivity secondActivity) {
            this.secondActivityWeakReference = new WeakReference<SecondActivity>(secondActivity);
        }

        @Override
            public void dispatchMessage(Message msg) {
            SecondActivity secondActivity = secondActivityWeakReference.get();
            if(secondActivity == null){
                return;
            }
                switch (msg.what) {
                    case TEST:
                        Toast.makeText(secondActivity, "测试 " + mFourMB.length, Toast.LENGTH_SHORT).show();
                        break;
                    case TEST_LOOP:
                        Toast.makeText(secondActivity, "测试 " + mFourMB.length, Toast.LENGTH_SHORT).show();
                        secondActivity.mUiHandler.sendEmptyMessageDelayed(TEST_LOOP, 5000);
                        break;
                }
            }
    }


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值