【环球快播报】Handler机制实现原理总结

2023-06-14 13:26:18 来源:博客园

Handler一般用于线程间通信,如常用的子线程使用handler让主线程更新UI。那么这是怎么实现的呢?我们先把这个大问题分解成多个小问题:

post();postDelayed();sendMessage();sendEmptyMessage();等方法有什么不同?Handler为什么需要一个Looper,为什么它不能为空?Handler为什么可以做到线程间通信?postDelayed()为什么可以让线程延迟执行?

接下来带着这些疑惑去寻找答案。

post();postDelayed();sendMessage();sendEmptyMessage();等方法有什么不同?

它们最终都是调用同一个方法:sendMessageAtTime(),只是参数不同,Handler帮我们进行了一下封装。


【资料图】

先看post();postDelayed();这两个方法,查看源码可以发现,这两个方法都是调用sendMessageDelayed(Message, long)。只是post()的时间这个参数是0。代码如下:

public final boolean post(@NonNull Runnable r) {     return  sendMessageDelayed(getPostMessage(r), 0);  }public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {      return sendMessageDelayed(getPostMessage(r), delayMillis);  }private static Message getPostMessage(Runnable r) {      Message m = Message.obtain();      m.callback = r;      return m;  }

关键是将Runable任务封装成Message的这个getPostMessage()。这里并不是简单地将Runable封装成Message,这里还有一个Message回收池机制的实现。将在下文展开介绍。

再看sendMessage();sendEmptyMessage();这两个方法:

public final boolean sendMessage(@NonNull Message msg) {      return sendMessageDelayed(msg, 0);  }public final boolean sendEmptyMessage(int what){      return sendEmptyMessageDelayed(what, 0);  }public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {      Message msg = Message.obtain();      msg.what = what;      return sendMessageDelayed(msg, delayMillis);  }

可以看到这四个方法最终都是调用sendMessageDelayed():

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {      if (delayMillis < 0) {          delayMillis = 0;      }      return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  }

上面sendMessageDelayed()的实现都很简单,但需要注意的是这里使用了SystemClock.uptimeMillis(),它返回的是设备的开机时间(不包括息屏睡眠时间),这个时间和Handler的消息可以延迟触发有关。将在后面详细介绍。

Handler为什么需要一个Looper,为什么它不能为空?

因为MessageQueue是通过Looper获取到的。而Message需要通过MessageQueue来等待执行。在创建Handler时如果检测到Looper为空,将会抛出NullPointerException错误

如果不是通过构造函数传入的Looper,比如Handler#Callback,构造方法会通过Looper.myLooper()获取到当前线程的Looper。

Looper.myLooper()可以获取到当前线程的Looper是因为ThreadLocal的特性。

代码如下:

