Handler機制在Android中是一個非常重要的知識點,在我們的平常開發中也是經常使用到的。在Android的面試中Handler機制更是必考的題目,而且題目也很單一:請說說Handler、Looper、MessageQueue之間的關係。這個問題無論是我去面試還是我面試別人,都會問到的一個問題。如果你遇到了這個問題,你只是簡單的說一下它們是什麼什麼關係,那是遠遠不夠的。這道題考察的無非就是你對Handler機制的實現和它的工作原理的瞭解。下面我們就通過Handler機制的工作流程圖和原始碼來詳細分析Handler實現細節和工作原理。
下面想看Handler的工作流程圖:(第一次畫圖,有點醜,湊合著看吧)
因為Handler的主要作用就是執行緒切換,所以在圖中我把Handler執行緒變化也畫了出來。從這張圖我們能看出幾點資訊:
1、Handler負責訊息的傳送和處理:Handler傳送訊息給MessageQueue和接收Looper返回的訊息並且處理訊息。
2、Looper負責管理MessageQueue:Looper會不斷地從MessageQueue取出訊息,交給Handler處理。
3、MessageQueue是訊息佇列(實時上它是用連結串列實現的),負責存放Handler傳送過來訊息。
4、一個Looper對應一個執行緒(自己所在的執行緒,如:執行緒B)。Looper的loop()方法執行在自己所在的執行緒(執行緒B)中,當Handler線上程A傳送一條訊息存放到MessageQueue時,Looper的loop()方法線上程B把訊息取出來,並交給Handler處理,所以Handler的處理訊息的方法是執行在Looper所在的執行緒(執行緒B)的。由於多個執行緒之間共享記憶體空間,所以Handler可以線上程A把訊息存放到MessageQueue,Looper可以線上程B把訊息取出來,一存一取之間就實現了執行緒的切換。
現在我們瞭解了Handler的工作流程和執行緒切換原理。那麼它在原始碼中又是如何去實現的呢?
從使用的角度看,我們要使用Handler首先要得到一個Handler物件,那麼我們就從最簡單的new Handler()作為入口,來分析它的原始碼。
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
//獲取Looper物件
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//獲取Looper物件的mQueue屬性,mQueue 就是MessageQueue物件。
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}複製程式碼
在Handler的構造方法中,首先通過Looper.myLooper()方法獲取當前執行緒的Looper物件,如果Looper物件為空,就丟擲異常,說當前執行緒還沒有呼叫Looper.prepare()方法。如果Looper不為空,Handler就會持有Looper的MessageQueue物件mQueue。
我們再看Looper.myLooper()和Looper.prepare()兩個方法:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//建立當前執行緒的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));
}
//獲取當前執行緒的Looper物件
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}複製程式碼
這裡有一個很關鍵的類:ThreadLocal,它一個執行緒內部的資料儲存類,通過它儲存的資料只有在它自己的執行緒才能獲取到,其他執行緒是獲取不到的。所以sThreadLocal.get()獲取的就是當前執行緒的Looper物件。在Looper.prepare()方法中我們看到了如果當前執行緒已經有Looper物件,就會丟擲異常,說一個執行緒只能建立一個Looper物件,所以Looper物件與自己所在的執行緒是相對應的。
再看Looper的構造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}複製程式碼
Looper的構造方法是私有的,外界不能直接建立Looper物件,只能通過Looper.prepare()方法建立物件並且通過Looper.myLooper()獲取物件,這就保證了一個執行緒只能有一個Looper物件。Looper.prepare()不能呼叫兩次。
在Looper的構造方法中會建立一個MessageQueue物件,這個就是負責存放訊息的訊息佇列,也就是Handler所持有的mQueue 物件。它是由Looper建立和管理的。
看完了Handler、Looper和MessageQueue物件的建立,接著看訊息的傳送:
Handler傳送訊息的方法有很多,但無論你是send一個Message還是post一個Runnable;無論你是延時傳送還是不延時傳送,最終都會呼叫Handler的enqueueMessage()方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//把this賦值給msg的target屬性,this就是Handler物件。
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//把訊息存放到MessageQueue
return queue.enqueueMessage(msg, uptimeMillis);
}複製程式碼
這裡直接把訊息存放到MessageQueue 就完事了。那麼訊息又是從哪裡被取出來的呢?
Looper裡有一個Looper.loop()方法,我們看一下它的原始碼。
public static void loop() {
final MessageQueue queue = me.mQueue;
//一個死迴圈
for (;;) {
//從MessageQueue中取出一條訊息
Message msg = queue.next();
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//把訊息交給Handler處理。
msg.target.dispatchMessage(msg);
}
}複製程式碼
從上面的程式碼中我們看到loop()會開啟一個死迴圈,不斷地從MessageQueue中取出訊息並交給Handler處理。在前面的enqueueMessage()方法中我們知道了msg.target就是傳送訊息的Handler物件。
這裡有同學可能會有疑問:上面的程式碼中明明如果(msg == null),就退出方法,為什麼我還說loop()裡面是個死迴圈呢?這是因為MessageQueue的next()方法取出訊息的時候,如果沒有訊息,next()方法會阻塞執行緒,直到MessageQueue有訊息進來,然後取出訊息並返回。所以queue.next()一般不會返回null,除非呼叫Looper的quit()或者quitSafely()方法結束訊息輪詢,queue.next()才會返回null,才會結束迴圈。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}複製程式碼
最後我們來看 一下訊息的處理:Handler的dispatchMessage(msg)方法。
public void dispatchMessage(Message msg) {
//如果Message有自己的callback,就由Message的callback處理
if (msg.callback != null) {
handleCallback(msg);
} else {
//如果Handler有自己的mCallback,就由Handler的mCallback處理
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//預設的處理訊息的方法
handleMessage(msg);
}
}複製程式碼
處理訊息的方法有三個:
1、優先順序最高的是Message自己的callback,這是一個Runnable物件,我們用Handler post一個Runnable的時候,其實就是把這個Runnable賦值個一個Message物件的callback,然後把Message物件傳送給MessageQueue。
2、優先順序第二的是Handler自己的mCallback,在我們建立一個Handler物件的使用可以傳一個Handler.Callback物件給它並實現Callback裡的handleMessage(msg)方法,作為我們的訊息處理方法。
3、優先順序最低的是Handler自己的handleMessage(msg)方法,這也是我們最常用的訊息處理方法。
到這裡我們的分析就結束了,現在你對Handler機制是不是有了深刻的認識呢。