美文网首页
Android内存泄漏(五):Handler

Android内存泄漏(五):Handler

作者: meixinxicun | 来源:发表于2017-03-22 18:29 被阅读578次

上一节我们介绍了非静态内部类作为静态变量造成的内存泄漏情况,这一节我们介绍一下Handler的使用造成的内存泄漏情况

知识点

非静态内部类匿名类内部类的实例都会潜在持有它们所属的外部类的强引用,但是静态内部类却不会

使用匿名内部类

我们来看一段代码:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }
}

这个Activity中new Runnable()是一个匿名内部类,这个内部类持有外部类Activity的强引用,内部类被封装成消息Message被传递到Handler的消息队列MessageQueue中,即消息持有Activity的强引用。在Message消息没有被Handler处理之前,Activity实例不会被销毁了,于是导致内存泄漏。发送postDelayed这样的消息,你输入延迟多少秒,它就会泄露至少多少秒。而发送没有延迟的消息的话,当队列中的消息过多时,也会照成一个临时的泄露。可参考Android内存泄漏之匿名内部类

使用静态内部类

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new MyRunnable(textView), 5000000L);
    }

    private static final class MyRunnable implements Runnable {
        private final TextView mTextView;

        protected MyRunnable(TextView textView) {
            mTextView = textView;
        }

        @Override
        public void run() {
            mTextView.setText("Finished");
        }
    }
}

这段代码中,我们使用的是静态内部类,那么这样还会内存泄漏吗?
答案:会
上面知识点中我们提到了静态内部类不会持有外部类的引用,那么为什么这里还会内存泄漏呢。
因为TextView持有Activity的强引用,我们都知道View都持有Context的引用,这里的Context就是Activity。new MyRunnable(textView)持有TextView的强引用,这样MyRunnable也就持有Activity的强引用了,所以消息为处理之前,Activity实例不会被销毁,于是导致内存泄漏。

解决方案1:弱引用+静态内部类

匿名内部类因为持有Activity的强引用,所以会导致内存泄漏。
静态内部类中的TextView持有Activity的强引用,所以也会导致内存泄漏
Android内存泄漏和引用的关系中,我们有讲到弱引用:如果一个对象具有弱引用,在GC线程扫描内存区域的过程中,不管当前内存空间足够与否,都会回收内存。
代码如下:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);

        handler.postDelayed(new MyRunnable(textView), 5000000L);
    }

    private static final class MyRunnable implements Runnable {
        private final WeakReference<TextView> wr;

        protected MyRunnable(TextView textView) {
            wr = new WeakReference<TextView>(textView);
        }

        @Override
        public void run() {
            final TextView tv = wr.get();
            if (tv != null) {
                tv.setText("Finished");
            }
        }
    }
}

这里我们把静态内部类的TextView改成弱引用了,这样虽然textView持有activity的强引用,但是new MyRunnable(textView)持有的是TextView的弱引用,这样MyRunnable持有Activity的引用也是弱引用,所以内存回收的时候,是可以回收的,我们可以通过wr.get()是否为空判断是否已经回收。

解决方案2:在onDestory的时候,手动清除Message

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}

如上方法,我们在ondestory的时候,清除所有未处理的Message,就不会有哪个消息持有Activity的强引用了,这样也不会导致内存泄漏。

解决方案3:使用第三方控件WeakHandler

WeakHandler是一个第三方库,我们看看他是怎么使用的:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private WeakHandler handler = new WeakHandler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }
}

它用起来很简单,不需要考虑弱应用的情况,你只需要把以前的Handler替换成WeakHandler就行了。
我们看看WeakHandler的源代码:

private final WeakHandler.ExecHandler mExec;
public final boolean postDelayed(Runnable r, long delayMillis) {
    return this.mExec.postDelayed(this.wrapRunnable(r), delayMillis);
}
private WeakHandler.WeakRunnable wrapRunnable(@NonNull Runnable r) {
    if(r == null) {
        throw new NullPointerException("Runnable can\'t be null");
    } else {
        WeakHandler.ChainedRef hardRef = new WeakHandler.ChainedRef(this.mLock, r);
        this.mRunnables.insertAfter(hardRef);
        return hardRef.wrapper;
    }
}
static class WeakRunnable implements Runnable {
    private final WeakReference<Runnable> mDelegate;
    private final WeakReference<WeakHandler.ChainedRef> mReference;

    WeakRunnable(WeakReference<Runnable> delegate, WeakReference<WeakHandler.ChainedRef> reference) {
        this.mDelegate = delegate;
        this.mReference = reference;
    }

    public void run() {
        Runnable delegate = (Runnable)this.mDelegate.get();
        WeakHandler.ChainedRef reference = (WeakHandler.ChainedRef)this.mReference.get();
        if(reference != null) {
            reference.remove();
        }

        if(delegate != null) {
            delegate.run();
        }

    }
}
private static class ExecHandler extends Handler {
    private final WeakReference<Callback> mCallback;

    ExecHandler() {
        this.mCallback = null;
    }

    ExecHandler(WeakReference<Callback> callback) {
        this.mCallback = callback;
    }

    ExecHandler(Looper looper) {
        super(looper);
        this.mCallback = null;
    }

    ExecHandler(Looper looper, WeakReference<Callback> callback) {
        super(looper);
        this.mCallback = callback;
    }

    public void handleMessage(@NonNull Message msg) {
        if(this.mCallback != null) {
            Callback callback = (Callback)this.mCallback.get();
            if(callback != null) {
                callback.handleMessage(msg);
            }
        }
    }
}

源代码中可以清楚的看到它将Handler和Runnable做了弱引用封装,而ExecHandler和WeakRunnable也就是封装之后的内部类。

上一节:Android内存泄漏(四):非静态内部类作为静态变量

相关文章

  • (转载自diycode)2017 Android 面试题分享整理

    Android(安卓) Android基础知识 Android内存泄漏总结 Handler内存泄漏分析及解决 An...

  • Android 基础

    1、Android布局 2、Android内存泄漏总结 3 、Handler内存泄漏分析及解决 4...

  • 性能优化与保活

    ------内存泄漏优化------Android 内存优化你的 Handler 内存泄露 了吗?Android卡...

  • android学习资料

    第一部分: Android(安卓) Android基础知识 Android内存泄漏总结 Handler内存泄漏分析...

  • Android学习之旅

    1.Android基础知识2.Android内存泄漏总结3.Handler内存泄漏分析及解决4.Android性能...

  • Android知识体系总结

    第一部分:Android(安卓)Android基础知识Android内存泄漏总结Handler内存泄漏分析及解决H...

  • Android 消息机制 你了解多少

    级别:★★☆☆☆标签:「Handler」「Android」「消息机制」「内存泄漏」作者: Zoyp晨[https:...

  • java基础

    Handler怎样防止内存泄漏 handler引起内存泄漏的原因:由于handler的写法问题,如果handler...

  • Android内存泄漏(五):Handler

    上一节我们介绍了非静态内部类作为静态变量造成的内存泄漏情况,这一节我们介绍一下Handler的使用造成的内存泄漏情...

  • Android的垃圾回收与内存泄露

    标签(空格分隔): Android 内存泄漏的基本知识请见博客一如何高效使用handler避免内存泄漏请见博客二 ...

网友评论

      本文标题:Android内存泄漏(五):Handler

      本文链接:https://www.haomeiwen.com/subject/akbonttx.html