? Handler是如何實現(xiàn)延遲消息的城榛,這是個老生常談的問題了。
? 這里我就帶大家從源碼的角度看看态兴,并把handler各方面實現(xiàn)查漏補缺一下狠持。
????handler核心的發(fā)送消息的方法是sendMessage,有的朋友會說那post呢瞻润?
????post的話其實算是一個handler的語法糖喘垂,傳入runnable后幫助我們構建一個message。
/**
* Causes the Runnable r to be added to the message queue.
* The runnable will be run on the thread to which this handler is
* attached.
*
* @param r The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到getPostMessage里幫我們構建出一個message然后再調(diào)用sendMessageDelayed绍撞。
接下來看sendMessage正勒,類似于startActivity最終都會走到startActivityForResult一樣,handler所有發(fā)送消息的方法最終都會走到sendMessageDelayed傻铣,只是delayMillis不同而已章贞,這個delayMillis就是延時的時間。
/**
* Pushes a message onto the end of the message queue after all pending messages
* before the current time. It will be received in {@link #handleMessage},
* in the thread attached to this handler.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
然后這里會將DelayMillis加上當前開機的時間(這里可以理解就是這個time就是非洲,現(xiàn)在的時間+需要延遲的時間=實際執(zhí)行的時間)鸭限,接下來進到sendMessageAtTime方法里面
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
public boolean sendMessageAtTime(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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
其實到這里handler的任務就完成了,把message發(fā)送到messageQueue里面两踏,每個消息都會帶有一個uptimeMillis參數(shù)败京,這就是延時的時間。
接下來我們看messageQueue里面queue.enqueueMessage這個方法梦染。
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) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 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.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
首先是進行msg的一些屬性判斷赡麦,handler發(fā)出的target值必須不為空,是為了通過target值來判斷是哪個handler發(fā)過來的消息的。
? ?順便說一說 并不是所有的msg隧甚,target值都必須不為空
(handler的同步屏障就是一個target為空的msg车荔,用來優(yōu)先執(zhí)行異步方法的)
同步屏障有一個很重要的使用場所就是接受垂直同步Vsync信號,用來刷新頁面view的戚扳。因為為了保證view的流暢度忧便,所以每次刷新信號到來的時候,要把其他的任務先放一放帽借,優(yōu)先刷新頁面珠增。
接下來主要就是將這個msg根據(jù)實際執(zhí)行時間進行排序插入到queue里面(看里面的for循環(huán))。
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
好了砍艾,現(xiàn)在queue也構建完成了蒂教,假設我現(xiàn)在第一條消息就是要延遲10秒,怎么辦呢脆荷。實際走一邊咯凝垛。
假設我現(xiàn)在是looper,我要遍歷這個messageQueue蜓谋,那肯定要調(diào)用next方法梦皮。
next()方法比較長,我只貼關于延時消息的核心部分桃焕。
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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.
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);
}
………………以下省略
可以看到這里也是一個for循環(huán)遍歷隊列剑肯,核心變量就是nextPollTimeoutMillis」厶茫可以看到让网,計算出nextPollTimeoutMillis后就調(diào)用nativiePollOnce這個native方法。這里的話大概可以猜到他的運行機制师痕,因為他是根據(jù)執(zhí)行時間進行排序的溃睹,那傳入的這個nextPollTimeoutMillis應該就是休眠時間,類似于java的sleep(time)胰坟。休眠到下一次message的時候就執(zhí)行因篇。那如果我在這段時間又插入了一個新的message怎么辦,所以handler每次插入message都會喚醒線程腕铸,重新計算插入后惜犀,再走一次這個休眠流程铛碑。
nativiePollOnce這個native方法可以通過名字知道狠裹,他用的是linux中的epoll機制,具體是調(diào)用了epoll_wait這個方法汽烦。
int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);
這個epoll和select一樣都是linux的一個I/O多路復用機制涛菠,主要原理就不深入了,這里大概了解一下I/O多路復用機制和它與Select的區(qū)別就行。
Linux里的I/O多路復用機制:舉個例子就是我們釣魚的時候俗冻,為了保證可以最短的時間釣到最多的魚礁叔,我們同一時間擺放多個魚竿,同時釣魚迄薄。然后哪個魚竿有魚兒咬鉤了琅关,我們就把哪個魚竿上面的魚釣起來。這里就是把這些全部message放到這個機制里面讥蔽,那個time到了涣易,就執(zhí)行那個message。
epoll與select的區(qū)別:epoll獲取事件的時候采用空間換時間的方式冶伞,類似與事件驅動新症,有哪個事件要執(zhí)行,就通知epoll响禽,所以獲取的時間復雜度是O(1)徒爹,select的話則是只知道有事件發(fā)生了,要通過O(n)的事件去輪詢找到這個事件芋类。
僅供參考隆嗅,歡迎指正