请选择 进入手机版 | 继续访问电脑版

[Android] Android Handler,Message,MessageQueue,Loper源码剖析 详解

[复制链接]
查看163 | 回复33 | 2021-9-14 05:13:52 | 显示全部楼层 |阅读模式

本文重要 是对Handler和消息循环的实现原理举行 源码分析,假如 不熟悉 Handler可以参见博文《 Android中Handler的利用 》,内里 临 Android为何以引入Handler机制以及怎样 利用 Handler做了讲解。

概括来说,Handler是Android中引入的一种让开发 者参与处理线程中消息循环的机制。我们在利用 Handler的时间 与Message打交道最多,Message是Hanlder机制向开发 职员 暴暴露 来的干系 类,可以通过Message类完成大部分操作Handler的功能。但作为程序员,我不能只知道怎么用Handler,还要知道其内部怎样 实现的。Handler的内部实现重要 涉及到如下几个类: Thread、MessageQueue和Looper。这几类之间的关系可以用如下的图来简单阐明 :

这里写图片形貌

Thread是最基础的,Looper和MessageQueue都构建在Thread之上,Handler又构建在Looper和MessageQueue之上,我们通过Handler间接地与下面这几个相对底层一点的类打交道。

MessageQueue

MessageQueue源码链接

最基础最底层的是Thread,每个线程内部都维护了一个消息队列——MessageQueue。消息队列MessageQueue,顾名思义,就是存放消息的队列(好像是废话…)。那队列中存储的消息是什么呢?假设我们在UI界面上单击了某个按钮,而此时程序又恰好 收到了某个广播变乱 ,那我们怎样 处理这两件事呢? 由于 一个线程在某一时间 只能处理一件事变 ,不能同时处理多件事变 ,以是 我们不能同时处理按钮的单击变乱 和广播变乱 ,我们只能挨个对其举行 处理,只要挨个处理就要有处理的先后次序 。 为此Android把UI界面上单击按钮的变乱 封装成了一个Message,将其放入到MessageQueue内里 去,即将单击按钮变乱 的Message入栈到消息队列中,然后再将广播变乱 的封装成以Message,也将其入栈到消息队列中。也就是说一个Message对象表示的是线程必要 处理的一件事变 ,消息队列就是一堆必要 处理的Message的池。线程Thread会依次取出消息队列中的消息,依次对其举行 处理。MessageQueue中有两个比较紧张 的方法,一个是enqueueMessage方法,一个是next方法。enqueueMessage方法用于将一个Message放入到消息队列MessageQueue中,next方法是从消息队列MessageQueue中壅闭 式地取出一个Message。在Android中,消息队列负责管理着顶级程序对象(Activity、BroadcastReceiver等)以及由其创建的全部 窗口。必要 留意 的是,消息队列不是Android平台特有的,其他的平台框架也会用到消息队列,比如微软的MFC框架等。

Looper

Looper源码链接

消息队列MessageQueue只是存储Message的地方,真正让消息队列循环起来的是Looper,这就好比消息队列MessageQueue是个水车,那么Looper就是让水车转动起来的河水,假如 没有河水,那么水车就是个静止的摆设,没有任何用处,Looper让MessageQueue动了起来,有了活力。

Looper是用来使线程中的消息循环起来的。默认环境 下当我们创建一个新的线程的时间 ,这个线程内里 是没有消息队列MessageQueue的。为了可以或许 让线程可以或许 绑定一个消息队列,我们必要 借助于Looper:起首 我们要调用Looper的prepare方法,然后调用Looper的Loop方法。典型的代码如下所示:

  1. class LooperThread extends Thread {
  2. public Handler mHandler;
  3. public void run() {
  4. Looper.prepare();
  5. mHandler = new Handler() {
  6. public void handleMessage(Message msg) {
  7. // process incoming messages here
  8. }
  9. };
  10. Looper.loop();
  11. }
  12. }
复制代码

必要 留意 的是Looper.prepare()和Looper.loop()都是在新线程的run方法内调用的,这两个方法都是静态方法。我们通过查看Looper的源码可以发现,Looper的构造函数是private的,也就是在该类的外部不能用new Looper()的情势 得到一个Looper对象。根据我们上面的形貌 ,我们知道线程Thread和Looper是一对一绑定的,也就是一个线程中最多只有一个Looper对象,这也就能表明 Looper的构造函数为什么是private的了,我们只能通过工厂方法Looper.myLooper()这个静态方法获取当火线 程所绑定的Looper。

