Android Handler消息机制核心代码
Posted 涂程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Handler消息机制核心代码相关的知识,希望对你有一定的参考价值。
前言
该文主要是分析Handler消息机制的关键源码,阅读前需要对handler有一些基本的认识。这里先简要回顾一下:
基本组成
完整的消息处理机制包含四个要素:
- Message(消息):信息的载体
- MessageQueue(消息队列):用来存储消息的队列
- Looper(消息循环):负责检查消息队列中是否有消息,并负责取出消息
- Handler(发送和处理消息):把消息加入消息队列中,并负责分发和处理消息
基本使用方法
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()
方法
工作流程
其工作流程如下图所示:
从发送消息到接收消息的流程概括如下:
- 发送消息
- 消息进入消息队列
- 从消息队列里取出消息
- 消息的处理
下面就一折四个步骤分析一下相关源码:
发送消息
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
方法将消息加入队列
消息进入消息队列
入队前的准备工作
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的异常),然后设置消息发送的时间(机器系统时间)。 接下来开始执行插入的相关操作
将消息加入队列
继续看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
,它们是用来唤醒当前线程的。因为在消息取出端,当前线程会根据消息队列的状态进入阻塞状态,在插入时也要根据情况判断是否需要唤醒。
接下来就是从消息队列中取出消息了
从消息队列里取出消息
依旧是先看看准备准备工作
准备工作
在非主线程中使用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;
//...省略下文代码
}
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,下文会讲到)
- 步骤三:拿到消息后开始消息的分发
- 步骤四:回收已经分发了的消息,然后开始新一轮的循环取数据
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相关代码
}
}
分析一下代码:
- 第一步:获取到队列头部
- 第二步:判断当前消息是否为同步消息(异步消息的target为null),开启循环直到发现同步消息为止
- 第三步:判断消息是否为null,不为空执行第四步,为空执行第六步;
- 第四步:判断消息执行的时间,如果大于当前时间,给前文提到的
nextPollTimeoutMillis
赋新值(当前时间和消息执行时间的时间差),在这一步基本完成了本次循环所有的取消息操作,如果当前消息没有到达执行时间,本次循环结束,新循环开始,就会使用上文中提到的nativePollOnce(ptr, nextPollTimeoutMillis);
方法进入阻塞状态 - 第五步:从消息队列中取出需要立即执行的消息,结束整个循环并返回。
- 第六部:消息队列中没有消息,标记
nextPollTimeoutMillis
,以便下一循环进入阻塞状态
剩下的代码就基本上是IdleHandler的处理和执行了,在IdleHandler
小节里进行讲解,这里就不展开说明了。
消息的处理
还记得上文中loop
方法中的msg.target.dispatchMessage(msg);
吗? 消息就是通过dispatchMessage
方法进行分发的。其中target是msg所持有的发送它的handler的引用,它在发送消息时被赋值。 dispatchMessage
的源码如下:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
代码很简单,通过判断Message是否有Runable
来决定是调用callback还是调用handleMessage
方法,交给你定义的Handler去处理。需要注意的是,callback虽然是一个Runable
,但是它并没有调用run
方法,而是直接执行。这说明它并没有开启新的线程,就是作为一个方法使用(如果一开始Handler使用kotlin写的话,此处或许就是一个高阶函数了)。
其他关键点
上面讲完了消息处理的主流程,接下来讲一下主流程之外的关键点源码
Loop的创建
还记得上文中的说到的在非主线程中的要调用Looper.prepare()
和 Looper.loop()
方法吗?这两个方法可以理解为初始化Loop和开启loop循环,而主线程中无需这么做是因为在app启动的main方法中,framework层已经帮我们做了。我们分别来看这两个方法:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
这里首先使用了一个静态的ThreadLocal确保Loop的唯一性,同时做到线程隔离,使得一个线程有且只有一个Loop
实例。接着初始化Loop
,同时创建MessageQueue
(quitAllowed设置是否允许退出)。在这一步实现了Loop和消息队列的关联。
需要注意的是,Loop的构造方式是私有的,我们只能通过prepare
区创建,然后通过myLooper
方法去获取。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
ThreadLocal.get源码:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到,每个Thread都持有一个ThreadLocalMap
,它和HashMap使用相同的数据结构,使用ThreadLocal
作为key值,value就是Loop实例。不难发现:我们只能获取到当前线程的Loop实例。
Loop也提供了主线程中初始化的办法prepareMainLooper
,但是这个方法明确说明不允许调用,只能由系统自己调用。 这基本上就是Loop创建的关键了,也是在这里完成了Loop和消息队列以及线成之间的关联。
Handler的创建
Handler的构造函数有以下几个:
- public Handler()
- public Handler(Callback callback)
- public Handler(Looper looper)
- public Handler(Looper looper, Callback callback)
- public Handler(boolean async)
- public Handler(Callback callback, boolean async)
- public Handler(Looper looper, Callback callback, boolean async)
其中第一个和第二个已经被废弃了,实际上第1~5个构造方法都是通过调用public Handler(Callback callback, boolean async)
或public Handler(Looper looper, Callback callback, boolean async)
实现的,它们的源码如下:
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
//注意这个异常,loop不能为空的,首先要Looper.prepare();
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
以上是关于Android Handler消息机制核心代码的主要内容,如果未能解决你的问题,请参考以下文章
[Android源代码分析]Android消息机制,Handler,Message,Looper,MessageQueue