本文中的源碼基於Android 29;
一、概述
對於Android開發者而言,我們處理異步消息用的最多的也是輕車熟路的一種方式,就是使用Handler進行消息的分發和處理。但是我們在一個頁面(Activity 或者 Fragment)中可以直接使用Handler進行消息的分發和處理。實例如下:
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
//處理消息
return false;
}
});
我們一般在handleMessage()方法裏進行消息的處理,同時在需要發送消息的地方使用mHandler.sendEmptyMessage(0)發送消息即可。callback回調是怎麼收到消息的呢?同時Handler又是怎麼發送消息的呢?我們帶着問題看一下源碼。
Android開發者們都知道:當APP啓動時,會默認產生一個主線程(也就是UI線程),這個線程會關聯一個消息隊列,然後所有的操作都會被封裝成消息後在主線程中處理。那麼到底Android中消息機制是什麼樣子的呢?
對於Android程序而言,運行程序也是需要通過Java中的程序入口開始執行。而在ActivityThread中的就存在這麼一個main方法,作爲程序的入口。我們看一下源碼:
public static void main(String[] args) {
//省略部分代碼…
Process.setArgV0("<pre-initialized>");
//創建主線程Looper
Looper.prepareMainLooper();
//省略部分代碼…
ActivityThread thread = new ActivityThread();
thread.attach(false);
//創建主線程對應Handler
if (sMainThreadHandler == null) {//UI線程的Handler
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//開始消息輪詢
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在應用程序中,當執行ActivityThread中的main方法之後,程序就運行起來了。如上面main()方法中的代碼所示,首先是通過 Looper.prepareMainLooper();創建主線程的Looper對象,然後通過sMainThreadHandler = thread.getHandler()創建了Handler,也就是ActivityThread中的類H,該類繼承於Handler,我們在這裏不在分析。最後通過Looper.loop()方法開始了消息的輪詢。業務邏輯就這麼簡單。
二、消息輪詢器:Looper
我們通過第一部分的代碼發現對於Android消息機制而言,Looper扮演者非常重要的角色。那麼我們就像瞭解一下Looper有哪些特性吧。
首先我們先了解一下Looper的成員變量吧,mQueue是消息隊列(MessageQueue),主要用來存儲消息。
mThread:是指當前的線程,嚴格的是與Looper關聯的線程。sThreadLocal:主要用來獲取或者設置Looper對象。主要就說這三個變量吧。
下面我們說一下它的構造器:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在構造器中,初始化了消息隊列和當前線程的信息,仔細的同學可能注意到了,這裏構造器的參數quitAllowed是什麼意思呢?我們通過查看MessageQueue的源碼我們可以發現,主要用於限制能否安全清除所有消息。若爲主線程不允許使用quit方法。
接下來我們瞭解一下它的主要方法。在第一部分我們看到主線程中,首先使用了Looper.prepareMainLooper()方法創建了主線程的Looper對象。下面我們分析一下源碼:
/**
* 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();
}
}
我們看到在這裏使用了prepare(false)方法傳入了false,我們看一下源碼:
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));
}
通過以上代碼我們通過sThreadLocal獲取當前線程的Looper對象,若已經存在了則會拋出異常“Only one Looper may be created per thread”,也就是說一條線程對應一個Looper對象。若沒有調用過該方法,則會通過sThreadLocal保存一個Looper對象。我們回過頭來,prepareMainLooper()方法,通過設置quitAllowed = false,來創建一個不允許終止的Looper對象,也就是主線程是不允許調用quit方法的,這樣我們在主線程創建的Handler就可以一直進行消息輪詢了~。sMainLooper 就是主線程對應的Looper對象。
同時還有一個方法prepare(),該方法主要用於子線程創建Looper,使用Handler();同時建議在子線程中調用了prepare()方法之後需要調用loop()方法,最後以quit()方法結束本次輪詢;
最後,我們看一下消息輪詢的邏輯吧,也就是loop()方法。源碼如下:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
//獲取Looper對象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//獲取looper對象對應的消息隊列
final MessageQueue queue = me.mQueue;
//省略部分代碼...
//循環
for (;;) {
//若存在,從消息隊列中獲取消息對象,否則會導致阻塞
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//省略部分代碼...
//進行消息的分發處理
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略部分代碼...
msg.recycleUnchecked();
}
}
該方法是Handler 消息機制的核心方法,首先該方法會獲取當前線程對應的looper對象MessageQueue的對象,然後就會進入一個死循環。在該死循環中首先是通過消息隊列獲取下一條消息,也就是源碼中的“queue.next()”方法,該方法的源碼在這裏就不在多餘解釋了,它的作用我們不看源碼也能猜得出來,哈哈~,但是如果消息隊列中沒有消息,就會發生阻塞~
我們繼續向下閱讀,我們看到“msg.target.dispatchMessage(msg);”這就是消息分發的調用方法,我們知道msg就是Message的對象,那麼target又是啥啊?哈哈,看過Message源碼的同學一定都知道:它裏面有一個成員變量target,原來是Handler,現在一切都清楚了吧!我們的Handler就是這麼收到消息的。
三、紐帶:Handler
我們分析完了Looper的源碼,作爲消息的輪詢器Looper扮演着很重要的角色,也可以說是是整個消息機制的動力核心,是Looper驅動着消息的傳遞。而Handler扮演着紐帶的角色,它承載着Looper ,連接着MessageQueue,進行消息的輸入輸出,我就叫它爲紐帶吧。
在第一部分我們通過使用Callback將消息回調回頁面進行處理,我們先看一下Callback吧。
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*/
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
Callback就是Handler暴露給外部的數據接口,用於接收分發的消息,我們就可以在handleMessage()方法裏進行處理了。在第二部分中我們分析Looper的loop()方法發現通過“msg.target.dispatchMessage(msg)”方法回調回Handler,我們看一下dispatchMessage()的源碼:
/**
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在該方法裏首先是判斷msg.callback是否爲空,我們通過源碼知道callback實際上就是一個Runnable對象,若非空的話,就會執行handleCallback(msg)方法,該方法就是讓msg.callback這個線程運行起來。若msg.callback爲空的話,然後看Handler的成員變量mCallback是否爲空不爲空的話就會回調Callback的handleMessage()方法;若mCallback爲空的話就會調用Handler的handleMessage()方法。現在清楚了吧,想要處理消息要麼通過Callback回調,要麼實現Handler並重寫handleMessage()方法。這就是爲啥Handler有一個沒有方法體的handleMessage()方法。
我們再看一下消息的添加的邏輯吧。在第一部分我們就是使用mHandler.sendEmptyMessage(0)將消息添加進消息隊列的,這類的方法有以下幾個:
這類方法都會在一定的時間的情況下,將消息推送在消息隊列的底部。然後會在與該Handler關聯的線程裏的handleMessage方法下處理該消息。
還有種情況就是我們有時候會使用mHandler.post(r),這類方法一般是開了一條子線程去運行,這又是怎麼回事呢?首先這類方法有以下幾種:
我們看到該類型的方法其參數都包含Runnable參數,當我們調用這類方法時,所添加Runnable就會被添加進相應的消息隊列中,該線程最終會在Handler的dispatchMessage()方法中執行,我們知道對於Message的callback就是一條線程,當callback非空時就會運行;
總體上上述方法:
- post()和postDelayed()方法就會通過調用sendMessageDelayed()方法將線程添加進隊列,同時在延遲一定時間運行;
- 同時postAtTime()方法是調用sendMessageAtTime()方法將該線程添加進消息隊列,然後在指定的時間運行;
我們繼續查看源碼發現post(),postDelayed()和postAtTime()方法最終都是通過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(@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);
}
該方法最終通過enqueueMessage()方法將消息添加進入消息隊列,隨後就會通過Looper將消息分發下去。
postAtFrontOfQueue()方法是通過sendMessageAtFrontQueue()方法將消息添加的消息隊列,並保證下一個就會執行。然而最終也是通過enqueueMessage()方法完成的將消息添加進消息隊列的操作。關於消息添加進入消息隊列的邏輯在這裏不在分析了,感興趣的同學可以看一下MessageQueue的enqueueMessage()方法。
同時在我們的開發中多數情況下頁面是會銷燬的,這就需要銷燬一些消息,有一個方法就能解決這個問題:
removeCallbacksAndMessages()該方法提供了一種解決方法,當Object爲null是,消息隊列中的消息會被標記回收。具體可以去MessageQueue的removeCallbacksAndMessages()方法中分析。
現在我們理解了爲什麼在子線程中需要這麼使用了吧。
class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }
四、小問題
給大家留個小問題,爲什麼在Activity中就不需要創建Looper了?與君共勉~