Looper通过如下代码保存了对当火线 程的引用:

  1. static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
复制代码

以是 在Looper对象中通过sThreadLocal就可以找到其绑定的线程。ThreadLocal中有个set方法和get方法,可以通过set方法向ThreadLocal中存入一个对象,然后可以通过get方法取出存入的对象。ThreadLocal在new的时间 利用 了泛型,从上面的代码中我们可以看到此处的泛型范例 是Looper,也就是我们通过ThreadLocal的set和get方法只能写入和读取Looper对象范例 ,假如 我们调用其ThreadLocal的set方法传入一个Looper,将该Looper绑定给了该线程,相应的get就能获得该线程所绑定的Looper对象。

我们再来看一下Looper.prepare(),该方法是让Looper做好准备 ,只有Looper准备 好了之后才能调用Looper.loop()方法,Looper.prepare()的代码如下:

  1. private static void prepare(boolean quitAllowed) {
  2. if (sThreadLocal.get() != null) {
  3. throw new RuntimeException("Only one Looper may be created per thread");
  4. }
  5. sThreadLocal.set(new Looper(quitAllowed));
  6. }
复制代码

上面的代码起首 通过sThreadLocal.get()拿到线程sThreadLocal所绑定的Looper对象,由于初始环境 下sThreadLocal并没有绑定Looper,以是 第一次调用prepare方法时,sThreadLocal.get()返回null,不会抛出非常 。重点是下面的代码sThreadLocal.set(new Looper(quitAllowed)),起首 通过私有的构造函数创建了一个Looper对象的实例,然后通过sThreadLocal的set方法将该Looper绑定到sThreadLocal中。
如许 就完成了线程sThreadLocal与Looper的双向绑定:
a. 在Looper内通过sThreadLocal可以获取Looper所绑定的线程;
b.线程sThreadLocal通过sThreadLocal.get()方法可以获取该线程所绑定的Looper对象。

上面的代码实行 了Looper的构造函数,我们看一下其代码:

  1. private Looper(boolean quitAllowed) {
  2. mQueue = new MessageQueue(quitAllowed);
  3. mThread = Thread.currentThread();
  4. }
复制代码

我们可以看到在其构造函数中实例化一个消息队列MessageQueue,并将其赋值给其成员字段mQueue,如许 Looper也就与MessageQueue通过成员字段mQueue举行 了关联。

在实行 完了Looper.prepare()之后,我们就可以在外部通过调用Looper.myLooper()获取当火线 程绑定的Looper对象。
myLooper的代码如下所示:

  1. public static Looper myLooper() {
  2. return sThreadLocal.get();
  3. }
复制代码

必要 留意 的是,在一个线程中,只能调用一次Looper.prepare(),由于 在第一次调用了Looper.prepare()之后,当火线 程就已经绑定了Looper,在该线程内第二次调用Looper.prepare()方法的时间 ,sThreadLocal.get()会返回第一次调用prepare的时间 绑定的Looper,不是null,如许 就会走的下面的代码throw new RuntimeException(“Only one Looper may be created per thread”),从而抛出非常 ,告诉开发 者一个线程只能绑定一个Looper对象。

在调用了Looper.prepare()方法之后,当火线 程和Looper就举行 了双向的绑定,这时间 我们就可以调用Looper.loop()方法让消息队列循环起来了。
必要 留意 的是Looper.loop()应该在该Looper所绑定的线程中实行 。

