1. Handler相关问题(二)
- 针对上一篇Handler相关问题(一)之后,篇幅原因,还有一些其他关于Handler的问题
- Handler的同步屏障
- IdelHandler的原理以及使用
- HandlerThread原理
- Handler如何实现线程的切换
2. Handler同步屏障源码分析
2.1 异步消息
- 当一个消息为异步消息时,它将不会像同步消息那样受到同步屏障的影响
- 异步消息的创建方式主要有两种,分别是
Handler构造
和message.setAsynchronous
,本质上都是调用的message.setAsynchronous
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
- Handler构造器的最后一个参数async就与异步消息有关
public Handler(@Nullable Callback callback, boolean async) {
...
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
}
- 关注这个全局变量mAsynchronous真正起作用的是handler.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);
}
- 先下个结论:当没有开启同步屏障时,异步消息和同步消息是没有区别的;而一旦开启了同步屏障,同步消息会被挡住而异步消息则会正常执行,具体后面会给出答案
2.2 同步屏障
- 一般来说,MessageQueue里面的所有Message是按时间先后顺序进行排列的
- 插入屏障消息开启了同步屏障之后,顾名思义,它将会挡住后面的所有同步消息,而异步消息将不会受到影响,无形中提高了异步消息执行的优先级,只有撤销同步屏障才会继续执行同步消息
- 同步屏障的开启方法
/**
* 发布一个同步屏障,注意这个方法必须与移除同步屏障方法removeSyncBarrier成对调用
* @hide 这个方法已经被设置成不可见了,通常只有系统的代码才可以调用,譬如系统更新UI
* 在draw/requestLayout、invalidate等很多方法都内部调用了viewRootImpl.scheduleTraversals,这个方法
* 内部就发送了一个同步屏障,当然它也是跟viewRootImpl.unscheduleTraversals成对调用的
*/
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis())
}
// 开启同步屏障的本质就是插入一个屏障消息,when默认为当前时间
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++
final Message msg = Message.obtain()
msg.markInUse()
// 构建一个屏障消息,屏障消息与普通消息的本质就是target(即handler)为null
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
}
}
@UnsupportedAppUsage
@TestApi
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
- 可以看到开启屏障这个方法现在已经不对开发者开放了(@hide),只是更新系统源码里会使用到,譬如下面ViewRootImpl中的
scheduleTraversals
方法,该方法会被draw、requestLayout、invalidate等方法中调用
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
- 知道了怎么发送和移除屏障消息,接下来看看屏障消息是如何隔离同步消息的,针对上一篇文章就知道应该是在MessageQueue的next方法中处理的,直接上相关源码
Message next() {
...
int nextPollTimeoutMillis = 0
for (
...
nativePollOnce(ptr, nextPollTimeoutMillis)
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis()
Message prevMsg = null
Message msg = mMessages
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// 当前消息是一个屏障消息,就需要获取messageQueue中所有的异步消息
do {
prevMsg = msg
msg = msg.next
// 不断遍历,直至找到异步消息或遍历完所有的消息
} while (msg != null && !msg.isAsynchronous())
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE)
} else {
// Got a message.
mBlocked = false
if (prevMsg != null) {
// 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 {
// No more messages.
nextPollTimeoutMillis = -1
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose()
return null
}
...
}
...
}
}
- 具体代码逻辑可见,当有屏障消息时,屏障消息后面的异步消息会得到执行,而同步消息会被一直过滤掉
2.3 总结
- Handler机制中的消息中的消息分为普通消息、异步消息以及屏障消息
- 同步消息就是我们普通方式发送的消息,消息默认发送的也是普通消息
- 异步消息可以通过设置
message.setAsynchronous(true)
,或者直接在Handler的构造器中将最后一个参数async设置为true
即可,通过该handler发送的消息默认都是异步消息(因为本质上是在发送消息时默认会调用message.setAsynchronous(true))
- 同步屏障是通过
handler.postSyncBarrier()
开启的,实质上就是向MessageQueue中插入一个屏蔽消息(标志是message.target=null
);开启之后需要在合适的位置通过removeSyncBarrier
关闭,即移除屏障消息。
- 开启同步屏障插入屏障消息后,MessageQueue中处于该屏障消息之后的异步消息将会不受影响,而同步消息将会被阻隔,达到一种同步屏障的功效,这也是同步消息与异步消息的唯一区别
- 开启与关闭同步屏障的方法已被设置成@hide仅供源码内部使用;源码中典型的应用就是
ViewRootImpl中的scheduleTraversals
方法,该方法会被draw、requestLayout、invalidate等方法中调用
3. IdelHandler
MessageQueue.java
public static interface IdleHandler {
boolean queueIdle();
}
- IdelHandler是MessageQueue的一个静态内部接口,当所在线程的MessageQueue所有即时消息已处理完毕,后续消息还未到执行时间或无更多消息,当前处于一个空闲状态,他内部的queueIdle将会回调
- queueIdle()需要一个boolean类型的返回值,当返回true时,将在后续所有的临时空闲状态均会回调,而返回false时,只会在处于第一次临时空闲时回调,回调一次后IdleHandler会被移除
3.1 源码分析
3.1.1 addIdleHandler、removeIdleHandler
addIdleHandler
最终是将IdleHandler接口实现类加入到ArrayList类型的mIdleHandlers集合中
removeIdleHandler
则是将IdleHandler接口实现类从mIdleHandlers集合中移除
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
3.1.2 messageQueue.next
Message next() {
...
int pendingIdleHandlerCount = -1;
for (;;) {
...
synchronized (this) {
...
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;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
3.2 IdelHandler的使用
- 应用启动时,希望一些不重要的操作延迟执行,虽然可以使用postDelayed实现,但具体的延迟时间并未可知,可使用IdelHandler在messageQueue空闲时执行
- 想要在一个 View 绘制完成之后添加其他依赖于这个 View 的 View,当然这个用
View.post()
也能实现,区别就是前者会在消息队列空闲时执行
- ActivityThread中的
handleResumeActivity
会在最后一行执行Looper.myQueue().addIdleHandler(new Idler());进行一些回收的操作
- ActivityThread中的
scheduleGcIdler()
方法调用Looper.myQueue().addIdleHandler(mGcIdler);在闲事进行GC操作
LeakCanary
进行内存泄漏检测也并不是onDestry方法执行完成后就进行垃圾回收和一些分析的,而是利用IdleHandler在空闲的时候进行这些操作的,尽量不去影响主线程的操作
4. HandlerThread原理
- 官方为我们在子线程中使用Handler、Looper提供的一个封装好了的Thread(继承自Thread类),防止出错
- 源码也就100多行,给开发者提供了一些相对应的API,下面展示下其中比较主要的方法
4.1 构造器
- HandlerThread没有无参构造,至少得设置线程名
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
4.2 run
- run方法中会自行调用Looper.prepare()以及Looper.loop(),这样就省去开发者自行处理了
- HandlerThread专门提供了一个可重写的onLooperPrepared()方法,用于开发者在Looper初始化之后,调用Looper.loop之前的一些操作
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
protected void onLooperPrepared() {
}
4.3 其他一些方法
- 还提供了包含获取HandlerThread子线程中的Looper实例的方法
handlerThread.getLooper()
,以及获取handler实例的方法handlerThread.getThreadHandler()
- 当然还封装了子线程中使用Handler/Looper的关键点:在子线程中不需要处理更多消息,即子线程维护的MessageQueue无消息时需要调用的looper.quit()使子线程退出,HandlerThread也提供了两个可供退出的方法
handlerThread.quit()
以及handlerThread.quitSafely()
本质上也是调用了looper.quit()以及looper.quitSafely()
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
4.4 总结
- HandlerThread是一个继承自Thread的线程类
- 在run方法中帮我们自动调用了Looper.prepare()以及Looper.loop(),也提供了一个可供重写的onLooperPrepared()方法,执行时间是Looper初始化之后,Looper.loop之前,可供开发者做一些需要的操作
- 提供了获取HandlerThread子线程中的Looper、Handler的方法
handlerThread.getLooper()
和handlerThread.getThreadHandler()
- 提供了该子线程MessageQueue无消息时的退出方法
handlerThread.quit()
和handlerThread.quitSafely()
5. Handler如何实现线程的切换
- 子线程中通过handler.sendMessage或者post方式发送消息,本质上调用的是messageQueue.enqueueMessage
- 发送消息后通过Looper.loop轮询消息本质上也是通过messageQueue.next获取消息
- 获取消息后会使用disptchMessage方法来回调handleMessage方法
- 综上所述:handleMessage方法执行的线程是由messageQueue/looper所在线程来决定的,也就是说最终handleMessage回调执行的线程与handler.sendMessage/post执行的线程无关,而是与messageQueue/Looper所在线一致;当使用handler向其他线程的messageQueue发送消息即可使得最终执行的回调方法handleMessage执行在其他线程,也就完成了线程之间的切换工作