Android中Looper的架构设计与赏析

版权声明:吴延宝原创 https://blog.csdn.net/codeyanbao/article/details/82320407

poster

0.你将获得什么?

看完本篇文章,你将搞清楚以下所有的关于Handler相关的任何问题。如果你以后在面试中碰到Handler相关的问题,相信你会给面试官眼前一亮的感觉。

  • Handler整个消息处理的架构是什么样的?
  • 什么是ThreadLocal?Looper?MessageQueue?
  • Handler的消息处理是怎么和线程关联的?
  • 在子线程中处理消息时为什么必须使用Looper.prepare()?
  • post()和sendMessage()有什么不同?

1.从使用出发

a.最常用的方式

我们最常用的是在我们的Activity或者什么组件里直接通过匿名内部类的方式使用Handler刷新UI

 1    private void doLogic() {
 2        new Thread(()->{
 3            // TODO 在异步的线程中请求数据或者其他耗时操作,请求完数据通知主线程刷新UI
 4            myHandler.sendEmptyMessage(1);
 5        }).start();
 6    }
 7
 8    Handler myHandler = new Handler() {
 9        @Override
10        public void handleMessage(Message msg) {
11            super.handleMessage(msg);
12            // TODO 数据请求到了,在这里刷新数据吧,一般会在Message中将数据带过来
13        }
14    };

上面这样做是可以实现的,但是匿名内部类会持有外部类的引用,比如如果你的外部是一个Activity,如果myHandler有一个延迟的10分钟的消息发送到MainLooper的消息队列中,在消息还未执行之前外部的Activity调用finish结束自己时,此时由于主线程Looper持有myHandler的引用,而myHandler又持有外部类Activity的引用导致Activity的内存无法释放,就会出现内存泄漏。所以推荐使用静态内部类的方式实现以避免内存泄漏的可能如下:

 1    Handler mHandler = new MyHandler();
 2    private void doLogic() {
 3        new Thread(()->{
 4            // TODO 在异步的线程中请求数据或者其他耗时操作,请求完数据通知主线程刷新UI
 5            mHandler.sendEmptyMessage(1);
 6        }).start();
 7    }
 8
 9    private static class MyHandler extends Handler{
10        @Override
11        public void handleMessage(Message msg) {
12            super.handleMessage(msg);
13            // TODO 数据请求到了,在这里刷新数据吧,一般会在Message中将数据带过来
14        }
15    }

b.在子线程中执行handMessage

上面的是在UI线程(主线程)中执行我们的消息处理,那是否可以在子线程中执行消息处理呢?当然是可以的,如下:

 1    private void doLogic() {
 2        new Thread(()->{
 3            Looper.prepare();
 4            Handler lHandler = new MyHandler(Looper.myLooper());
 5            Looper.loop();
 6            lHandler.sendEmptyMessage(1);
 7        }).start();
 8    }
 9
10    private static class MyHandler extends Handler{
11        MyHandler(Looper looper) {
12            super(looper);
13        }
14        @Override
15        public void handleMessage(Message msg) {
16            super.handleMessage(msg);
17            // TODO 此时该方法将在子线程中执行
18        }
19    }

2.Handler的架构

Handler里面有一个重要的成员变量Looper,Looper里面维护了一个MessageQueue(消息队列),当我们使用handler.post或者sendMessage相关的方法都是将消息Message放入到消息队列中。每一个线程都将拥有一个自己的Looper,是通过:

1static final ThreadLocal<Looper> sThreadLocal

实现的,顾名思义ThreadLocal是和线程绑定的。当我们有一个线程A使用sThreadLocal.set(Looper a),线程B使用sThreadLocal.set(Looper b)的方式存储,如果我们在线程B中使用sThreadLocal.get()将会得到Looper b的实例。所以我们每个线程可以拥有独立的Looper,Looper里面维护了一个消息队列,也就是每一个线程维护自己的消息队列。

当在主线程中时,在你的应用启动时系统便给我们创建了一个MainLooper存入了sThreadLocal中,所以平时我们使用Handler时,如果是在主线程中创建的,我们是不需再去创建一个Looper给Handler的,因为系统已经做了,所以当我们new Handler时,系统便将之前存入的Looper通过sThreadLoca中get出来,然后再去从对应的消息队列中读取执行。

而当我们在子线程中创建Handler时,如果直接new Handler运行时肯定会报错的,提示我们必须先调用Looper.prepare()方法,为什么呢?因为我们没有创建子线程对应的Looper放入sThreadLocal当中,而prepare方法就是new了一个Looper的实例通过sThreadLocal.set设置到当前线程的。整个建立过程类似于下图:

looper建立

也就是说,Handler创建的时候肯定会在一个线程当中(主线程或者子线程),并且创建一个Looper实例与此线程绑定(无论是系统帮我们创建或者通过prepare自己绑定),在Looper中维护一个消息队列,然后looper循环的从消息队列中读取消息执行(在消息队列所在线程执行)。这就是整个Handler的运行机制了。

3.几种发送消息的方式

通过Handler有很多种发送消息的方式:

  • post(Runable run)
  • postDelayed
  • sendEmptyMessage()
  • …等等

其实无论是通过post的方式或者send的方式,最后都是通过:

1public final boolean sendMessageDelayed(Message msg, long delayMillis)
2

我们使用post传入的Runnable实例,也是通过:

1    private static Message getPostMessage(Runnable r) {
2        Message m = Message.obtain();
3        m.callback = r;
4        return m;
5    }

通过上面的方法,将Runnable的实例转换为Message的实例,然后调用通用的方法发送到消息队列中,最终会通过下面的方式放入队列:

1    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
2        msg.target = this;
3        if (mAsynchronous) {
4            msg.setAsynchronous(true);
5        }
6        return queue.enqueueMessage(msg, uptimeMillis);
7    }

最后

由于时间和篇幅有限,上述文章讲的还是比较粗糙,也就是给大家一个抛砖引玉的作用,如果想要真正的探究Handler实现的整个细节,大家不妨多看看他们的源码,真个Handler、Looper、ThreadLocal的源码和注释全部加起来也就1900行左右,还是蛮小的。值得大家研究一下。

好了,今天就到这了。写的不好,仅供参考。

如果对你又一点点的帮助,请关注微信公众号:南京Android部落

code

猜你喜欢

转载自blog.csdn.net/codeyanbao/article/details/82320407