Looper.loop()的代码如下:

  1. public static void loop() {
  2. final Looper me = myLooper();
  3. if (me == null) {
  4. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  5. }
  6. //注意下面这行
  7. final MessageQueue queue = me.mQueue;
  8. // Make sure the identity of this thread is that of the local process,
  9. // and keep track of what that identity token actually is.
  10. Binder.clearCallingIdentity();
  11. final long ident = Binder.clearCallingIdentity();
  12. //注意下面这行
  13. for (;;) {
  14. //注意下面这行
  15. Message msg = queue.next(); // might block
  16. if (msg == null) {
  17. // No message indicates that the message queue is quitting.
  18. return;
  19. }
  20. // This must be in a local variable, in case a UI event sets the logger
  21. Printer logging = me.mLogging;
  22. if (logging != null) {
  23. logging.println(">>>>> Dispatching to " + msg.target + " " +
  24. msg.callback + ": " + msg.what);
  25. }
  26. //注意下面这行
  27. msg.target.dispatchMessage(msg);
  28. if (logging != null) {
  29. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  30. }
  31. // Make sure that during the course of dispatching the
  32. // identity of the thread wasn't corrupted.
  33. final long newIdent = Binder.clearCallingIdentity();
  34. if (ident != newIdent) {
  35. Log.wtf(TAG, "Thread identity changed from 0x"
  36. + Long.toHexString(ident) + " to 0x"
  37. + Long.toHexString(newIdent) + " while dispatching to "
  38. + msg.target.getClass().getName() + " "
  39. + msg.callback + " what=" + msg.what);
  40. }
  41. msg.recycleUnchecked();
  42. }
  43. }
复制代码

上面有几行代码是关键代码:
1. final MessageQueue queue = me.mQueue;
变量me是通过静态方法myLooper()获得的当火线 程所绑定的Looper,me.mQueue是当火线 程所关联的消息队列。
2. for (;;)
我们发现for循环没有设置循环停止 的条件,以是 这个for循环是个死循环。
3. Message msg = queue.next(); // might block
我们通过消息队列MessageQueue的next方法从消息队列中取出一条消息,假如 此时消息队列中有Message,那么next方法会立即 返回该Message,假如 此时消息队列中没有Message,那么next方法就会壅闭 式地等待获取Message。
4. msg.target.dispatchMessage(msg);
msg的target属性是Handler,该代码的意思是让Message所关联的Handler通过dispatchMessage方法让Handler处理该Message,关于Handler的dispatchMessage方法将会在下面具体 先容 。

Handler

Handler源码链接

Handler是暴露给开发 者最顶层的一个类,其构建在Thread、Looper与MessageQueue之上。
Handler具有多个构造函数,署名 分别如下所示:
1. publicHandler()
2. publicHandler(Callbackcallback)
3. publicHandler(Looperlooper)
4. publicHandler(Looperlooper, Callbackcallback)
第1个和第2个构造函数都没有传递Looper,这两个构造函数都将通过调用Looper.myLooper()获取当火线 程绑定的Looper对象,然后将该Looper对象保存到名为mLooper的成员字段中。
第3个和第4个构造函数传递了Looper对象,这两个构造函数会将该Looper保存到名为mLooper的成员字段中。
第2个和第4个构造函数还传递了Callback对象,Callback是Handler中的内部接口,必要 实现其内部的handleMessage方法,Callback代码如下:

  1. public interface Callback {
  2. public boolean handleMessage(Message msg);
  3. }
复制代码

Handler.Callback是用来处理Message的一种本领 ,假如 没有传递该参数,那么就应该重写Handler的handleMessage方法,也就是说为了使得Handler可以或许 处理Message,我们有两种办法:
1. 向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage方法
2. 无需向Hanlder的构造函数传入Handler.Callback对象,但是必要 重写Handler本身的handleMessage方法
也就是说无论哪种方式,我们都得通过某种方式实现handleMessage方法,这点与Java中对Thread的计划 有异曲同工之处。
在Java中,假如 我们想利用 多线程,有两种办法:
1. 向Thread的构造函数传入一个Runnable对象,并实现Runnable的run方法
2. 无需向Thread的构造函数传入Runnable对象,但是要重写Thread本身的run方法
以是 只要用过多线程Thread,应该就对Hanlder这种必要 实现handleMessage的两种方式了然于心了。

我们知道通过sendMessageXXX系列方法可以向消息队列中添加消息,我们通过源码可以看出这些方法的调用次序 ,
sendMessage调用了sendMessageDelayed,sendMessageDelayed又调用了sendMessageAtTime。
Handler中还有一系列的sendEmptyMessageXXX方法,而这些sendEmptyMessageXXX方法在其内部又分别调用了其对应的sendMessageXXX方法。

通过以下调用关系图我们可以看的更清楚 些:

这里写图片形貌

由此可见全部 的sendMessageXXX方法和sendEmptyMessageXXX终极 都调用了sendMessageAtTime方法。

