Android Handler機制系列文章整體內容如下:
- Android Handler機制1之Thread
- Android Handler機制2之ThreadLocal
- Android Handler機制3之SystemClock類
- Android Handler機制4之Looper與Handler簡介
- Android Handler機制5之Message簡介與消息對象對象池
- Android Handler機制6之MessageQueue簡介
- Android Handler機制7之消息發送
- Android Handler機制8之消息的取出與消息的其他操作
- Android Handler機制9之Handler的Native實現前奏之Linux IO多路復用
- Android Handler機制10之Handdler的Native實現Native的實現
- Android Handler機制11之Handler機制總結
- Android Handler機制12之Callable、Future和FutureTask
- Android Handler機制13之AsyncTask源碼解析
本片文章的主要內容如下:
- 1、MessageQueue簡介
- 2、MessageQueue類注釋
- 3、MessageQueue成員變量
- 4、MessageQueue的構造函數
- 5、native層代碼的初始化
- 6、IdleHandler簡介
- 7、MessageQueue中的Message分類
一、MessageQueue簡介
MessageQueue即消息隊列,這個消息隊列和上篇文章里面的Android Handler機制5之Message簡介與消息對象對象池里面的 消息對象池 可不是同一個東西。MessageQueue是一個消息隊列,Handler將Message發送到消息隊列中,消息隊列會按照一定的規則取出要執行的Message。需要注意的是Java層的MessageQueue負責處理Java的消息,native也有一個MessageQueue負責處理native的消息,本文重點是Java層,所以暫時不分析native源碼。
二、MessageQueue類注釋
Low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.
You can retrieve the MessageQueue for the current thread with Looper.myQueue().
翻譯一下:
它是一個被Looper分發、低等級的持有Message集合的類。Message并不是直接加到MessageQueue的,而是通過Handler對象和Looper關聯到一起。
我們可以通過Looper.myQueue()方法來檢索當前線程的MessageQueue
它是一個低等級的持有Messages集合的類,被Looper分發。Messages并不是直接加到MessageQueue的,而是通過Handler對象和Looper關聯到一起。我們可以通過Looper.myQueue()方法來檢索當前線程的
三、MessageQueue成員變量
// True if the message queue can be quit.
//用于標示消息隊列是否可以被關閉,主線程的消息隊列不可關閉
private final boolean mQuitAllowed;
@SuppressWarnings("unused")
// 該變量用于保存native代碼中的MessageQueue的指針
private long mPtr; // used by native code
//在MessageQueue中,所有的Message是以鏈表的形式組織在一起的,該變量保存了鏈表的第一個元素,也可以說它就是鏈表的本身
Message mMessages;
//當Handler線程處于空閑狀態的時候(MessageQueue沒有其他Message時),可以利用它來處理一些事物,該變量就是用于保存這些空閑時候要處理的事務
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// 注冊FileDescriptor以及感興趣的Events,例如文件輸入、輸出和錯誤,設置回調函數,最后
// 調用nativeSetFileDescriptorEvent注冊到C++層中,
// 當產生相應事件時,由C++層調用Java的DispathEvents,激活相應的回調函數
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
// 用于保存將要被執行的IdleHandler
private IdleHandler[] mPendingIdleHandlers;
//標示MessageQueue是否正在關閉。
private boolean mQuitting;
// Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
// 標示 MessageQueue是否阻塞
private boolean mBlocked;
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
// 在MessageQueue里面有一個概念叫做障柵,它用于攔截同步的Message,阻止這些消息被執行,
// 只有異步Message才會放行。障柵本身也是一個Message,只是它的target為null并且arg1用于區分不同的障柵,
// 所以該變量就是用于不斷累加生成不同的障柵。
private int mNextBarrierToken;
四、MessageQueue的構造函數
通過分析下圖
我們知道MessageQueue就一個構造函數
代碼在MessageQueue.java 68行
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
MessageQueue只是有一個構造函數,該構造函數是包內可見的,其內部就兩行代碼,分別是設置了MessageQueue是否可以退出和native層代碼的相關初始化
五、native層代碼的初始化
在MessageQueue的構造函數里面調用 nativeInit(),我們來看下
代碼在MessageQueue.java 61行
private native static long nativeInit();
根據Android跨進程通信IPC之3——關于"JNI"的那些事中知道,nativeInit這個native方法對應的是android_os_MessageQueue.cpp里面的android_os_MessageQueue_nativeInit(JNIEnv* , jclass )函數
代碼在android_os_MessageQueue.cpp 172 行
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
// 初始化native消息隊列
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
后面在講解流程時候的會詳細講解,這里就不如深入了。
六、IdleHandler簡介
作為Android開發者我們知道,Handler除了用于發送Message,其本身也承載著執行具體業務邏輯的責任handlerMessage(Message msg),而IdleHandler在處理業務邏輯方面和Handler一樣,不過它只會在線程空閑的時候才執行業務邏輯的處理,這些業務經常是哪些不是很緊要或者不可預期的,比如GC。
(一) IdleHandler接口
代碼在MessageQueue.java 777行
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
老規矩 先來翻譯一下接口的注釋:
回調的接口,當線程空閑的時候可以利用它來處理一些業務員
這個IdleHandler接口就一個抽象方法queueIdle,我也看一下抽象方法的注釋
當消息隊內所有的Message都執行完之后,該方法會被調用。該返回值為True的時候,IdleHandler會一直保持在消息隊列中,False則會執行完該方法后移除IdleHandler。需要注意的是,當消息隊列中還有其他Delay Message并且這些Message還沒到被執行的時間的時候,由于線程是空閑的,所以IdleHandler也可能會被執行,
從源碼可以看出IdleHandler其實就是一個簡單的回調接口,內部就一個帶返回值的方法boolean queueIdle(),在使用的時候只需要實現該接口并加入到MessageQueue中就可以了,例如
從源碼可以看出IdleHandler其實就是一個簡單的回調接口,內部就一個帶返回值的方法boolean queueIdle(),在使用的時候只需要實現該接口并加入到MessageQueue中就可以了,例如下面簡答的代碼所示
MessageQueue messageQueue = Looper.myQueue();
messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// do something.
return false;
}
});
(二) 添加IdleHandler:addIdleHandler(IdleHandler handler)
代碼在MessageQueue.java 115行
/**
* Add a new {@link IdleHandler} to this message queue. This may be
* removed automatically for you by returning false from
* {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
* invoked, or explicitly removing it with {@link #removeIdleHandler}.
*
* <p>This method is safe to call from any thread.
*
* @param handler The IdleHandler to be added.
*/
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
看先注釋:
- 添加一個新的IdleHanlder到消息隊列中,當IdleHandler的回調方法返回False的時候,該IdleHanlder在被執行后會被立即移除,你也可以通過調用removeIdleHandler(IdleHandler handler)方法來移除指定的IdleHandler。
- 在任何線程中調用該方法都是安全的。
方法內部很簡單,就是三步
- 第一步,做非空判斷
- 第二步,加一個同步鎖
- 第三步,調用mIdleHandlers.add(handler);添加 (PS:mIdleHandlers是一個ArrayList)
(四) 刪除IdleHandler:removeIdleHandler(IdleHandler handler)
代碼在MessageQueue.java
133行
/**
* Remove an {@link IdleHandler} from the queue that was previously added
* with {@link #addIdleHandler}. If the given object is not currently
* in the idle list, nothing is done.
*
* <p>This method is safe to call from any thread.
*
* @param handler The IdleHandler to be removed.
*/
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
看先注釋:
- 從消息隊列中移除一個之前添加的IdleHandler。如果該IdleHandler不存在,則什么也不做。
移除IdleHandler的方法同樣很簡單,下一步同步處理然后直接mIdleHandlers.reomve(handler)就可以了。
七、MessageQueue中的Message分類
在MessageQueue中,Message被分成3類,分別是
- 同步消息
- 異步消息
- 障柵
那我們就一次來看下:
(一) 同步消息:
正常情況下我們通過Handler發送的Message都屬于同步消息,除非我們在發送的時候執行該消息是一個異步消息。同步消息會按順序排列在隊列中,除非指定Message的執行時間,否咋Message會按順序執行。
(二) 異步消息:
想要往消息隊列中發送異步消息,我們必須在初始化Handler的時候通過構造函數public Handler(boolean async)中指定Handler是異步的,這樣Handler在講Message加入消息隊列的時候就會將Message設置為異步的。
(三) 障柵(Barrier):
障柵(Barrier) 是一種特殊的Message,它的target為null(只有障柵的target可以為null,如果我們自己視圖設置Message的target為null的話會報異常),并且arg1屬性被用作障柵的標識符來區別不同的障柵。障柵的作用是用于攔截隊列中同步消息,放行異步消息。就好像交警一樣,在道路擁擠的時候會決定哪些車輛可以先通過,這些可以通過的車輛就是異步消息。
1、添加障柵:postSyncBarrier()
代碼在MessageQueue.java
458行
/**
* Posts a synchronization barrier to the Looper's message queue.
*
* Message processing occurs as usual until the message queue encounters the
* synchronization barrier that has been posted. When the barrier is encountered,
* later synchronous messages in the queue are stalled (prevented from being executed)
* until the barrier is released by calling {@link #removeSyncBarrier} and specifying
* the token that identifies the synchronization barrier.
*
* This method is used to immediately postpone execution of all subsequently posted
* synchronous messages until a condition is met that releases the barrier.
* Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
* and continue to be processed as usual.
*
* This call must be always matched by a call to {@link #removeSyncBarrier} with
* the same token to ensure that the message queue resumes normal operation.
* Otherwise the application will probably hang!
*
* @return A token that uniquely identifies the barrier. This token must be
* passed to {@link #removeSyncBarrier} to release the barrier.
*
* @hide
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
看先注釋:
- 向Looper的消息隊列中發送一個同步的障柵(barrier)
- 如果沒有發送同步的障柵(barrier),消息處理像往常一樣該怎么處理就怎么處理。當發現遇到障柵(barrier)后,隊列中后續的同步消息會被阻塞,直到通過調用removeSyncBarrier()釋放指定的障柵(barrier)。
- 這個方法會導致立即推遲所有后續發布的同步消息,知道滿足釋放指定的障柵(barrier)。而異步消息則不受障柵(barrier)的影響,并按照之前的流程繼續處理。
- 必須使用相同的token去調用removeSyncBarrier(),來保證插入的障柵(barrier)和移除的是一個同一個,這樣可以確保消息隊列可以正常運行,否則應用程序可能會掛起。
- 返回值是障柵(barrier)的唯一標識符,持有個token去調用removeSyncBarrier()方法才能達到真正的釋放障柵(barrier)
方法內部很簡單就是調用了postSyncBarrier(SystemClock.uptimeMillis()),通過Android Handler機制3之SystemClock類,我們知道SystemClock.uptimeMillis()是手機開機到現在的時間。那我們來看下這個postSyncBarrier(long)方法
1.1、postSyncBarrier(long when)
在講解之前先補充一個知識點:MessageQueue里面的所有Message是按照時間從前往后有序排列的。后面我們講解Handler機制流程的時候會詳細說明
代碼在MessageQueue.java
462行
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;
//把消息隊列的第一個元素指向p
Message p = mMessages;
if (when != 0) {
/** 第四步 */
while (p != null && p.when <= when) {
//通過p的時間點和障柵的時間點的比較,如果比障柵的小,就把消息隊列中的消息向后移動一位(因為消息隊列中所有元素是按照時間排序的)
prev = p;
p = p.next;
}
}
/** 第五步 */
//prev != null 代表不是消息隊列的頭部,則需要考慮前面一個消息和后面的一個消息
if (prev != null) { // invariant: p == prev.next
//msg的下一個消息是p
msg.next = p;
//msg的上一個消息是msg
prev.next = msg;
} else {
//prev == null 代表是消息隊列的頭部,則只需要負責下一個消息即可
msg.next = p;
//設置自己是消息隊列的頭部
mMessages = msg;
}
/** 第六步 */
return token;
}
}
方法詳解
- 第一步 獲取障柵的唯一標示,然后自增該變量作為下一個障柵的標示,通過mNextBarrierToken ++,我們得知,這些唯一標示是從0開始,自加1的。
- 第二步 從Message消息對象池中獲取一個Message,并重置它的when和arg1。并且arg1為token的值,通過msg.markInUse()標示msg正在被使用。這里并沒有給tareget賦值。
- 第三步 創建變量pre和p為第四步做準備,其中p被賦值為mMessages,而mMessages未消息隊列的第一個元素,所以p此時就是消息隊列的第一個元素。
- 第四步 通過對 隊列中的第一個Message的when和障柵的when作比較,決定障柵在整個消息隊列中的位置,比如是放在隊列的頭部,還是隊列中第二個位置,如果障柵在頭部,則攔截后面所有的同步消息,如果在第二的位置,則會放過第一個,然后攔截剩下的消息,以此類推。
- 第五步 把msg插入到消息隊列中
- 第六步 返回token
從源碼中我們可以看出,在把障柵插入隊列的時候先通過when的比較,根據不同的情況把障柵插入到不同的位置,具體情況如下圖所示:
ps:藍色的為Message、紅色的為Barrier
當Message.when<Barrier.when,也就是第一個Message的執行時間點在障柵之前。
當Message.when>=Barrier.when,也就是第一個Message的執行時間點在障柵之后。
大家在看上面的代碼的時候有沒有注意到一個事情,就是msg這個對象的target是null,因為從始至終就沒有賦值過,這也是后面在移除障柵的時候通過判斷條件之一:是target是否為null來判斷的
2、移除障柵
代碼在MessageQueue.java
501行
/**
* 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) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
// 獲取消息隊列的第一個元素
Message p = mMessages;
//遍歷消息隊列的所有元素,直到p.targe==null并且 p.arg1==token才是我們想要的障柵
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) {
//跳過障柵,將障柵的上一個元素的next指向障柵的next
prev.next = p.next;
//因為有元素,所以不需要喚醒
needWake = false;
} else {
//如果是第一個元素,則直接下消息隊列中的第一個元素指向障柵的下一個即可
mMessages = p.next;
//如果消息隊列中的第一個元素是null則說明消息隊列中消息,所以需要喚醒
//
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
刪除障柵(barrier)的方法也很簡單,就是不斷地遍歷消息隊列(鏈表結構),直到倒找與指定的token相匹配的障柵,然后把它從隊列中移除。