概述

相信不管是出入Android,还是已开发多年的老司机们,肯定都对Android的Handler不会陌生,而它就是今天要介绍的Android消息机制中的一部分。在Android系统中,有两大特色利剑:Binder IPC机制和消息机制。Android也由大量的消息驱动方式来交互,大到四大组件的工作流程,小到异步回调更新UI等等,各处都有消息机制的存在。


角色

在对消息机制进行分析之前,先来看一下消息机制中,都含有哪些角色以及他们各自的作用又是什么:

  • Message
    消息本体,一切逻辑都围绕它来展开。
  • MessageQueue
    消息队列,管理消息的入队和出队。
  • andler
    消息机制的两端,可作为消息产生端,也可作为消息消费端。
  • Looper
    消息机制运转的动力,不断的循环执行,取出消息、分发消息。

他们之间的关系,可以通过一个简单图来表示一下:


Handler

在消息机制的四个角色中,我们经常使用和见到的就是Handler了,那就先从Handler看起。

构造

Handler有很多构造方法,但是可用开发中使用的只有如下几个:

  • Handler()
  • Handler(@Nullable Callback callback)
  • Handler(@NonNull Looper looper)
  • Handler(@NonNull Looper looper, @Nullable Callback callback)

这样来看无非是可设置两个参数:Looper和Callback。如果不指定Looper,在构造时会通过Looper.myLooper()获取当前线程的Looper,如果当前线程没有Looper那么会抛出异常。

  mLooper = Looper.myLooper();  if (mLooper == null) {      throw new RuntimeException(          "Can't create handler inside thread " + Thread.currentThread()          + " that has not called Looper.prepare()");  }

至于Callback,后面会进行分析。

作为生产者生产Message

Handler的sendEmptyMessage、sendEmptyMessageAtTime、sendEmptyMessageDelayed、sendMessage和sendMessageDelayed,最终都会调用sendMessageAtTime:

  public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {      MessageQueue queue = mQueue;      if (queue == null) {          RuntimeException e = new RuntimeException(                      this + " sendMessageAtTime() called with no mQueue");          Log.w("Looper", e.getMessage(), e);          return false;      }      return enqueueMessage(queue, msg, uptimeMillis);  }

这其中的uptimeMillis = SystemClock.uptimeMillis() + delayMillis,之所以采用SystemClock.uptimeMillis(),是因为它是已开机时间,而如果使用System.currentTimeMillis()在用户修改手机时间时,该值就会发生变化。

