引言
源码版本:Android10
Handler的作用
做过Android的人都知道,Handler一般用于线程间通信,无论是在Android的平常开发中,还是Android的官方源码中,都有大量使用Handler的身影。所以,Handler的具体实现原理,是作为Android开发所必不可少的一个知识,必须要掌握。
构建一个Handler
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) {
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的构造方法,我们可以看到,上面出现了mLoop、mQueen、mCallBack、mAsynchronous,那这些变量都是什么呢?
- mLooper Looper,用于为线程运行消息循环的类
- mQueen MessageQueen,包含了要发送消息的列表
- mCallBack Handler$Callback,实例化Handler时可以使用的回调接口,以避免必须实现一个自己的Handler子类。
- mAsynchronous boolean值标记发送的是同步还是异步的消息,我们需要探讨同步消息和异步消息的区别以及实现。
这样,就引出了整个消息机制所涉及到的类,Looper、MessageQueen、Handler,另外,还有一个Message类,即上面一直提到的消息。
Looper
Handler的构造方法中,通过Loop.myLoop()方法,获得了一个Looper对象。我们来看看这个Loop对象是如何获得的,跟踪源码。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
复制代码
可以看到Looper对象是调用sThreadLocal.get()方法获得的。
一般情况下,我们定义了一个全局变量,无论在子线程和主线程是都可以访问,所以可以说是线程之间共享的。Android中有一个类名为ThreadLocal
,是线程内部的存储类,保存的是当前线程私有的数据(详情见ThreadLocal实现原理)。
上面出现的sThreadLocal其实就是ThreadLocal对象。既然是通过get()方法获得的,那我们需要看一下,是那个地方进行了sThreadLocal的set()调用
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));
}
复制代码
当我们调用Looper.prepare()时,就可以给Looper内部的sThreadLocal设置一个Looper对象。
Looper是线程运行消息循环的类,其实在Android应用在启动时,整个消息机制就已经建立了,在我们应用初始化的时候,会调用到ActivityThread类的main(),即一个入口函数,在这个main方法中,就会调用Looper.prepareMainLooper(),内部其实还是调用prepare()。这样主线程的Looper对象就创建好了。
Looper.loop()
重要的Looper.loop()方法,真正消息机制开始启动,就必须调用Looper.loop(),loop()方法把消息通信的基本框架完全展示出来了。
下面我们来看一下它的实现
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
Message msg = queue.next();
...
msg.target.dispatchMessage(msg)
}
}
复制代码
主线程中,myLooper()返回的Looper对象,就是我们在应用启动时,初始化好的Looper对象,该Looper对象里,持有了一个MessageQueen,通过循环调用queue.next(),获取消息msg,最终调用msg.target.dispatchMessage(msg)处理消息。
quene是MessageQueen对象,它包含了要发送的消息列表,每次调用next,其实是取出对应时间需要执行的Message对象。现在我们可以想象,MessageQueue 中有一大堆需要处理的消息对象,我们在循环中,每次调用queue.next(),都会给我们当前需要执行的消息,而真正处理消息的是msg.target,msg.traget其实就是我们创建的handler对象,我们到时候会在Handler的handlerMessage(Message)中收到消息,然后做出对应的处理。
其实消息机制整个流程就是这样,但是还有很多细节,我们并不知道。所以可以先在这里提出几个问题,以便我们能更好的分析代码。
- 消息是如何发送的,并且如何正确的保存到MessageQueen当中的呢?
- 消息是如何取出的,Looper.loop()方法,是一个死循环,为什么不会造成ANR?
- 系统也是通过Handler来进行进程内部通信的,那我们使用Handler会不会影响系统的任务执行呢?
消息的发送
无论是通过Handler.post(runnable),Handler.postDelay(runnable,delayMillis)还是Handler.sendMessage(msg).最终都会调用一个方法Handler的sendMessageAtTime(msg,uptimeMillis)。
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);
}
复制代码
这个方法就是存消息的,当你要执行一个Runnable的时候,内部会帮你构建一个Message(Handler.getPostMessage(runnable)),只是会把Message的Callback使用我们的Runnble进行赋值。如果直接是一个Message,就不需要包装一下。uptimeMillis其实是任务执行的时间,即何时执行这个任务或者处理这个消息,updateMillis会等于SystemClock.uptimeMillis() + delayTime。一般即时消息,delayTime是0,updateMillis会等于SystemClock.uptimeMillis()
SystemClock.uptimeMillis()是什么时间呢?其实这个时间呢,就是你开机了多久时间,单位是毫秒。其实是一个相对时间。
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);
}
复制代码
可以看到,消息的target设置成了this,即Handler对象本身。剩下的就是通过quene对象进行添加消息。
msg.setAsynchronous(true)这个代码的作用我们稍后再讲,一般我们使用的时候,mAsynchronous是false,所以也进不了这个判断里面。
往MessageQueen中添加消息
下面是MessageQueen的enqueqeMessaege()方法,看下这个方法
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) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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);
}
}
return true;
}
复制代码
现在我们主要分析这个方法。
前面是两个判断,一个是判断msg.target,我们设置的消息,msg.target是当前的Handler对象,所以肯定不为空。并且msg.isInUse()也是false,消息只有被添加到队列中,才会标记为正在使用。msg.when = when,设置消息执行的时间。Message其实是一个单链表结构,mMessages是链表的表头。Message链表中的元素是通过任务执行的时间进行排序的,从小到大进行排序。如果链表不为空的话,那表头的那个Message其实就是下一个需要处理的Message。
所以当mMessages=null、when=0(Message的when是有一个基础值的,使用的是系统开机时间,这个值一般不等于0)、when<p.when(新添加进来的消息比表头的Message执行时间还要更近),出现上述情况,我们都需要更新链表的表头。
并且我们还需要更新一下needWake的值,因为当消息队列为空时,取消息的方法是阻塞的(阻塞的原因也可能是消息未到执行时间),需要在存消息的时候唤醒一下。这也是为什么Android的进程内通信是事件驱动机制,这个我们也稍后在分析MessageQueen.next()的时候再说。
如果不用给链表一个新的head,就通过代码中的for循环,根据已有的Message的when,把我们刚新增的Message的添加进去。进而又是一个有序的Message链表。
往MessageQueen中取出消息
终于看到取Message的方法了
Message next() {
...//省略
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
...//省略
nativePollOnce(ptr, nextPollTimeoutMillis);
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;
}
...//省略 处理IdlerHandler的逻辑
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
复制代码
阻塞的实现
这里面的next方法调用时机上面已经讲过了,就是Loop.loop()方法中,通过一个死循环进行调用。next()方法中,nativePollOnce(ptr, nextPollTimeoutMillis)方法被首先调用,这是一个native方法,它实际的作用就是用来阻塞的。
为什么要把方法阻塞?
主线程的Loop.loop()方法中,存在了一个死循环,如果MessageQueen的next()不是阻塞的,直接会造成程序的ANR。实际上,主线程不会直接运行结束退出,也是因为这个死循环。
其实我们追踪这个native方法,在android_os_MessageQueue.cpp文件中,可以通过调用关系发现android_os_MessageQueue_nativePollOnce()->pollOnce()->pollInner(),在pollInner()中,有一个epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)方法,通过这个方法来实现阻塞的。
epoll_wait()是通过epoll监听文件描述符的写入事件来实现的。
- nextPollTimeoutMillis = -1时,会一直阻塞
- nextPollTimeoutMillis = 0时,不会阻塞,会立即返回
- nextPollTimeoutMillis > 0 时,阻塞nextPollTimeoutMillis毫秒(这是一个阻塞的超时时长),如果这期间被唤醒了,也会立即返回。
与之对应的是nativeWake方法
追踪这个方法,也可以根据调用关系发现android_os_MessageQueue_nativeWake()->wake()->write(mWakeEventFd, &inc, sizeof(uint64_t)。通过epoll监听的文件写入指定值,这时候epoll监听到事件,进而被唤醒了。
异步消息以及Barrier
看完阻塞之后,我们查看第一个if判断,判断msg!=null,并且msg.target==null,我们正常添加的消息msg.target是Handler对象,不会等于null。那什么情况下,msg.target会等于null。
其实当我们添加同步屏障的时候,就会添加一个msg.target=null的消息。
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
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) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
复制代码
同步屏障的作用是什么呢?
同步屏障其实是在消息队列中设置一个屏障,当链表的表头是一个同步屏障消息,那么会遍历整个消息队列,不理会同步消息,把最近的异步消息取出来,进而让这个异步消息能够更优先执行。
异步消息和同步消息的区别?
还记得我们在Handler.enqueueMessage()方法中,添加消息时,有一个判断
if (mAsynchronous) {
msg.setAsynchronous(true);
}
复制代码
当mAsynchronous的值为true时,这个Handler发送的消息,都是异步消息。默认的Handler空参构造,mAsynchronous的默认值为false,所以一般情况下,我们所发送的消息,都是同步消息。
另外,如果当检测到屏障时,如果找不到异步消息,那next方法会一直阻塞。除非移除屏障消息。
在Android源码中,在ViewRootImpl中scheduleTraversals()方法中,就存在屏障消息的应用。scheduleTraversals()是遍历view树,进行视图测量、布局、绘制等操作的。系统为了尽可能保证其优先得到执行,发送的是异步消息。触发mTraversalRunnable的执行。
同步消息
如果不是同步屏障,那就取msg。判断msg的执行时间是否到了,没到的话,就设置阻塞超时,阻塞的时长=消息的处理时间 – System.uptimeMillis()。
到了的话,就取出msg(链表头),并且重新设置链表头。
消息的处理
消息的处理比较简单
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
复制代码
首先判断msg.callback是不是空的,如果通过Handler发送的是一个Runnable,那msg.callback不为空,会调用runnable.run()方法;接着判断mCallback是否为空,mCallback可以通过构造方法传递进来,一般如果调用的是空参构造,mCallback也是空的。如果上述判断都不成立,会调用Handler内部的
handleMessage(msg)方法,我们构建Handler的时候,可以构建一个Handler的子类重写handleMessage(msg)来处理消息。
子线程使用Handler
如何在子线程使用Handler,并接收消息。
子线程是没有Looper对象的,所以我们需要在子线程调用Looper.prepare(),构建一个属于该子线程的Looper对象。在这之后,才能创建Handler对象,否则会抛出一个运行时异常(在该线程没有调用Looper.prepare(),是不能够创建Handler的)。
最后调用Looper.loop()让整个轮询器工作。
IdleHandler
public static interface IdleHandler {
boolean queueIdle();
}
复制代码
在MessageQueue中分析next()方法时,贴出的源码中省略了IdleHandler的代码。我们在最后也提一下这个东西。
IdleHandler其实是一个简单的接口,MessageQueue里面有一个mPendingIdleHandlers对象,通过addIdleHandler(IdleHandler handler)方法,保存了添加进来的IdleHandler对象,当MessageQueue中消息列表为空或者需要执行的消息时间还没到,即闲置的时候,才会执行得到执行的任务。
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
复制代码
通过idler.queueIdle(),来执行任务的实际代码,最后,通过返回值Keep判断,这个idle是否还需要继续保持下去,不需要就把这个idleHandler移除掉。否则下次闲置的时候,还是会执行这个IdleHandler。
IdleHandler在源码中也有应用,例如在ActivityThread中,就有几个idleHandler的实现(GcIdler、PurgeIdler、Idler),比较好理解的就是Android应用的GC回收(GcIdler),IdleHandlder执行的就是一些必要但是不紧急的任务。