我们再来看看postXXX方法,会发现postXXX方法在其内部又调用了对应的sendMessageXXX方法,我们可以查看下sendMessage的源码:

  1. public final boolean post(Runnable r)
  2. {
  3. return sendMessageDelayed(getPostMessage(r), 0);
  4. }
复制代码

可以看到内部调用了getPostMessage方法,该方法传入一个Runnable对象,得到一个Message对象,getPostMessage的源码如下:

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

通过上面的代码我们可以看到在getPostMessage方法中,我们创建了一个Message对象,并将传入的Runnable对象赋值给Message的callback成员字段,然后返回该Message,然后在post方法中该携带有Runnable信息的Message传入到sendMessageDelayed方法中。由此我们可以看到全部 的postXXX方法内部都必要 借助sendMessageXXX方法来实现,以是 postXXX与sendMessageXXX并不是对立关系,而是postXXX依靠 sendMessageXXX,以是 postXXX方法可以通过sendMessageXXX方法向消息队列中传入消息,只不过通过postXXX方法向消息队列中传入的消息都携带有Runnable对象(Message.callback)。

我们可以通过如下关系图看清楚 postXXX系列方法与sendMessageXXX方法之间的调用关系:

这里写图片形貌

通过分别分析sendEmptyMessageXXX、postXXX方法与sendMessageXXX方法之间的关系,我们可以看到在Handler中全部 可以直接或间接向消息队列发送Message的方法终极 都调用了sendMessageAtTime方法,该方法的源码如下:

  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
  2. MessageQueue queue = mQueue;
  3. if (queue == null) {
  4. RuntimeException e = new RuntimeException(
  5. this + " sendMessageAtTime() called with no mQueue");
  6. Log.w("Looper", e.getMessage(), e);
  7. return false;
  8. }
  9. //注意下面这行代码
  10. return enqueueMessage(queue, msg, uptimeMillis);
  11. }
复制代码

该方法内部调用了enqueueMessage方法,该方法的源码如下:

  1. private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
  2. //注意下面这行代码
  3. msg.target = this;
  4. if (mAsynchronous) {
  5. msg.setAsynchronous(true);
  6. }
  7. //注意下面这行代码
  8. return queue.enqueueMessage(msg, uptimeMillis);
  9. }
复制代码

在该方法中有两件事必要 留意 :
1. msg.target = this
该代码将Message的target绑定为当前的Handler
2. queue.enqueueMessage
变量queue表示的是Handler所绑定的消息队列MessageQueue,通过调用queue.enqueueMessage(msg, uptimeMillis)我们将Message放入到消息队列中。

以是 我们通过下图可以看到完备 的方法调用次序 :

这里写图片形貌

我们在分析Looper.loop()的源码时发现,Looper不停 在不断的从消息队列中通过MessageQueue的next方法获取Message,然后通过代码msg.target.dispatchMessage(msg)让该msg所绑定的Handler(Message.target)实行 dispatchMessage方法以实现对Message的处理。
Handler的dispatchMessage的源码如下:

  1. public void dispatchMessage(Message msg) {
  2. //注意下面这行代码
  3. if (msg.callback != null) {
  4. handleCallback(msg);
  5. } else {
  6. //注意下面这行代码
  7. if (mCallback != null) {
  8. if (mCallback.handleMessage(msg)) {
  9. return;
  10. }
  11. }
  12. //注意下面这行代码
  13. handleMessage(msg);
  14. }
  15. }
复制代码

我们来分析下这段代码:

1.起首 会判定 msg.callback存不存在,msg.callback是Runnable范例 ,假如 msg.callback存在,那么阐明 该Message是通过实行 Handler的postXXX系列方法将Message放入到消息队列中的,这种环境 下会实行 handleCallback(msg), handleCallback源码如下:

  1. private static void handleCallback(Message message) {
  2. message.callback.run();
  3. }
复制代码

如许 我们我们就清楚 地看到我们实行 了msg.callback的run方法,也就是实行 了postXXX所传递的Runnable对象的run方法。

2.假如 我们不是通过postXXX系列方法将Message放入到消息队列中的,那么msg.callback就是null,代码继续往下实行 ,接着我们会判定 Handler的成员字段mCallback存不存在。mCallback是Hanlder.Callback范例 的,我们在上面提到过,在Handler的构造函数中我们可以传递Hanlder.Callback范例 的对象,该对象必要 实现handleMessage方法,假如 我们在构造函数中传递了该Callback对象,那么我们就会让Callback的handleMessage方法来处理Message。