public Handler(@Nullable Callback callback, boolean async) {    mLooper = Looper.myLooper();      if (mLooper == null) {          throw new RuntimeException(              "Can"t create handler inside thread " + Thread.currentThread()                      + " that has not called Looper.prepare()");      }      mQueue = mLooper.mQueue;      mCallback = callback;      mAsynchronous = async;  }
Handler为什么可以做到线程间通信?

Handler最长使用的大概是子线程通知UI线程更新UI吧。

我们通过post();sendMessage();等方法提交一个Message时,这个Message会被放入MessageQueue。在创建Handler时会得到一个Looper,Looper会循环从MessageQueue取出Message处理,而每个Looper属于一个线程,如果该Looper是UI线程的,那Message就是在UI线程处理。

知其然亦应知其所以然。

我们从Handler#post()这个方法开始研究。在上面已经讲过,post()最终调用的是sendMessageAtTime(),这个方法首先是获取了与该Handler绑定Looper的MessageQueue对象,然后通过一些参数设置,最后执行MessageQueue#enqueueMessage()方法。

MessageQueue#enqueueMessage()

看方法名就知道这个方法主要工作是对Message排队处理:

boolean enqueueMessage(Message msg, long when) {  //...     synchronized (this) {          if (msg.isInUse()) {                throw new IllegalStateException(msg + " This message is already in use.");          }  //...        msg.markInUse();          msg.when = when;          Message p = mMessages;          boolean needWake;          if (p == null || when == 0 || when < p.when) {              msg.next = p;              mMessages = msg;              needWake = mBlocked;          } else {              Message prev;              for (;;) {                  prev = p;                  p = p.next;                  if (p == null || when < p.when) {                      break;                  }                //...            }              msg.next = p; // invariant: p == prev.next              prev.next = msg;          }       //...    }      return true;  }

我去掉了一些和排队无关的代码,上面这段代码并不难,就是以链表的形式将Message进行排列。首先判断这个Message是否正在被使用。

Message什么情况下是被使用状态呢?

其实这个也和Message回收池有些关系。我们new的对象和回收池中取出的Message默认状态是0,当Message进入MessageQueue等待处理时就是被使用状态。从MessageQueue取出,被处理完成回收的Message其状态又会被重置为0 。

回到Message排队问题,然后判断三个条件:

Message链表是否为空when == 0这种情况只有调用Handler#sendMessageAtFrontOfQueue()才会出现when < p.whenwhen表示消息将在什么时候执行,数字越小的排在前面

当其中一个条件满足时,将当前Message插入链表头部。

既然Message时怎么放入MessageQueue这块已经弄清楚了,那接着看一下Looper。看看是怎么取出Messages,又是怎么处理的

Looper

如果使用过Looper就应该知道,这是一个循环。查看这个类的注释,可以看到它提供了一个简单的用法。

Looper.prepare();Looper.loop();

主要有两个方法:

第一个在当前线程创建Looper对象并放入ThreadLocal中,第二个循环MessageQueue,取出其中的Message在当前线程处理

Looper实现在这里不进行深入,只讲一下和MessageQueue有关的。先看loop():

//Looperpublic static void loop() {      final Looper me = myLooper();      //...    for (;;) {          if (!loopOnce(me, ident, thresholdOverride)) {              return;          }      }  }private static boolean loopOnce(final Looper me,          final long ident, final int thresholdOverride) {      Message msg = me.mQueue.next(); // might block      if (msg == null) {      // No message indicates that the message queue is quitting.      return false;  }    //。。。    msg.target.dispatchMessage(msg);  //。。。    msg.recycleUnchecked();      return true;}//Handlerpublic void dispatchMessage(@NonNull Message msg) {      if (msg.callback != null) {          handleCallback(msg);      } else {          if (mCallback != null) {              if (mCallback.handleMessage(msg)) {                  return;              }          }          handleMessage(msg);      }  }

可以看到loop()里面用for写了一个死循环,它执行了loopOnce(),它是真正取出Message并执行的方法。在dispatchMessage()方法内,首先判断callback是否为空,它是Message的Runable,就是我们使用post();postDelayed();提交到Runable。然后判断Handler#Callback,是否为空,这个是在Handler构造方法传入的Callback,这里也解释了当我们实现了callback时可以跨线程通信的原因。

next()返回null会结束for循环。我们Android主线程没有消息为什么还可以继续运行?

我们创建Looper对象都是通过其静态方法来创建的,而Looper的构造方法有一个参数quitAllowed,这个参数为True时MessageQueue不会因为消息为空而退出。

private Looper(boolean quitAllowed) {      mQueue = new MessageQueue(quitAllowed);      mThread = Thread.currentThread();  }
postDelayed()为什么可以让线程延迟执行?

让我们回到上面Looper#loopOnce()这个方法,我在上面没有介绍怎么从MessageQueue取出Message就是留给这个问题的。线上关键代码:

//MessageQueueMessage next() {for (;;) {        synchronized (this) {        //获取当前的开机时间            final long now = SystemClock.uptimeMillis();            Message prevMsg = null;            Message msg = mMessages;            if (msg != null) {            //判断当前开机时间是否小于msg的开机时间            //如果为false表示这条消息应该被拿出去处理了                if (now < msg.when) {                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                } else {                    // Got a message.                    if (prevMsg != null) {                        prevMsg.next = msg.next;                    } else {                        mMessages = msg.next;                    }                    msg.next = null;                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);                    msg.markInUse();                    return msg;                }            }            //。。。        }}}

这块代码还是很多的,我去掉了和该问题无关的代码。这里先获取了当前开机时间,然后和Message的when对比,如果当前开机时间比when大,表示这条消息到了处理时间,直接return。我们使用postDelayed(Runnable,0)时,执行到sendMessageDelayed()后会加上SystemClock.uptimeMillis()最后变成when值,也就是SystemClock.uptimeMillis()+0,因此MessageQueue取出消息时,该Message会被立即执行,而延迟xxx时间是一样的道理。

Message回收池是怎么回事?

让我们回到上面展示的Looper#loopOnce()这个方法,可以看到当消息被取出来处理后,调用了msg.recycleUnchecked();回收当前Message:

void recycleUnchecked() {      // Mark the message as in use while it remains in the recycled object pool.      // Clear out all other details.    flags = FLAG_IN_USE;      what = 0;      arg1 = 0;      arg2 = 0;      obj = null;      replyTo = null;      sendingUid = UID_NONE;      workSourceUid = UID_NONE;      when = 0;      target = null;      callback = null;      data = null;        synchronized (sPoolSync) {          if (sPoolSize < MAX_POOL_SIZE) {              next = sPool;              sPool = this;              sPoolSize++;          }      }  }

这个方法会重置Message参数,然后判断当前回收池有没有达到上限,上限是50个,没有达到会把这个Message插入链表等待再次使用。

public static Message obtain() {      synchronized (sPoolSync) {          if (sPool != null) {              Message m = sPool;              sPool = m.next;              m.next = null;              m.flags = 0; // clear in-use flag              sPoolSize--;              return m;          }      }      return new Message();  }

Message.obtain()会检查回收池,如果回收池不为空,从链表头部取出一个对象并返回。在Google官方文档也能看到,Google建议我们通过Message.obtain()获取一个新的Message对象,而不是直接new。

总结

handler的这个机制是由几个类一起协作共同实现的。它们分别是:

HandlerHandler:负责协调各个类的工作,以达到这个机制的功能。LooperLooper:一个Looper对象属于一个线程,由它来管理此线程里的MessageQueue(消息队列)MessageQueueMessageQueue:消息队列,负责管理Message,以及延迟Message处理MessageMessage:用于存放需要发送的数据,将数据包装为消息对象。管理回收池等。扩展知识MessageQueue
标签:

环球速讯:626国际禁毒日丨金海街道小微“潭”法之全民禁毒共参与

2023-06-26

马来西亚空军训练事故致2死1伤 全球聚焦

2023-06-26

深圳通报百富兴大厦异响振动事件后续:房屋基础薄弱,将全面加固

2023-06-26

限娱令_关于限娱令的介绍

2023-06-26

热文:泡泡人消消乐

2023-06-26

美以涉芬太尼问题逮捕中国公民,我驻美使馆提出强烈抗议 当前速看

2023-06-26

2023年浙江高考分数线公布:普通类本科一段488分

2023-06-25

长华集团(605018.SH):收到国内车企关于新能源新车型冲焊件的定点通知书

2023-06-25

武磊感叹中日差距 日媒评价武磊|全球热消息

2023-06-25

每日观点:天津外国语大学2023年招生咨询方式汇总

2023-06-25

大戏看北京|6.26-30日文艺资讯

2023-06-25

英雄联盟手游主动装备有哪些 英雄联盟手游主动装备介绍

2023-06-25

cctv1中秋晚会节目表-cctv1中秋晚会-全球时快讯

2023-06-25

为中国式现代化提供坚实资源支撑——写在第三十三个全国土地日之际 世界今热点

2023-06-25

北京高考分数线公布:普通本科录取控制分数线448分 全球速递

2023-06-25

每日热讯!花呗临时额度二维码_花呗突然给你临时额度

2023-06-25

世界聚焦:探索丰盈美好生活,与星纪元STERRA ES一起夏日露营

2023-06-25

小米录音文件在哪个文件夹里面 小米录音文件在哪个文件夹

2023-06-25

已确认,网红大雁“大宝”突然死亡!公安介入 天天热讯

2023-06-25

我的端午 通讯

2023-06-25

成渝地区双城经济圈建设围绕“十项行动”推进“四重清单” 全球热资讯

2023-06-25

2023年“新一线城市”榜单发布,“昆明”替换“佛山”重回榜单

2023-06-25

打pp的男打女视频 打pp的故事男打女视频_全球热闻

2023-06-25

曝国家电网招聘严格,只要985,211学生,且要专业对口,非年年招_天天观点

2023-06-25

环球新消息丨山枝根(山枝根泡酒是什么作用)

2023-06-25

超级棒球明星2013闪退_超级棒球明星II

2023-06-25

俄罗斯总统普京与土耳其总统埃尔多安通电话

2023-06-24

大连足球史上今天:2012年中超阿尔滨连追2球!绝平申花(6月24日)

2023-06-24

惠州汽车站官网(惠州汽车站)

2023-06-24

河蚌吃啥(河蚌吃什么简介介绍)

2023-06-24

Copyright ©  2015-2022 南极频道网版权所有  备案号:粤ICP备2022077823号-13   联系邮箱: 317 493 128@qq.com