Android 消息機制作為系統(tǒng)運行的機制之一括细,在大一點的廠子面試被問到的概率比較大,可見它的重要性戚啥。下面將分兩部分奋单,首先介紹消息的整體機制,接著聊聊消息的類型猫十。
一览濒、消息機制
在消息機制中,有下面幾個角色:
- a. Message: 消息實體
- b. MessageQueue: 消息隊列拖云,存放Message贷笛,以鏈表的方式實現(xiàn)
- c. Looper: 對MessageQueue進行循環(huán),獲取Message給Handler處理
- d. Handler: 對Message進行處理
下面從源碼的角度分析它們之間是怎么協(xié)作的
Looper:
public final class Looper {
...
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
...
}
從上述源碼可知宙项,Looper提供了兩個方法來創(chuàng)建Looper對象乏苦,并將創(chuàng)建的對象保存在sThreadLocal。
從prepare方法可以看到尤筐,每個線程只允許創(chuàng)建一個Looper對象邑贴,否則會拋異常。
而我們在主線程創(chuàng)建Handler時叔磷,則不用自己創(chuàng)建Looper拢驾,那主線程的Looper是在哪里被創(chuàng)建的呢?我們看下prepareMainLooper()方法的注釋
在 Looper 的 prepareMainLooper() 方法注釋中可以看到這樣一句話:
Initialize the current thread as a looper, marking it as an application's main looper.
The main looper for your application is created by the Android environment, so
you should never need to call this function yourself. See also: {@link #prepare()}
意思是說:將當前線程初始化為looper改基,將其標記為應用程序的主循環(huán)繁疤。您的應用程序的主要循環(huán)器是由Android環(huán)境創(chuàng)建的,永遠不應該自己調用這個函數(shù)秕狰。
由此可知稠腊,是系統(tǒng)運行時就幫我們創(chuàng)建了Looper了,可以看下ActivityThread的main方法:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
}
印證了上述的注釋鸣哀。
接下來看下里面loop()方法:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
...
for (;;) {
// 從消息隊列中取得消息架忌,具體的在下面MessageQueue進行解析
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
// target是Handler,由Handler進行處理我衬,具體看下面Handler的解析
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
...
}
}
Handler:
首先是send系列方法叹放,拿一個出來看:
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);
}
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);
}
可見最后是調用了sendMessageAtTime方法饰恕,其它的方法包括post系列的方法也一樣,最終都是調用了sendMessageAtTime井仰,不同的是最后傳入的uptimeMillis埋嵌。
然后再看下Looper調用Handler的dispatchMessage方法
/**
* Handle system messages here.
* 處理消息
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
MessageQueue:
接下來看下sendMessageAtTime里的enqueueMessage方法,此方法為將一個消息入隊俱恶。進入MessageQueue類
// msg-上面說的消息實體雹嗦, when-需要延時執(zhí)行的時長
boolean enqueueMessage(Message msg, long when) {
// 參數(shù)檢查省略
synchronized (this) {
// 檢查Looper及相應的隊列是否已經終止,是的話對msg進行回收并退出
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; // mMessages指向隊列頭部
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
// 假設隊列為空 或 msg不需要延時 或
// msg需要延時的時長小于隊頭消息的延時時長合是,
// 則將msg插入對頭
msg.next = p;
mMessages = msg;
// 標記是否需要喚醒了罪,mBlocked表示現(xiàn)在消息隊列是否處于阻塞狀態(tài)
// 阻塞的原因在下面next()方法獲取消息時再進行說明
needWake = mBlocked;
} else {
// 將msg插入在隊列的中部,插入的位置
// 根據(jù)when得到聪全,when小的msg會在前面
// 這樣方便保證之后先出隊msg都是需要
// 先執(zhí)行的捶惜,從而保證在delay的時候過后執(zhí)行msg
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;
}
// 當在next()獲取msg時,假設獲取到的when大于0荔烧,
// 說明此時需要延時when后才能
// 執(zhí)行這個msg吱七,因此進行了阻塞鹤竭,mBlocked=true踊餐。
// 但是這個時候有新的消息入隊并處于隊頭位置,
// 因此先于上一個隊頭消息執(zhí)行臀稚,所醒
// 以此時需要喚醒隊列吝岭,才能保證后來者需要先執(zhí)行
// 的不會因為阻塞而中斷
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
接下來看看上面提到的出隊的方法next():
Message next() {
// 條件檢查,省略
// 第一次循環(huán)的時候為-1吧寺,其它情況不會時-1
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0; // 需要進行阻塞的時間
for (;;) { // 死循環(huán)窜管,不斷獲取msg
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 當頭部消息還未到執(zhí)行時間時,
// 調用本地方法進行阻塞掛起
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) {
// 下一個消息還未到執(zhí)行時間幕帆,因此設置一個時間進行阻塞,
// 過了這個時間赖条,將喚醒
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 此時有消息到執(zhí)行時間了失乾,則設置隊列處于不阻塞狀態(tài),
// 將隊頭出隊纬乍,并返回
mBlocked = false;
if (prevMsg != null) { // 有消息屏障的情況下碱茁,將鏈表的前部分的同步消息連接到后面
prevMsg.next = msg.next;
} else { // 否則,直接將mMessages指向下一個消息即可
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 如果沒有消息仿贬,則設置阻塞時長為無限纽竣,直到被喚醒
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// 第一次循環(huán) 且 (消息隊列為空 或
// 消息隊列的第一個消息的觸發(fā)時間還沒有到)時,
// 表示處于空閑狀態(tài)
// 獲取到 IdleHandler 數(shù)量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// 沒有 IdleHandler 需要運行,循環(huán)并等待
mBlocked = true; // 設置阻塞狀態(tài)為 true
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 運行 IdleHandler蜓氨,只有第一次循環(huán)時才會運行
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);
}
}
}
// 重置 IdleHandler 的數(shù)量為 0聋袋,確保不會重復運行
// pendingIdleHandlerCount 置為 0 后,上面可以通
// 過 pendingIdleHandlerCount < 0 判斷是否是第一次循環(huán)语盈,
// 不是第一次循環(huán)則 pendingIdleHandlerCount 的值不會變舱馅,始終為 0缰泡。
pendingIdleHandlerCount = 0;
// 在執(zhí)行 IdleHandler 后刀荒,可能有新的消息插
// 入或消息隊列中的消息到了觸發(fā)時間,
// 所以將 nextPollTimeoutMillis 置為 0棘钞,表示不
// 需要阻塞缠借,重新檢查消息隊列。
nextPollTimeoutMillis = 0;
}
}
經過上面兩個方法宜猜,這里總結下消息機制的總體流程:
消息在入隊(即有新消息加入)的時候泼返,會根據(jù)delay的時間,在隊列找到合適的位置入隊姨拥,從而保證整個隊列的順序是以延遲時間從小到大排序绅喉。
- a. 當入隊的消息的delay時間比原先隊頭消息短的時候,或者隊列為空的時候叫乌,則消息會入隊在隊頭柴罐,并且當此時列表處于阻塞狀態(tài)時,則喚醒隊列憨奸;
- b. 否則入隊的位置為非隊頭革屠。
這個特性方便后續(xù)獲取消息即出隊的時候,直接出隊頭消息排宰,即是最優(yōu)先需要執(zhí)行的消息似芝。出隊時
- a. 若隊列為空,則無限長時間進行阻塞板甘;
- b. 出隊的消息要是到達執(zhí)行時間了党瓮,則出隊;
- c. 出隊的消息還沒到執(zhí)行時間盐类,則進行對應時間的阻塞麻诀。
二、消息類型(同步消息傲醉、異步消息蝇闭、消息屏障)
上面已經介紹完了Handler的機制,代碼注釋中提及了消息屏障硬毕、異步消息呻引,下面具體介紹下這部分內容。
Hander的消息類型有三種吐咳,分別是同步消息逻悠、異步消息元践、消息屏障,下面先看看使用方式童谒。
1. 同步消息
我們平常使用Handler發(fā)送的消息基本都是同步消息单旁,例如下面的代碼:
Handler handler = new Handler(Looper.getMainLooper());
// 使用方式1
handler.post(new Runnable() {
@Override
public void run() {
// do something
}
});
// 使用方式2
handler.sendEmptyMessage(0);
// 使用方式3
Message msg = Message.obtain();
msg.what = 1;
handler.sendMessage(msg);
...
2. 異步消息
使用方式如下:
Message msg = Message.obtain();
msg.what = 2;
msg.setAsynchronous(true); // 設置消息為異步消息
handler.sendMessage(msg);
3. 消息屏障
添加消息屏障的API如下:
/**
* Posts a synchronization barrier to the Looper's message queue.
*
* @return A token that uniquely identifies the barrier. This token must be
* @hide
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
該方法是@hide的,因此需要自行反射調用饥伊,添加完消息屏障后象浑,會返回這個消息的token,移除消息屏障時琅豆,需要用到這個token愉豺,具體API如下(同樣需要反射調用):
/**
* Removes a synchronization barrier.
*
* @param token The synchronization barrier token that was returned by
* {@link #postSyncBarrier}.
*
* @throws IllegalStateException if the barrier was not found.
*
* @hide
*/
public void removeSyncBarrier(int token) {
}
上面介紹了三種消息類型的大體使用方式,那它們有什么作用茫因,或者有什么區(qū)別呢蚪拦?
下面回到MessageQueue#next()的源碼中:
Message next() {
for (;;) {
synchronized (this) {
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) {
} else {
if (prevMsg != null) { // 有消息屏障的情況下冻押,將鏈表的前部分的同步消息連接到后面
prevMsg.next = msg.next;
} else { // 否則驰贷,直接將mMessages指向下一個消息即可
mMessages = msg.next;
}
}
}
}
}
}
從上面的代碼可以看出,消息屏障的作用是來阻塞消息隊列后面的同步消息洛巢,而異步消息不受消息屏障影響括袒。在無消息屏障的情況下,同步消息與異步消息無本質上的區(qū)別狼渊。