3.假如 我们在构造函数中没有传入Callback范例 的对象,那么mCallback就为null,那么我们会调用Handler自身的hanldeMessage方法,该方法默认是个空方法,我们必要 本身 是重写实现该方法。

综上,我们可以看到Handler提供了三种途径处理Message,而且处理有前后优先级之分:起首 尝试让postXXX中传递的Runnable实行 ,其次尝试让Handler构造函数中传入的Callback的handleMessage方法处理,末了 才是让Handler自身的handleMessage方法处理Message。

一图胜千言

我们在本文讨论了Thread、MessageQueue、Looper以及Hanlder的之间的关系,我们可以通过如下一张传送带的图来更形象的明白 他们之间的关系。

这里写图片形貌

在现实 生存 的生产生存 中,存在着各种各样的传送带,传送带上面洒满了各种货物,传送带在发动机滚轮的带动下不停 在向前滚动,不断有新的货物放置在传送带的一端,货物在传送带的带动下送到另一端举行 网络 处理。

我们可以把传送带上的货物看做是一个个的Message,而承载这些货物的传送带就是装载Message的消息队列MessageQueue。传送带是靠发送机滚轮带动起来转动的,我们可以把发送机滚轮看做是Looper,而发动机的转动是必要 电源的,我们可以把电源看做是线程Thread,全部 的消息循环的统统 操作都是基于某个线程的。统统 准备 停当 ,我们只必要 按下电源开关发动机就会转动起来,这个开关就是Looper的loop方法,当我们按下开关的时间 ,我们就相称 于实行 了Looper的loop方法,此时Looper就会驱动着消息队列循环起来。

那Hanlder在传送带模子 中相称 于什么呢?我们可以将Handler看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从另一端顺着管道划出传送带。我们在传送带的一端放入货物的操作就相称 于我们调用了Handler的sendMessageXXX、sendEmptyMessageXXX或postXXX方法,这就把Message对象放入到了消息队列MessageQueue中了。当货物从传送带的另一端顺着管道划出时,我们就相称 于调用了Hanlder的dispatchMessage方法,在该方法中我们完成对Message的处理。

啰啰嗦嗦说了很多,盼望 本文对于大家明白 Android中的Handler和消息循环机制有所帮助。

到此这篇关于Android Handler,Message,MessageQueue,Loper源码分析 详解的文章就先容 到这了,更多干系 Android Handler,Message,MessageQueue,Loper内容请搜刮 脚本之家从前 的文章或继续欣赏 下面的干系 文章盼望 大家以后多多支持脚本之家!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar m12345666 | 2021-9-20 01:13:36 | 显示全部楼层
支持一下,下面的保持队形!
回复

使用道具 举报

avatar 是鹅好甜 | 2021-9-23 02:31:37 | 显示全部楼层
admin楼主,我告诉你一个你不知道的的秘密,有一个牛逼的网站,运动刷步数还是免费刷的,QQ和微信都可以刷,特别好用。访问地址:http://yd.mxswl.com 猫先森网络
回复

使用道具 举报

avatar 核桃脆 | 2021-10-4 16:12:38 | 显示全部楼层
admin楼主最近很消极啊!
回复

使用道具 举报

avatar 当当当当裤裆坦 | 2021-10-4 19:38:24 | 显示全部楼层
admin楼主的文笔不错!
回复

使用道具 举报

avatar 尘埃416 | 2021-10-7 00:33:36 | 显示全部楼层
楼上的说的很好!
回复

使用道具 举报

avatar 仙翁童子子os | 2021-10-9 02:38:03 | 显示全部楼层
支持一下!
回复

使用道具 举报

avatar 冰下的火圆 | 2021-10-9 07:04:57 | 显示全部楼层
世界末日我都挺过去了,看到admin楼主我才知道为什么上帝留我到现在!
回复

使用道具 举报

avatar 阿呜O | 2021-10-10 00:26:38 | 显示全部楼层
帖子好乱!
回复

使用道具 举报

avatar 紫罗兰的叶栏 | 2021-10-14 09:23:56 | 显示全部楼层
被admin楼主的逻辑打败了!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则