说说Handler的一些使用姿势

Handler大家应该都很熟悉了,毕竟只要是涉及到Android里面线程间的通讯,总会看到它的身影。我平时也经常使用Handler,今天整理笔记,就将之前记录下来的Handler的一些使用姿势发到这里来。

1.在子线程中创建Handler对象

这个可能比较少见,一般都是在主线程创建Handler对象。不过少见不代表我们以后不会遇到。
按照我们平时在主线程创建Handler对象的习惯,代码应该是下面这个样子:

private class otherThread extends Thread {

        @Override
        public void run() {
            super.run();
            Handler otherHandler = new Handler();
        }
    }

不过这样子只要一运行程序就可以看到下面的报错:

这里写图片描述
按照提示我们在子线程里面创建Handler对象之前调用Looper.prepare();方法。这里或许有人会提出疑问,明明我们在主线程都不需要这样子做啊。很简单,因为Android的系统已经提前做了处理了。到ActivityThread中的main方法(APK的Java静态入门)看一眼:

这里写图片描述

再点Looper.prepareMainLooper();进去看一眼:

这里写图片描述

这里的注释说明的很清楚了,这个是给当前线程初始化一个Looper。所以不是主线程创建Handler不需要调用Looper.prepare();方法,而是人家已经提前帮我们做了。所以在子线程创建Handler对象的正确姿势应该是下面这个样子:

 private class otherThread extends Thread {

        @Override
        public void run() {
            super.run();
            Looper.prepare();
            Handler otherHandler = new Handler();
            Looper.loop();
        }
    }

2.Handler导致的内存泄露

这个估计会比较常见,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用只要一个不小心即有可能造成内存泄漏,如下示例:

public class TestActivity extends AppCompatActivity {

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
             //这里处理逻辑
        }
    };

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

    private void loadData() {
        //子线程访问网络获取数据中
        ................
        Message message = Message.obtain();
        mHandler.sendMessageAtTime(message, 5 * 1000);
        ..........
    }

由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类TestActivity 的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,从而引发内存泄漏。所以我们应该这么写:

public class TestActivity extends AppCompatActivity {

    private MyHandler mHandler = new MyHandler(this);

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

    private void loadData() {
        //子线程访问网络获取数据中
        ................
        Message message = Message.obtain();
        mHandler.sendMessageAtTime(message, 5 * 1000);
        ................
    }

    private static class MyHandler extends Handler {

        //对context使用弱引用
        private WeakReference<Context> reference;

        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
            TestActivity activity = (TestActivity) reference.get();
            //避免TestActivity已经被回收了,是一个null值
            if (activity != null) {
                //这里处理逻辑
            }
        }
    }
}

创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在GC可以顺利回收Handler持有的对象,这样就是避免了Activity的泄漏。但是,每次在Activity里面使用Handler都这么写会不会很累啊。其实还有一个更加简单方法,如下所示:

扫描二维码关注公众号,回复: 2073653 查看本文章
public class TestActivity extends AppCompatActivity {
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有Message.清空消息队列也就意味着这个Handler的对象被打回原形,GC可以很顺利一棍子将其打死。
所以,我们平时在Activity或Fragment使用Handler避免潜在的内存泄露的正确姿势就是onDestroy方法或onStop方法中调用一下Handler的removeCallbacksAndMessages(null);就可以了

3.Handler的post方法也是经常用到的

有时候我们是会直接在子线程中调用主线程的Handler对象的post来修改UI。我们来捉下打印来看下它为什么可以修改UI吧:

public class TestActivity extends AppCompatActivity {

    private static final String TAG = "TestActivity";
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        Log.e(TAG,"MainThread id:"+Thread.currentThread().getId());
        setThreadAction();
    }

    private void setThreadAction() {
        new otherThread().start();
    }

    private class otherThread extends Thread {

        @Override
        public void run() {
            super.run();
            Log.e(TAG,"otherThread id:"+Thread.currentThread().getId());
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    Log.e(TAG,"the id:"+Thread.currentThread().getId());
                }
            });
        }
    }
}

看下打印的结果:

这里写图片描述
这就是说明了这里的Runnable的run()方法其实运行在主线程了,这也是我们为什么可以用它来修改UI的原因。或许有人会疑问Runnable明明是在子线程中创建的,怎么就变成了主线程呢?我们去瞄一下源码其实是可以发现Message里面有个Runnable参数对象,Handler的post方法最终就是给Message的这个Runnable参数赋值
因此,mHandler.post(Runnable runnable)和mHandler.sendMessage(Message message)这两个方法在本质上就是Handler发送了Message。那么主线程的Handler发送的Message自然就在主线程中处理。
既然是主线程,那么当我们使用这个Handler的post方法时就不能执行任何耗时操作了,否则就会就会出现ANR异常。
类似的传递Runnable方法还有两个,View里面的post(Runnable runnable);和Activity的runOnUiThread(Runnable runnable),也去瞄一下他们源码好了。
View的post(Runnable runnable):

这里写图片描述
看到Handler的post方法就不用多说了。

Activity的runOnUiThread(Runnable runnable):

这里写图片描述
看样子又是不用多说什么了。
所以我们平时在使用Handler的post方法(或是方法内部封装该post方法)时的正确姿势就是:不能在里面执行任何耗时操作!


后记

到此这篇文章就结束了,Handler在日常Android开发中有着十分重要的地位,懂得如何正确使用是一件十分必要的事情。最后,纪念下以前做笔记时的渣渣绘图:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/f409031mn/article/details/80868942