handler导致内存泄露的真正原因

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/alex01550/article/details/82744191

handler是我们在更新UI时经常使用到的类,但是不注意的话,很容易就导致内存泄露,最后导致OOM,故现在探究下handler导致内存泄露的原因及有哪些常用的解决办法。

先看下面一段代码:

 可以看到这段代码编辑器为我们标出了黄色,并且提示如下:

This Handler class should be static or leaks might occur (anonymous android.os.Handler) less... (Ctrl+F1)
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

简单翻译如下:

由于handler定义为内部类,可能会阻止GC。如果handler的Looper或MessageQueue 非主线程,那么没有问题。如果handler的Looper或MessageQueue 在主线程,那么需要按如下定义:定义handler为静态内部类,当你实例化handler的时候,传入一个外部类的弱引用,以便通过弱引用使用外部类的所有成员。

理解这段话很重要,为何要用static、为何要使用WeakReference 引用外部类,我自己理解如下:

①先说handler导致activity内存泄露的原因:

handler发送的消息在当前handler的消息队列中,如果此时activity finish掉了,那么消息队列的消息依旧会由handler进行处理,若此时handler声明为内部类(非静态内部类),我们知道内部类天然持有外部类的实例引用,那么就会导致activity无法回收,进而导致activity泄露。

为何handler要定义为static?

因为静态内部类不持有外部类的引用,所以使用静态的handler不会导致activity的泄露

为何handler要定义为static的同时,还要用WeakReference 包裹外部类的对象?

这是因为我们需要使用外部类的成员,可以通过"activity. "获取变量方法等,如果直接使用强引用,显然会导致activity泄露。


其实,上面那段英文已经说的非常清楚了,下面用例子进行说明:

演示内存泄露

看下面代码:

private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handleroom);

        findViewById(R.id.btn_send).setOnClickListener(this);
        tv = findViewById(R.id.tv);

        // 延时5min发送一个消息,此时handler是持有activity引用的
        mHandler.sendEmptyMessageDelayed(1, 5 * 60 * 1000);
    }

声明了一个匿名内部类的handler,在onCreat中延时两分钟发送了一个消息,将程序运行起来,

初始内存占用:61M

 然后我们旋转屏幕,屏幕旋转几次之后94M左右,每次旋转都会导致内存占用增加

注意这时候的操作:如果我们快速的进行屏幕旋转,然后强制进行GC回收(在5min之内),那么内存占用是降不下来的,说明发生了内存泄露。这时候我们再等个几分钟,以保证handler处理完所有的消息,会发现内存占用降下来了,我的理解是几分钟不操作后,handler会处理完所有的消息,这时handler会被回收,进而引用的activity也得到了回收,所以最后内存占用和初始是差不多的。

使用静态内部类的handler

代码如下:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handleroom);

        findViewById(R.id.btn_send).setOnClickListener(this);
        tv = findViewById(R.id.tv);

        // 延时5min发送一个消息,此时handler是持有activity引用的
//        mHandler.sendEmptyMessageDelayed(1, 5 * 60 * 1000);
        handler.sendEmptyMessageDelayed(0, 5 * 60 * 1000);
    }

 /**
     * 避免handler内存泄露的写法
     * handler为何会导致内存泄露:
     * 如果handler为内部类(非静态内部类),那么会持有外部类的实例,
     * 若在handler.sendMessage的时候,activity finish掉了,那么此时activity将无法得到释放
     * <p>
     * 如果申明handler为静态内部类,则不会含有外部类的引用,
     * 但是需要在handler中更新UI(注意此时handler为static),则需要引入一个activity引用,
     * 显然必须是弱引用,否则会导致和上面一样的结果
     * <p>
     * 使用activity弱引用
     * 之所以使用弱引用,是因为handler为static,使用activity的弱引用来访问activity对象内的成员
     */
    private static class MyHandler extends Handler {
        private WeakReference<HandlerOOMActivity> weakReference;
//        private HandlerOOMActivity activity;

        public MyHandler(HandlerOOMActivity activity) {
            weakReference = new WeakReference<>(activity);
//            this.activity = activity;
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG, "handleMessage: ");
//            HandlerOOMActivity activity = weakReference.get();
            switch (msg.what) {
                case 0:
//                    if (activity != null) {
//                        activity.tv.setText("我是更改后的文字");
//                    }
//                    activity.tv.setText("我是更改后的文字");
                    break;
            }
        }
    }

 请忽略activity的弱引用,因为关系不太,之所以弱引用activity,是为了访问activity对象的成员,看注释掉的代码,如果强引用了activity,那么将导致和上面一样的结果!

初始内存占用:60M

 如果我们快速的旋转屏幕,那么会发现刚开始内存占用会增加,但是很快会降下来,如果强制GC,那么会回到初始值,与我们预想的一样。

使用单独定义的handler类

这里不再贴代码和演示了,通过上面的分析,同样不会出现内存泄露,因为handler没有持有activity的引用

既然handler导致的activity泄露是由于activity finish后持有activity引用的handler处理消息导致的,那么在activity finish之前移除所有的消息,这时虽然内部类的handler仍然持有activity引用,但是由于handler是可以随时被回收的,即持有activity引用的handler可以随时被回收,有点类似于第一种情形,即让所有的消息处理完。这样是不是也是可行的?答案是肯定的。

 可以看下上面的内存占用图,和前两种情形不太一样,是在达到了较高的阈值之后马上降下来,而前两种是缓慢增加缓慢下降的,这是因为这种handler依旧含有activity的引用,所以handler对象占用的内存相对较高。

总结:

* 避免handle内存泄露的办法
* 1.使用static 修饰的handler,但是一般会弱引用activity对象,因为要使用activity对象中的成员
* 2.单独定义handler,同样可以弱引用activity
* 3.使用内部类的handler,在onDestroy方法中removeCallbacksAndMessages

以上

猜你喜欢

转载自blog.csdn.net/alex01550/article/details/82744191