除了上面的sendMessageAtTime,还有一个特殊的方法sendMessageAtFrontOfQueue:

  public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {          MessageQueue queue = mQueue;          if (queue == null) {              RuntimeException e = new RuntimeException(                  this + " sendMessageAtTime() called with no mQueue");                  Log.w("Looper", e.getMessage(), e);                  return false;         }         //固定uptimeMillis为0        return enqueueMessage(queue, msg, 0);   }

可以发现这两类方法最终都会通过调用方法:

  private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,              long uptimeMillis) {      //将msg的target属性指向当前Handler      msg.target = this;      msg.workSourceUid = ThreadLocalWorkSource.getUid();      if (mAsynchronous) {          msg.setAsynchronous(true);      }      //转到MessageQueue,消息入队      return queue.enqueueMessage(msg, uptimeMillis);  }

作为消费者消费Message

当Looper在通过MessageQueue读取到下一条消息时,就会通过handler的dispatchMessage分发给目标Handler来消费这条消息:

  public void dispatchMessage(@NonNull Message msg) {      if (msg.callback != null) {          handleCallback(msg);      } else {          if (mCallback != null) {              //如果配置了Callback,就不再走Handler的handleMessage              if (mCallback.handleMessage(msg)) {                  return;              }          }          handleMessage(msg);      }   }

Looper

既然在创建Handler时需要制定或从当前线程获取Looper,那么接下来就看一下Looper。

构造

  private Looper(boolean quitAllowed) {      mQueue = new MessageQueue(quitAllowed);      mThread = Thread.currentThread();  }

从构造方法可以看出:

1.私有方法,不允许外部直接通过构造方法创建

2.初始化时会初始化MessageQueue

3.初始化时会记录当前线程

在线程中创建Looper,可以使用prepare:

  public static void prepare() {      //必须可退出      prepare(true);  }  //quitAllowed是否允许退出Looper  private static void prepare(boolean quitAllowed) {      if (sThreadLocal.get() != null) {          throw new RuntimeException("Only one Looper may be created per thread");      }      sThreadLocal.set(new Looper(quitAllowed));  }

Android系统在创建主线程Looper时,是通过prepareMainLooper:

  public static void prepareMainLooper() {      //不允许退出      prepare(false);      synchronized (Looper.class) {          if (sMainLooper != null) {              throw new IllegalStateException("The main Looper has already been prepared.");          }          sMainLooper = myLooper();      }  }

开启死循环

Looper既然是通过死循环,为消息机制提供运转动力,那么在创建Looper之后,就要适时的开启死循环:

  public static void loop() {      final Looper me = myLooper();      if (me == null) {          throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");      }      final MessageQueue queue = me.mQueue;      //省略部分代码      for (;;) {          //从queue中取消息,可能会阻塞当前线程          Message msg = queue.next();          if (msg == null) {              // 取出null,说明消息机制退出,那么跳出循环              return;          }          final Printer logging = me.mLogging;          if (logging != null) {              logging.println(">>>>> Dispatching to " + msg.target + " " +              msg.callback + ": " + msg.what);          }                  //省略部分代码                  //取到了消息,分发消息          msg.target.dispatchMessage(msg);                  //省略部分代码                  if (logging != null) {              logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);          }                  //省略部分代码                  //回收这条消息          msg.recycleUnchecked();      }  }

MessageQueue

在Looper初始化时,会初始化MessageQueue,接下来就看一它有哪些内容。

构造

  //quitAllowed是否允许退出  MessageQueue(boolean quitAllowed) {      mQuitAllowed = quitAllowed;      mPtr = nativeInit();  }

不允许退出(quitAllowed传false)会怎样:

  void quit(boolean safe) {      if (!mQuitAllowed) {          throw new IllegalStateException("Main thread not allowed to quit.");      }          //省略部分代码  }

消息入队

在前面分析Handler时,最终发送消息都会通过MessageQueue的enqueueMessage:

  boolean enqueueMessage(Message msg, long when) {      if (msg.target == null) {          //抛异常          throw new IllegalArgumentException("Message must have a target.");      }      if (msg.isInUse()) {          //抛异常          throw new IllegalStateException(msg + " This message is already in use.");      }      synchronized (this) {          if (mQuitting) {              //抛异常              IllegalStateException e = new IllegalStateException(                      msg.target + " sending message to a Handler on a dead thread");              Log.w(TAG, e.getMessage(), e);              msg.recycle();              return false;          }          msg.markInUse();          msg.when = when;          //下一个准备要分发的消息          Message p = mMessages;          boolean needWake;          if (p == null || when == 0 || when < p.when) {              //没有准备要分发的消息              //或者这条消息是sendMessageAtFrontOfQueue发送的               //或者这条消息要发送的时间比下一条要早              //那么下一条就是你了              msg.next = p;              mMessages = msg;              needWake = mBlocked;          } else {                      //省略部分代码                      }          //省略部分代码              }      return true;  }

消息出队

在Looper中,会通过死循环的方式调用queue.next()来获取下一条消息:

  Message next() {      //省略部分代码      for (;;) {                  //省略部分代码                  synchronized (this) {              final long now = SystemClock.uptimeMillis();              Message prevMsg = null;              Message msg = mMessages;                          //省略部分代码                          if (msg != null) {                  if (now < msg.when) {                                          // 省略部分代码                                          } else {                      // 取到了一个消息                      mBlocked = false;                      if (prevMsg != null) {                          prevMsg.next = msg.next;                      } else {                          mMessages = msg.next;                      }                      msg.next = null;                      if (DEBUG) Log.v(TAG, "Returning message: " + msg);                      msg.markInUse();                      //返给looper                      return msg;                  }              }                          //省略部分代码                      }      }  }

我是主线程,我要做的事很多,但有优先级机制

我们知道,在Android中,主线程不只是要完成开发者写代码逻辑需求,还要完成系统对它的指示,比如刷新页面。对于同一个Looper来说,是可以同时存在多个Handler,可以同时向Looper中发送消息,这其中既有Android系统中定义的各种Handler,又有开发者编写的Handler,那么如何才能让MessageQueue首先将系统发布的msg分发出来,能够被率先执行呢?


MessageQueue.postSyncBarrier(long when)

往消息队列头部,放入一个系统的“告示”,告知MessageQueue接下来我会发送一些优先级高的指令,务必先执行我接下来的优先指令。

  //类似于enqueueMessage,根据when合理的插入这个“告示”  //此方法被hide标记  //添加成功后会返回一个唯一的token,标识该屏障  public int postSyncBarrier() {      return postSyncBarrier(SystemClock.uptimeMillis());  }  private int postSyncBarrier(long when) {      synchronized (this) {          final int token = mNextBarrierToken++;          final Message msg = Message.obtain();          //未给该msg设置target,是“告示”的身份特征          msg.markInUse();          msg.when = when;          msg.arg1 = token;          Message prev = null;          Message p = mMessages;          if (when != 0) {              while (p != null && p.when <= when) {                  prev = p;                  p = p.next;              }          }          if (prev != null) {              msg.next = p;              prev.next = msg;          } else {              msg.next = p;              mMessages = msg;          }          return token;      }  }

再来MessageQueue是怎么识别“告示”的:

  Message next() {      //省略部分代码          for (;;) {              //省略部分代码              synchronized (this) {              final long now = SystemClock.uptimeMillis();                  Message prevMsg = null;                  Message msg = mMessages;                  if (msg != null && msg.target == null) {                      // msg.target == null 说明是系统“告示”,让我先进行优先消息的分发                      do {                          prevMsg = msg;                          msg = msg.next;                          //当找到第一个msg.isAsynchronous() = true的消息时,就会跳出循环,首先分发这个消息                      } while (msg != null && !msg.isAsynchronous());                  }                                  //省略部分代码                          }          //省略部分代码          }  }

Message.setAsynchronous(true)

通过postSyncBarrier,系统告知MessageQueue接下来先执行的事,那么哪些才是要先执行的事呢?就是通过msg.setAsynchronous(true)方法,标记为true的事。


MessageQueue.removeSyncBarrier(int token)

系统除了可以在特定的场合(如刷新屏幕)添加同步屏障,告知MessageQueue先执行特定的优先级消息之外,还可以取消同步屏障,让MessageQueue回复正常排队执行。比如本来需要刷新下一帧,但是页面在下一帧刷新时间前被关闭了,那么就移除之前的“告知”。


一些思考

没有这个“告知”,始终在MessageQueue.next中判断msg.isAsynchronous为true,那么就优先分发它行不行?

如果没有这个添加“告知”和移除“告知”的存在,那么有些消息,包括系统发出的普通消息(msg.isAsynchronous = false),就可能永远不会被执行了(开发者把所有消息都进行msg.setAsynchronous(true))。必须要在合适的时机,让queue按照时间顺序,依次执行消息的分发,而不是始终将isAsynchronous标志放在第一位。

这里放出一个问题,可以思考一下:如果将Message的setAsynchronous进行hide处理,在MessageQueue.next中始终判断msg.isAsynchronous,优先分发msg.isAsynchronous == true的消息,又是否可行呢?


MessageQueue.IdleHandler

再来回看MessageQueue.next方法:

  Message next() {  //省略部分代码  int pendingIdleHandlerCount = -1;   int nextPollTimeoutMillis = 0;  for (;;) {      if (msg != null) {          if (now < msg.when) {              //省略部分代码          }else{                      //省略部分代码                      //如果找到了要分发的msg,并且到了分发时间,那么就返回给looper              return msg;          }      }      //省略部分代码          //以下代码执行条件,是未找到下一条msg或下一条msg还未到分发时间          if (pendingIdleHandlerCount < 0                          && (mMessages == null || now < mMessages.when)) {          pendingIdleHandlerCount = mIdleHandlers.size();      }      if (pendingIdleHandlerCount <= 0) {          mBlocked = true;          continue;      }      if (mPendingIdleHandlers == null) {          mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];      }      mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);      for (int i = 0; i < pendingIdleHandlerCount; i++) {          final IdleHandler idler = mPendingIdleHandlers[i];          mPendingIdleHandlers[i] = null; // release the reference to the handler          boolean keep = false;          try {              //执行idler.queueIdle,保存返回结果              keep = idler.queueIdle();          } catch (Throwable t) {              Log.wtf(TAG, "IdleHandler threw exception", t);          }          if (!keep) {              synchronized (this) {                  //如果不保留当前idler,那么移除                  mIdleHandlers.remove(idler);              }          }      }      }  }

再来看一下接口IdleHandler:

  public static interface IdleHandler {      boolean queueIdle();  }

使用场景:

  • 在Activity绘制完成后,做一些事情
  • 结合HandlerThread, 用于单线程消息通知器

关于使用场景,更详细的内容,之后再详解。


死循环为什么不会阻塞App

阻塞App时,往往是不能在继续处理后续的逻辑,但是Android的消息机制,虽然是死循环,但是依然在有条不紊的接收和处理任务。这跟业务代码中,错误书写的一个局部小的死循环不同,正是由于存在这个死循环的存在,主线程才能一直轮询处理新的任务,保持应用的生机。

在消息机制中,取消息时,如果没有可分发消息或下一条要分发的消息还未到分发时间,就会进行适当的阻塞;在有消息传入时,会根据目标线程的阻塞状态,决定是否进行唤醒,已使其能够顺利的处理接下来的消息分发。

android return 如何跳出两个循环_关于不得不学的Android知识之消息机制相关推荐

  1. android return 如何跳出两个循环_PHP跳出循环的方法

    PHP中的循环结构大致有for循环,while循环,do{} while 循环以及foreach循环几种,不管哪种循环中,在PHP中跳出循环大致有这么几种方式: 一:exit exit是用来结束程序执 ...

  2. c++ break跳出几个循环_必须知道的C语言知识细节:break、continue语句区别

    break语句.continue语句都是C语言标准规定的跳转类语句,能够实现程序无条件转向另一处执行. break和continue中在循环体中经常出现,因此必须掌握其区别,避免出错. 先复习下两种语 ...

  3. Java:关于跳出两层循环的方法以及Label(标签)

    Java:关于跳出两层循环的方法以及Label(标签) HR曾问过我这样一个问题:Java中,如何跳出两层循环? 虽然当时没有说我的答案是错的,但也没完全认可.以下为对该问题的重新研究. 1. Lab ...

  4. python 跳出两层循环

    if __name__ == '__main__':# 外循环是从0到4,内循环是从6到9,如果内循环的j大于7时,跳出两层循环.# 使用 for...else解决,如下for i in range( ...

  5. break跳出两重循环

    用感知器算法求下列模式分类的解向量w:     ω1: {(0 0 0)T, (1 0 0)T, (1 0 1)T, (1 1 0)T} ω2: {(0 0 1)T, (0 1 1)T, (0 1 0 ...

  6. python的return语句求两数之和_程序题(python解)

    PyCharm整理代码:Ctrl+Alt+l list行数:len(list) list列数:len(list[1]) 笔试常使用到的功能: 1.单独取出一长串字符中每个单独的字符: 输入'abcd' ...

  7. python break怎么跳出两层循环(多层循环)?添加标志量

    break2 = False while (True):// 省略while (True):// 省略if xxx:break2 = Truebreakif (break2):break 参考文章1: ...

  8. Python 骚操作 之 内层for循环如何break出外层的循环(跳出两层循环)

    In [31]: for i in range(1,5):...: for j in range(5,10):...: print(i,j)...: if j==6:...: break...: el ...

  9. python 跳出两层循环,跳出多层循环

    pip install goto-statement @with_goto def range(start, stop):i = startresult = []label .beginif i == ...

最新文章

  1. 清华大学大数据研究中心“RONG”奖学金申请通知
  2. python语言1010的八进制_python打印十六进制
  3. eclipse 安装svn插件
  4. 笔记-高项案例题-2019年下-计算题
  5. keras从入门到放弃(七)多层感知器训练
  6. 1694 件 AI 事件大盘点,哪些事让你记忆深刻?
  7. 使用grub2制作U盘启动盘安装操作系统
  8. 网络工程师职业发展方向和职业前景
  9. 签署您的应用——多渠道签名打包教程
  10. 我的个人成长(1-3年)
  11. 再谈 iOS App Crash 防护
  12. Arduino使用ESP8266安装问题(包括附加开发板管理网址)
  13. 如何把一个文件夹的文件分配到多个文件夹
  14. 计算机毕业设计Android安卓旅游结伴景点评论-酒店预订系统app用户相约伴一起游玩
  15. 怎样才能掌握好计算机知识,简析怎样才能上好计算机课
  16. 录制课程用什么软件好?3款超好用的课程视频录课软件
  17. ubuntu安装cad快速看图linux版
  18. hive 已知日期计算是周几
  19. 鸿蒙系统手机模拟器,鸿蒙系统2.0手机版
  20. ESP32开发之旅——ssd1306 OLED屏的使用

热门文章

  1. 台前与幕后的 5G 战争
  2. 如何用 30 天入门年薪 30 万的技术领域?
  3. 如果没有云栖大会,中国云计算将要落后多少年?
  4. 数据量太大?用数据库水平切分搞定!
  5. Google 员工公开 Windows 10 零日漏洞隐藏 Bug!
  6. “小程序肯定会取代 App!” | 人物志
  7. 苹果 5G 掉队?!
  8. 深度学习难?学完它,拿高薪绝对不是问题
  9. 熬夜写代码,不如换女装入 GitHub 获上千 Star?
  10. 百度进军游戏;腾讯起诉抄袭者;苹果急撤 watchOS 5.1 更新 | 极客头条