该文主要是分析Handler消息机制的关键源码,文章会从对handler有一些基本的认识开始介绍,内容详细,感兴趣的小伙伴可以参考下
阅读前需要对handler有一些基本的认识。这里先简要概述一下:
一、handler基本认识
1、基本组成
完整的消息处理机制包含四个要素:
Message
(消息):信息的载体MessageQueue
(消息队列):用来存储消息的队列Looper
(消息循环):负责检查消息队列中是否有消息,并负责取出消息Handler
(发送和处理消息):把消息加入消息队列中,并负责分发和处理消息
2、基本使用方法
Handler的简单用法如下:
Handler handler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } }; Message message = new Message(); handler.sendMessage(message);
注意在非主线程中的要调用Looper.prepare()
和 Looper.loop()
方法
3、工作流程
其工作流程如下图所示:
工作流程 从发送消息到接收消息的流程概括如下:
发送消息
消息进入消息队列
从消息队列里取出消息
消息的处理
下面就一折四个步骤分析一下相关源码:
二、发送消息
handle
有两类发送消息的方法,它们在本质上并没有什么区别:
sendXxxx()
boolean sendMessage(Message msg)
boolean sendEmptyMessage(int what)
boolean sendEmptyMessageDelayed(int what, long delayMillis)
boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
boolean sendMessageDelayed(Message msg, long delayMillis)
boolean sendMessageAtTime(Message msg, long uptimeMillis)
boolean sendMessageAtFrontOfQueue(Message msg)
postXxxx()
boolean post(Runnable r)
boolean postAtFrontOfQueue(Runnable r)
boolean postAtTime(Runnable r, long uptimeMillis)
boolean postAtTime(Runnable r, Object token, long uptimeMillis)
boolean postDelayed(Runnable r, long delayMillis)
boolean postDelayed(Runnable r, Object token, long delayMillis)
这里不分析具体的方法特性,它们最终都是通过调用sendMessageAtTime()
或者sendMessageAtFrontOfQueue
实现消息入队的操作,唯一的区别就是post
系列方法在消息发送前调用了getPostMessage
方法:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
需要注意的是:sendMessageAtTime()
再被其他sendXxx
调用时,典型用法为:
sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
若调用者没有指定延迟时间,则消息的执行时间即为当前时间,也就是立即执行。Handler所暴露的方法都遵循这种操作,除非特别指定,msg消息执行时间就为:当前时间加上延迟时间,本质上是个时间戳。当然,你也可以任意指定时间,这个时间稍后的消息插入中会用到。 代码很简单,就是讲调用者传递过来的Runnable回调赋值给message
(用处在消息处理中讲)。 sendMessageAtTime()
和sendMessageAtFrontOfQueue
方法都会通过enqueueMessage
方法实现消息的入栈:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
代码很简单,主要有以下操作:
让
message
持有发送它的Handler
的引用(这也是处理消息时能找到对应handler的关键)设置消息是否为异步消息(异步消息无须排队,通过同步屏障,插队执行)
调用
MessageQueue
的enqueueMessage
方法将消息加入队列
三、消息进入消息队列
1、入队前的准备工作
enqueueMessage
方法是消息加入到MessageQueue
的关键,下面分段来分析一下:
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } //...省略下文代码 }
这端代码很简单:判断message
的target
是否为空,为空则抛出异常。其中,target
就是上文Handler.enqueueMessage
里提到到Handler
引用。 接下来下来开始判断和处理消息
boolean enqueueMessage(Message msg, long when) { //...省略上文代码 synchronized (this) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } 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; //...省略下文代码 } //...省略下文代码 }
首先加一个同步锁,接下来所有的操作都在synchronized
代码块里运行 然后两个if语句用来处理两个异常情况:
判断当前
msg
是否已经被使用,若被使用,则排除异常;判断消息队列(
MessageQueue
)是否正在关闭,如果是,则回收消息,返回入队失败(false)给调用者,并打印相关日志
若一切正常,通过markInUse
标记消息正在使用(对应第一个if的异常),然后设置消息发送的时间(机器系统时间)。 接下来开始执行插入的相关操作
2、将消息加入队列
继续看enqueueMessage
的代码实现
boolean enqueueMessage(Message msg, long when) { //...省略上文代码 synchronized (this) { //...省略上文代码 //步骤1 Message p = mMessages; boolean needWake; //步骤2 if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); //步骤3 Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; prev.next = msg; } if (needWake) { nativeWake(mPtr); } } //步骤4 return true; }
首先说明MessageQueue
使用一个单向链表维持着消息队列的,遵循先进先出的软解。 分析上面这端代码:
第一步:mMessages
就是表头,首先取出链表头部。
第二步:一个判断语句,满足三种条件则直接将msg作为表头:
若表头为空,说明队列内没有任何消息,msg直接作为链表头部;
when == 0 说明消息要立即执行(例如
sendMessageAtFrontOfQueue
方法,但一般的发送的消息除非特别指定都是发送时的时间加上延迟时间),msg插入作为链表头部;when < p.when
,说明要插入的消息执行时间早于表头,msg插入作为链表头部。
第三步:通过循环不断的比对队列中消息的执行时间和插入消息的执行时间,遵循时间戳小的在前原则,将消息插入和合适的位置。
第四步:返回给调用者消息插入完成。
需要注意代码中的needWake
和nativeWake
,它们是用来唤醒当前线程的。因为在消息取出端,当前线程会根据消息队列的状态进入阻塞状态,在插入时也要根据情况判断是否需要唤醒。
接下来就是从消息队列中取出消息了
四、从消息队列里取出消息
依旧是先看看准备准备工作
1、准备工作
在非主线程中使用Handler,必须要做两件事
Looper.prepare()
:创建一个LoopLooper.loop()
:开启循环
我们先不管它的创建,直接分段看啊循环开始的代码:首先是一些检查和判断工作,具体细节在代码中已注释
public static void loop() { //获取loop对象 final Looper me = myLooper(); if (me == null) { //若loop为空,则抛出异常终止操作 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } if (me.mInLoop) { //loop循环重复开启 Slog.w(TAG, "Loop again would have the queued messages be executed" + " before this one completed."); } //标记当前loop已经开启 me.mInLoop = true; //获取消息队列 final MessageQueue queue = me.mQueue; //确保权限检查基于本地进程, Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); final int thresholdOverride = SystemProperties.getInt("log.looper." + Process.myUid() + "." + Thread.currentThread().getName() + ".slow", 0); boolean slowDeliveryDetected = false; //...省略下文代码 }
2、loop中的操作
接下来就是循环的正式开启,精简关键代码:
public static void loop() { //...省略上文代码 for (;;) { //步骤一 Message msg = queue.next(); if (msg == null) { //步骤二 return; } //...省略非核心代码 try { //步骤三 msg.target.dispatchMessage(msg); //... } catch (Exception exception) { //...省略非核心代码 } finally { //...省略非核心代码 } //步骤四 msg.recycleUnchecked(); } }
分步骤分析上述代码:
步骤一:从消息队列MessageQueue
中取出消息(queue.next()可能会造成阻塞,下文会讲到)
步骤二:如果消息为null,则结束循环(消息队列中没有消息并不会返回null,而是在队列关闭才会返回null,下文会讲到)
步骤三:拿到消息后开始消息的分发
步骤四:回收已经分发了的消息,然后开始新一轮的循环取数据
2.1 MessageQueue的next方法
我们先只看第一步消息的取出,其他的在稍后小节再看,queue.next()
代码较多,依旧分段来看
Message next() { //步骤一 final long ptr = mPtr; if (ptr == 0) { return null; } //步骤二 int pendingIdleHandlerCount = -1; //步骤三 int nextPollTimeoutMillis = 0; //...省略下文代码 }
第一步:如果消息循环已经退出并且已经disposed
之后,直接返回null,对应上文中Loop
通过queue.next()取消息拿到null后退出循环
第二部:初始化IdleHandler
计数器
第三部:初始化native需要用的判断条件,初始值为0,大于0表示还有消息等待处理(延时消息未到执行时间),-1则表示没有消息了。
继续分析代码:
Message next() { //...省略上文代码 for(;;){ if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); //...省略下文代码 } }
这一段比较简单:
开启一个无限循环nextPollTimeoutMillis != 0
表示消息队列里没有消息或者所有消息都没到执行时间,调用nativeBinder.flushPendingCommands()
方法,在进入阻塞之前跟内核线程发送消息,以便内核合理调度分配资源
再次调用native方法,根据nextPollTimeoutMillis
判断,当为-1时,阻塞当前线程(在新消息入队时会重新进入可运行状态),当大于0时,说明有延时消息,nextPollTimeoutMillis
会作为一个阻塞时间,也就是消息在多就后要执行。
继续看代码:
Message next() { //...省略上文代码 for(;;){ //...省略上文代码 //开启同步锁 synchronized (this) { final long now = SystemClock.uptimeMillis(); //步骤一 Message prevMsg = null; Message msg = mMessages; //步骤二 if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } //步骤三 if (msg != null) { //步骤四 if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } 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(); return msg; } } else { //步骤六 nextPollTimeoutMillis = -1; } } //...省略下文IdleHandler相关代码 } }
相关推荐
0评论