android return 如何跳出两个循环_关于不得不学的Android知识之消息机制
概述
相信不管是出入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知识之消息机制相关推荐
- android return 如何跳出两个循环_PHP跳出循环的方法
PHP中的循环结构大致有for循环,while循环,do{} while 循环以及foreach循环几种,不管哪种循环中,在PHP中跳出循环大致有这么几种方式: 一:exit exit是用来结束程序执 ...
- c++ break跳出几个循环_必须知道的C语言知识细节:break、continue语句区别
break语句.continue语句都是C语言标准规定的跳转类语句,能够实现程序无条件转向另一处执行. break和continue中在循环体中经常出现,因此必须掌握其区别,避免出错. 先复习下两种语 ...
- Java:关于跳出两层循环的方法以及Label(标签)
Java:关于跳出两层循环的方法以及Label(标签) HR曾问过我这样一个问题:Java中,如何跳出两层循环? 虽然当时没有说我的答案是错的,但也没完全认可.以下为对该问题的重新研究. 1. Lab ...
- python 跳出两层循环
if __name__ == '__main__':# 外循环是从0到4,内循环是从6到9,如果内循环的j大于7时,跳出两层循环.# 使用 for...else解决,如下for i in range( ...
- 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 ...
- python的return语句求两数之和_程序题(python解)
PyCharm整理代码:Ctrl+Alt+l list行数:len(list) list列数:len(list[1]) 笔试常使用到的功能: 1.单独取出一长串字符中每个单独的字符: 输入'abcd' ...
- python break怎么跳出两层循环(多层循环)?添加标志量
break2 = False while (True):// 省略while (True):// 省略if xxx:break2 = Truebreakif (break2):break 参考文章1: ...
- 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 ...
- python 跳出两层循环,跳出多层循环
pip install goto-statement @with_goto def range(start, stop):i = startresult = []label .beginif i == ...
最新文章
- 清华大学大数据研究中心“RONG”奖学金申请通知
- python语言1010的八进制_python打印十六进制
- eclipse 安装svn插件
- 笔记-高项案例题-2019年下-计算题
- keras从入门到放弃(七)多层感知器训练
- 1694 件 AI 事件大盘点,哪些事让你记忆深刻?
- 使用grub2制作U盘启动盘安装操作系统
- 网络工程师职业发展方向和职业前景
- 签署您的应用——多渠道签名打包教程
- 我的个人成长(1-3年)
- 再谈 iOS App Crash 防护
- Arduino使用ESP8266安装问题(包括附加开发板管理网址)
- 如何把一个文件夹的文件分配到多个文件夹
- 计算机毕业设计Android安卓旅游结伴景点评论-酒店预订系统app用户相约伴一起游玩
- 怎样才能掌握好计算机知识,简析怎样才能上好计算机课
- 录制课程用什么软件好?3款超好用的课程视频录课软件
- ubuntu安装cad快速看图linux版
- hive 已知日期计算是周几
- 鸿蒙系统手机模拟器,鸿蒙系统2.0手机版
- ESP32开发之旅——ssd1306 OLED屏的使用