帶著問題找答案:
本文主要解答,handler.sendMessageDelayed()延時消息是如何實現(xiàn)的纯出?
1蚯妇、Thread、Looper暂筝、MessageQueue箩言、Handler 鐵三角關(guān)系
1.1、Looper是依附在Thread之上的焕襟。一個Thread 對一個Looper
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}
}
調(diào)用Looper.prepare()創(chuàng)建Looper后是保存在sThreadLocal中陨收。
1.2、建立起消息循環(huán),需要線程中調(diào)用Looper.loop()
[Thread]
public class HandlerThread extends Thread{
@Override
public void run() {
Looper.prepare();
Looper.loop();
}
}
1.3鸵赖、Handler 持有run起來的Looper,是MessageQueue的入口和出口务漩。
實例化Handler 需要傳入一個Looper對象
HandlerThread handerThread = new HandlerThread("demo");
Handler handler = new Handler(handerThread.getLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
1.3.1、Message 的核心屬性
Message 的核心屬性
public final class Message implements Parcelable {
public long when; //(1)消費該消息的預(yù)期時間點
Runnable callback; //(2)Handler.post()方法傳入的Runnable對象
Handler target; // (3)發(fā)送該消息的handler它褪,最終也是該Message消息的消費者饵骨;配合what執(zhí)行具體的業(yè)務(wù)操作
public int what; // what 標識具體的業(yè)務(wù)
}
- when 標識消費該消息的預(yù)期時間點
- callback 記錄Handler.post()方法傳入的Runnable對象
- target 發(fā)送該消息的handler,最終也是該Message消息的消費者
- what 區(qū)分具體的業(yè)務(wù)
1.3.2茫打、向Handler發(fā)送Message
- handler.sendMessageDelayed(Message.obtain(),1000)
- handler.sendMessage(Message.obtain())
- handler.post(Runnable { })
- handler.postDelayed(Runnable { },100)
向handler中添加消息 有多種形式:,無論是sendMessage() 還是post(),最終都會封裝一個Message對象居触,然后調(diào)用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);
}
sendMessageDelayed()
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
postDelayed()
Runnable會封裝成一個Message對象(Runnable保存Message.callBack屬性上)
然后執(zhí)行sendMessageDelayed()
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
1.3.3老赤、消費Message
Message從MessageQueue取出之后,會交給target Handler來處理,最終調(diào)用Handler.dispatchMessage()
Message msg = queue.next(); // might block
msg.target.dispatchMessage(msg);
dispatchMessage
public void dispatchMessage(@NonNull Message msg) {
//(1)Runnable != null 優(yōu)先執(zhí)行Runnable()
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
- 通過handler.post()發(fā)送的消息,Message.callBack 不為空轮洋,優(yōu)先執(zhí)行Runnable.run()
private static void handleCallback(Message message) {
message.callback.run();
}
- Handler 構(gòu)建時 若指定了mCallback 回調(diào),則由mCallback 來處理消息诗越。一般情況mCallback都為null,所以此種情況可以不考慮
- 然后Message 交由Handler.handleMessage()來處理
class Handler{
public void handleMessage(@NonNull Message msg) {}
}
通過handler.sendMessage()發(fā)送的消息砖瞧,需要復(fù)寫Handler.handleMessage()來具體處理消息
二、延時消息是如何實現(xiàn)的嚷狞。
- handler.sendMessageDelayed(Message.obtain(),1000)
- handler.postDelayed(Runnable { },100)
這就要看MessageQueue對象了块促。
2.1荣堰、入隊列
[Handler]
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);
}
Handler.enqueueMessage()方法中
- 將handler本身 賦給了msg.target
- 調(diào)用MessageQueue.queueMessage()
MessageQueue中最重要的就是兩個方法:
1.enqueueMessage向隊列中插入消息
2.next 從隊列中取出消息
我們先分析enqueueMessage:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { //排除msg.target == null 的case
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) { //排除 正在使用的msg
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; //MesssageQueue中的消息隊列 以單向鏈表的形式存在;mMessages 指向鏈表中的第一個幻速
boolean needWake;
//(1)新消息插入鏈表表頭:a、當前隊列沒有待處理的消息 或者 b竭翠、新消息msg.when = 0(這種情況一般不存在)或者 c振坚、新消息的觸發(fā)時間 早于mMessages表頭消息的觸發(fā)時間
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else { //(2)將新消息按照msg.when時間的先后順序,插入到mMessages鏈表的中間位置.使整個鏈表以時間排序斋扰。
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
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; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
//(3)若當前是阻塞狀態(tài),則喚醒next()操作
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
MessageQueue中的消息 是一個單向鏈表的形式保存的,mMessages 變量 指向鏈表的表頭渡八。Message鏈表中的元素是以觸發(fā)時間(when)為基準,從小道大排列的,when小的排在鏈表前面,優(yōu)先被處理;when大的排在鏈表的后面,延后處理传货。
2.1.1屎鳍、 enqueueMessage 主要做了兩件事情:
(1)將新來的Message消息 插入到鏈表中合適的位置。
滿足以下條件,新message會被插入到鏈表頭部
- a、當前隊列沒有待處理的消息
- b挺智、新消息msg.when = 0(這種情況一般不存在)
- c钦奋、新消息的觸發(fā)時間 早于mMessages表頭消息的觸發(fā)時間
其他情況,新message會被插入到鏈表中部合適位置。
(2)滿足條件時,喚醒隊列
if (needWake) {
nativeWake(mPtr);
}
什么時候需要喚醒隊列:
- 新消息插入鏈表頭部時,需要立即喚醒隊列
- 新消息插入鏈表中部時,一般不需要立即喚醒;但是當鏈表表頭是一個消息屏障忧饭,且先消息是一個異步消息時,才需要喚醒隊列筷畦。
2.1.2词裤、next() 從隊列中消費Message,進行分發(fā)處理
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
//自旋
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//(1)nativePollOnce 嘗試阻塞
//如果nextPollTimeoutMillis=-1汁咏,一直阻塞不會超時亚斋。
//如果nextPollTimeoutMillis=0,不會阻塞攘滩,立即返回帅刊。
//如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時)漂问,如果期間有程序喚醒會立即返回赖瞒。
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;
//(2)如果鏈表表頭是一個同步屏障消息,則跳過眾多同步消息,找到鏈表中第一個異步消息,進行分發(fā)處理
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//(3) 返回取到的Message
if (msg != null) {
//msg尚未到達觸發(fā)時間,則計算新的阻塞超時時間nextPollTimeoutMillis,下次循環(huán)觸發(fā)隊列阻塞
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.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else { //沒有找到異步消息,設(shè)置nextPollTimeoutMillis=-1,隊列阻塞
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
//(4)MessageQueue執(zhí)行了quit()栏饮,此處釋放MessageQueue
if (mQuitting) {
dispose();
return null;
}
//(5) 對已注冊的HandlerIdle回調(diào)的處理
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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 {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
next()方法大概可以描述為:從Message鏈表中取出一條Message返回;當鏈表中無消息,或者鏈表中第一個Message尚未到達觸發(fā)時間時,則阻塞next()方法磷仰。
代碼比較長袍嬉,我們分幾部分來講:
(1) nativePollOnce()
通過Native層的epoll來阻塞住當前線程
nativePollOnce(ptr, nextPollTimeoutMillis);
private native void nativePollOnce(long ptr, int timeoutMillis)
nativePollOnce根據(jù)傳入的參數(shù)nextPollTimeoutMillis 會有不同的阻塞行為
- 如果nextPollTimeoutMillis=-1,一直阻塞不會超時。
- 如果nextPollTimeoutMillis=0伺通,不會阻塞箍土,立即返回。
- 如果nextPollTimeoutMillis>0罐监,最長阻塞nextPollTimeoutMillis毫秒(超時)吴藻,如果期間有程序喚醒會立即返回。
(2)處理同步屏障消息
如果鏈表表頭是一個同步屏障消息,則跳過眾多同步消息,找到鏈表中第一個異步消息,進行分發(fā)處理
Message 分為同步消息和異步消息弓柱。
class Message{
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
}
通常我們使用Handler發(fā)消息都是同步消息沟堡,發(fā)出去之后就會在消息隊列里面排隊處理。我們都知道矢空,Android系統(tǒng)16ms會刷新一次屏幕航罗,如果主線程的消息過多,在16ms之內(nèi)沒有執(zhí)行完妇多,必然會造成卡頓或者掉幀伤哺。那怎么才能不排隊,沒有延時的處理呢者祖?這個時候就需要異步消息。在處理異步消息的時候绢彤,我們就需要同步屏障七问,讓異步消息不用排隊等候處理。
可以理解為同步屏障是一堵墻茫舶,把同步消息隊列攔住械巡,先處理異步消息,等異步消息處理完了饶氏,這堵墻就會取消讥耗,然后繼續(xù)處理同步消息。
MessageQueue里面有postSyncBarrier()可以發(fā)送同步屏障消息
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
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();
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;
}
}
值得注意的是疹启,同步屏障消息沒有target古程,普通的消息的必須有target的。
再回到MessageQueue.next() 方法
//(2)如果鏈表表頭是一個同步屏障消息,則跳過眾多同步消息,找到鏈表中第一個異步消息,進行分發(fā)處理
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
我們看到,如果鏈表表頭是一個同步屏障消息,就會遍歷鏈表,返回第一個待處理異步消息喊崖。這樣就跳過了前面眾多的同步消息挣磨。
(3) 取到的Message返回還是阻塞?
//(3) 返回取到的Message
if (msg != null) {
//msg尚未到達觸發(fā)時間,則計算新的阻塞超時時間nextPollTimeoutMillis,下次循環(huán)觸發(fā)隊列阻塞
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.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else { //沒有找到異步消息,設(shè)置nextPollTimeoutMillis=-1,隊列阻塞
// No more messages.
nextPollTimeoutMillis = -1;
}
回到next()中的代碼塊,取到待處理的message后做了如下處理
- 如果msg == null,說明鏈表中沒有消息,則nextPollTimeoutMillis = -1茁裙,下次循環(huán) 會無限阻塞。
- msg !=null,并且msg.when 符合觸發(fā)條件,則直接返回
- msg !=null,但是msg.when 尚未到達預(yù)期的觸發(fā)時間點,則重新計算nextPollTimeoutMillis节仿,下次循環(huán)進行固定時長的阻塞晤锥。
(4)mQuitting 的處理
如果調(diào)用了MessageQueue.quit() ,mQuitting = true,隊列中所有的消息都會處理后,會調(diào)用dispose 釋放MessageQueue
if (mQuitting) {
dispose();
return null;
}
(5)對已注冊的HandlerIdle回調(diào)的處理
MessageQueue可以注冊HandlerIdle監(jiān)聽,此處對注冊的HandlerIdle做回調(diào)處理。
//(5) 對已注冊的HandlerIdle回調(diào)的處理
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
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 {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
三廊宪、參考文章
https://zhuanlan.zhihu.com/p/265859150