由Handler引起内存泄露引发的java类设计思考

在用eclipse编写Android应用时,可能导致内存泄漏问题的handler一般会被提示 Lint警告:

handler类static警告

This Handler class should be static or leaks might occur
意思:Handler类应该使用静态声明,否则可能导致内存泄露。
用MAT工具查看TestActivity
java的垃圾回收机制已经帮助我们解决内存回收的一些问题。但是尽管有了垃圾回收机制,在开发android的时候仍然时不时的遇到OOM(Out Of Memory)的问题。大家知道,GC的原理是当对象的所有引用关系都断开时才会被回收。因此handler引起内存泄露一定是因为对象的引用未被全部释放。为了探寻这个问题的究竟,我们来做一个模拟测试:

用MAT工具查看TestActivity

  • 1. 缘起(一个非常简单的需求)
    在TestActivity中,实现一个定时循环任务,每5秒执行一次。任务本身很简单,打印一句log。
    为了实现此需求,利用Handler机制,在Activity启动时,向Handler发送一条消息MSG_TEST,在Handler处理此消息时,执行一次任务,同时向Handler本身延时5秒再发送一次该消息,如此可构成循环消息,即完成了循环任务。代码如下:
public class TestActivity extends Activity {

    protected static final String TAG = "TestActivity";
    protected static final int MSG_TEST = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i(TAG, "onCreate");
        mHandler.sendEmptyMessage(MSG_TEST);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            switch (msg.what) {
            case MSG_TEST:
                Log.w(TAG, "Handle the test message!");
                mHandler.sendEmptyMessageDelayed(MSG_TEST, 5000);
                break;

            default:
                break;
            }
            super.handleMessage(msg);
        }
    };
}

看起来,代码完成了需求,实际跑一跑吧!
不废话,上log:
这里写图片描述
哎哟不错,确实是每5秒打印一次log哟!
然并卵,真的就此OK了么?对不起,我小手抖了一下,退出TestActivity再重新进来,结果诡异发生了:
这里写图片描述
仔细看!
退出界面重新进来后,TestActivity又跑了一次onCreate方法,这个可以理解。问题是,看下面的log,一下子打印得密集起来了,仔细看时间,不难看出,是每5秒有两次打印。不是上一个Activity退出后销毁了,Handler跟着销毁,任务不再被执行么?下次再进来新的Activity创建并重新启动循环任务并执行么?
猜测一下,是不是有两个Handler同时存在,并且都在执行定时任务?
为了验证这个猜测,小手再抖一次,再退出重进一次,是不是log打印密度变成了三倍呢?
继续上log:
这里写图片描述
再仔细看log,还真是!有三个定时任务在同时跑,它们之间互相没有关联,都是每隔5秒打印一次。
多旋转几次屏幕,通过log分析,就能确切的证明之前的猜测不幸言中!
当然,通过debug断点调试,也可以得出同样的结论。

  • 2. 问题来了:这不是传说中的内存泄露了?怎么分析和解决呢?
    用内存分析工具MAT(Memory Analyzer Tool)吧!没用过MAT的同学,自行搜索一下MAT的安装及使用方法。
    通过对该测试进程dump出其内存镜像,MAT给出的直方图如下:
    这里写图片描述
    有经验的同学,直接就能看出,TestActivity及其成员mHandler(就是匿名内部类实例TestActivity$1)在内存中分别有16个对象,显然是发生了内存泄露!
    这里写图片描述
    如上图,再查看TestActivity的引用链(对TestActivity进行merge_shortest_paths[excludes all phantom/weak/soft etc. references]),发现它是被MessageQueue所持有。MessageQueue就是Handler在其线程上建立的消息队列!
    用同样的方法,可以看到mHandler也是被MessageQueue所持有:
    这里写图片描述
    至此,可以判定是Handler引起内存泄露。
    再回过头仔细回味eclipse的这句警告语”This Handler class should be static or leaks might occur“,真是这么回事,用Handler不小心就容易引发内存泄漏!

  • 3. 避免因Handler引发的内存泄露的实践
    既然eclipse都已经警告了Handler要设计成static,那就照做呗,代码修改:

private static Handler mHandler = new Handler() { … };

再跑一次,发现是同样的结果,仍然有内存泄漏!
究其原因,Activity退出后仍然被MessageQueue所引用,无法被GC回收。既然这样,那就改用弱引用吧,弱引用是能被GC回收的。修改,上代码:

public class TestActivity extends Activity implements Callback {

    protected static final String TAG = "TestActivity";
    protected static final int MSG_TEST = 1;
    private Handler mHandler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.i(TAG, "onCreate:"+this);
        mHandler.sendEmptyMessage(MSG_TEST);
    }

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

        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            switch (msg.what) {
            case MSG_TEST:
                TestActivity activity = mActivityRef.get();
                if (activity!=null) {
                    activity.doTask();
                } else {
                    Log.e(TAG, "activity has been recycled!");
                }
                break;

            default:
                break;
            }
            super.handleMessage(msg);
        }
    };

    void doTask() {
        Log.w(TAG, "doTask! Handle the test message!");
        if (mHandler!=null) {
            mHandler.sendEmptyMessageDelayed(MSG_TEST, 5000);
        }
    }

    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        Log.w(TAG, "onDestroy:"+this);
        mHandler.removeCallbacksAndMessages(null);
        mHandler = null;
        super.onDestroy();
    }

    @Override
    public boolean handleMessage(Message msg) {
        // TODO Auto-generated method stub
        switch (msg.what) {
        case MSG_TEST:
            doTask();
            break;

        default:
            break;
        }
        return false;
    }

}

再次通过log打印及MAT工具分析,仔细查看,终于已经没有内存泄露了!

由此总结出:类似于Handler,生命周期脱离其Activity的生命周期而又引用Activity的,一定要引起重视,尽量设计为static类,并通过弱引用持有Activity的相关对象,并在Activity销毁时,尽量移除待处理的消息及事件。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值