专栏首页中国Android研究院Android中Looper的架构设计与赏析

Android中Looper的架构设计与赏析

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设置到当前线程的。整个建立过程类似于下图:

也就是说,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部落(nj_android),作者:BobWu

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-09-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 热修复框架?我们都能做出来!

    大家都是开发,所以应该都知道有一个东西我们永远也避免不了。不错,Bug!我们在开发阶段碰到bug那还好,直接解决就是了,大不了让测试多测一轮。可是,如果我们发出...

    吴延宝
  • Java 编程技巧之数据结构

    编写代码的"老司机"也是如此,"老司机"之所以被称为"老司机",原因也是"无他,唯手熟尔"。编码过程中踩过的坑多了,获得的编码经验也就多了,总结的编码技巧也就更...

    吴延宝
  • 大图做帧动画就卡顿?不存在的!

    https://juejin.im/post/5cd240f2e51d453afb40d83a

    吴延宝
  • Handler都没搞懂,拿什么去跳槽啊?!

    做 Android 开发肯定离不开跟 Handler 打交道,它通常被我们用来做主线程与子线程之间的通信工具,而 Handler 作为 Android 中消息机...

    程序亦非猿
  • Android消息机制——Handler

    从上面可以看出,在子线程中创建Handler之前,要调用 Looper.prepare()方法,Handler创建后,还要调用 Looper.loop()方法。...

    用户1205080
  • 异步线程大师Handler(源码+图+demo+常见问题)

    Handler 机制 源码+图+常见问题+Demo 详细记录(本文内容略长,但内容较为详细,推荐Android开发者可深入观看.如有问题,欢迎指正)

    Anymarvel
  • [Android] Handler消息传递机制

    其实这块知识我都看过,但是读完这段话有些地方还是让我回想了一小会儿。想完就觉着既然回想了一遍,不如整理一篇博客出来好了。

    wOw
  • Art of Android Development Reading Notes 10

    《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制

    宅男潇涧
  • [026]Zygote中Socket通信能否替换成Binder通信?

    大家都知道App进程是AMS通过通过Socket通信通知Zygote孵化出来的,借用gityuan的图就是图中的第2步,能否用Binder通信替换Socket通...

    王小二
  • AQR最最最新 | 计量经济学应用投资失败的7个原因

    “[T]he concept of multiple regression and the linear regression model in particu...

    量化投资与机器学习微信公众号

扫码关注云+社区

领取腾讯云代金券