前言:本文針對讀者:1.零基礎的。2.看了各種教程依然毫無頭緒的。3.對 Handler 略有瞭解但是思維混沌的。對於想較為深入理解 Handler 的,本文對你幫助不大。本文將以圖文並茂的方式,帶你領略整個 Handler 的工作機制。零基礎的讀起來可能會略有障礙,建議反覆閱讀。是否是全網最清晰, ...
前言:本文針對讀者:1.零基礎的。2.看了各種教程依然毫無頭緒的。3.對 Handler 略有瞭解但是思維混沌的。對於想較為深入理解 Handler 的,本文對你幫助不大。本文將以圖文並茂的方式,帶你領略整個 Handler 的工作機制。零基礎的讀起來可能會略有障礙,建議反覆閱讀。是否是全網最清晰,還請各位看官細細品讀。
首先來看一張圖:
註:無論是否零基礎,都建議在新視窗中打開圖片或保存圖片至本地方便隨時查看。下文中將頻繁用到該圖。
一句話概括整張圖的工作流程:
Looper 調用 loop() 函數從 MessageQueue 中依次取出帶有 target 屬性的 Message 並分發給對應的 target 進行處理。
零基礎的同學肯定不知道我在說什麼,且聽我一一道來。
首先我們要知道,什麼是 Handler.簡而言之:
Handler 是 Android 系統提供的一種方便線程間進行通信的機制。我們普遍用 Handler 機制實現在子線程中更新 UI.
這裡插一句,Android 系統設計時將 UI 線程也就是主線程設計成線程不安全的以提高效率,並規定在任何子線程中不可以進行對 UI 修改的操作。想修改 UI ,必須在主線程中。此時我們子線程完成耗時的工作,想把結果呈現到 UI 上,怎麼辦呢?此時就需要用到 Handler與主線程進行通信。那 Handler 整套機制是如何工作的呢?
對 Handler 稍微有些瞭解的同學一定知道,Handler 是與 Looper 和 MessageQueue 協同工作的。下麵我將分別從 Looper, Handler 的視角從零構造一個 Handler 工作環境。
Looper 視角
1.通過在一個線程中調用 Looper.prepare() 函數
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是 Handler 中 LocalThread 類型的成員變數,你可以簡單的把他理解成一種方便線程間進行通信的容器。
2.既然創建了 Looper 那我們看看被調用 Looper 的構造函數是什麼樣的。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看出,這裡new出了Looper最重要的成員變數mQueue,它就是整個消息機制的載體MessageQueue . 此時可以通過觀察圖片看到,在一個線程中三層的 Looper 工作環境已經搭建完畢。從外到內分別是 LocalThread,Looper,MessageQueue.
3.調用 Looper 的 loop() 函數開始工作。對應圖中的鉤子,每次從消息隊列中夾一個消息出來。源代碼不需要全部都懂,只看我寫的中文註釋找到圖中對應動作即可。
public static void loop() {
final Looper me = myLooper();//myLooper(){return sThreadLocal.get();}通過容器獲得唯一的 Looper 對象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//整個 loop() 函數的核心
for (;;) {
//開始從消息隊列里鉤消息了
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
/*最重要的一行回調:如果取出的消息不空,調用消息中 target 的 dispatchMessage() 方法分發消息。
這裡的 target 就是一個 Handler,可以從 Message 的類圖看到這個成員變數。他是 Message 中最重要的成員變數。
這一步的動作對應圖中將消息鉤到 Handler 等待處理。*/
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
以上就是Looper視角的從初始化到開始消息輪詢工作的全部過程。零基礎的同學再看看“一句話概括整張圖工作流程”:
Looper 調用 loop() 函數從 MessageQueue 中依次取出帶有 target 屬性的 Message 並分發給對應的 target 進行處理。
是否有了更清晰的認識呢?
Handler 視角
在介紹 Handler 視角之前,我們首先要瞭解兩個前提,這兩個前提很重要,首先要記住然後要理解。
第一個前提:所有Handler的初始化函數都會與一個 Looper 進行關聯。所以在初始化Handler之前請確保你可以提供一個 Looper 供 Handler 關聯。
第二個前提:如你所見,圖中消息隊列中存放的都是Message類型的消息,所以無論 Handler 採用何種方式與消息隊列進行通信,最終通信的內容都會被封裝成Message類型。
1.基於以上兩個前提我們首先看一下Handler的構造函數。常用的基本就三種:
public Handler();
public Handler(Callback callback);
public Handler(Looper looper);
Callback 是 Handler 提供的一個公有介面:
public interface Callback {
public boolean handleMessage(Message msg);
}
先不用理解,稍後我會在講解dispatchMessage()函數時對他進行介紹。
有的同學可能會有疑問了:你不是說Handler所有構造函數都會與一個 Looper 進行關聯嗎?上面給出的前兩個,我也沒看出進行關聯啊?
先別急,我們以第一個空參數表的構造函數為例。先來看下源代碼是怎麼寫的。
public Handler() {
this(null, false);//調用下麵的函數。
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();//關聯 Looper. 還記得在loop()函數中如何獲得Looper對象嗎?他們調用的是同一個函數。
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");//這個異常保證了第一個前提哦。
}
mQueue = mLooper.mQueue;//關聯MessageQueue
mCallback = callback;//如果是Handler()調用過來的,mCallback就是null
mAsynchronous = async;
}
由此可以看到即使調用空的構造函數,在內部也會通過mLooper = Looper.myLooper();
自動關聯 Looper .所以我們在調用空的構造函數初始化Handler前,一定要先調用 Looper.prepare() 來把 Looper 放到 LocalThread 里。大概就是這樣一個效果:
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();
}
}
所以在一個線程中,我們可以通過這三種常見的構造函數構造許多個 Handler .但是一個線程中因為 Looper.prepare() 方法保證了一個線程中 Looper 的唯一性。這好比一個小部門中,有一個部門經理(Looper),手下眾多形態各異的員工(Handler)。
2.構造完了 Handler ,也就代表了一個 Handler 與一個 Looper 進行了關聯。通過 Handler 的名字我們可以看出,他的本質是一個處理者,處理 Looper 給他鉤過去的消息。這引申出了兩個問題:
I: 消息是怎麼進到 MessageQueue 中等待被鉤的?
II: Handler是如何處理鉤過來的消息的?
對於
I: 消息是怎麼進到 MessageQueue 中等待被鉤的?
由圖可知,以發送者身份劃分,有兩大類方式。一類是 Handler 對象調用三種常用函數進行發送。另一類是 Message 對象調用 sendToTarget() 函數。
先說Handler的三種常用函數:
public final boolean post(Runnable r);
public final boolean sendMessage(Message msg);
public final boolean sendEmptyMessage(int what);
雖然這三種函數傳入的參數類型並不統一,不過不要忘了前面說的第二個前提:
無論 Handler 採用何種方式與消息隊列進行通信,最終通信的內容都會被封裝成Message類型。
從圖中可以看到,將消息封裝成 Message 類型後會先調用 Handler 的 enqueueMessage() 方法,最終調用 MessageQueue 的 enqueueMessage() 方法入隊列。在調用這兩個 enqueueMessage() 方法前會經過一系列的封裝和傳遞,感興趣的同學可以自行查看源碼,這裡就不再詳細介紹了。不過這裡有一點需要註意,之前我們提到過, Message 最重要的屬性是什麼?你寫一封信,內容再豐滿,你不寫地址的話,也是一張廢紙。所以 Message 最重要的成員變數是 target,也就是指定一個消息處理者Handler.可封裝好的 Message 指定了誰來處理呢?我們來看 Handler 中的enqueueMessage()源碼:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
我們看到了他將自己指定成了target。這也符合邏輯:誰發送的請求(消息),誰處理。你總不能跟Looper(領導)說我要上廁所,然後領導同意後讓小王(另一個Handler)來處理這條消息吧。所以對於使用 Handler 的這三種常用函數來發送消息,不是預設而是強制性的指定了消息的target是Handler自己,所以當使用這三種常用函數的第二種時:
public final boolean sendMessage(Message msg);
即使你自己封裝的消息中target是另一個Handler,最終也會在 enqueueMessage()中被修改。
再來說說第二類發送消息的方式:Message對象調用自己的sendToTarget()方式。這種方式工作原理很簡單,看圖就懂了。源碼就一行:
public void sendToTarget() {
target.sendMessage(this);
}
說完這兩種發送消息的方式,有一點需要註意的。當我們需要寫一封信的時候,首先得有一個信封吧。正常情況下,我們都會 new 一個信封出來,然後再往裡添加信息。但是Android 系統中的 Message 可不簡單,我們不光可以 new 一個Message出來,我們還可以通過靜態方法:
public static Message obtain();
獲得一個 Message. 原來 Message 內部維護了一個消息池,obtain()函數前的註釋是這樣說的:
Return a new Message instance from the global pool. Allows us to avoid allocating new objects in many cases.
而 Message 空的構造函數前,文檔也做了說明:
/** Constructor ( but the preferred way to get a Message is to call Message.obtain() ).
*/
public Message() {
}
所以,當我們要一個Message時,首選的方式是使用Message.obtain()方法來獲得.
對於:
II: Handler是如何處理鉤過來的消息的?
由圖可知,loop() 函數中鉤到消息後調用 target.dispatchMessage() 方法來讓 Handler 處理,那我們就來到 dispatchMessage() 方法中一探究竟。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;//截斷消息
}
}
handleMessage(msg);
}
}
首先去看圖,看看 callback, mCallback 都是什麼。我們發現,callback 是 Message 中的成員變數, 類型是 Runnable . mCallback 是 Handler 中的變數,類型是Callback。還記得我們之前說過,Callback是一個Handler中的介面吧:
public interface Callback {
public boolean handleMessage(Message msg);
}
我們回來繼續分析源碼,源碼結構很清晰,兩大分支:如果 Message 中的 Runnable 類型變數不為空,調用 handleCallback() 方法。
private static void handleCallback(Message message) {
message.callback.run();
}
可以看到這裡讓這個Runnable跑起來了。這樣我們就處理了一個包含Runnable請求的消息。
如果 消息中 Runnable 為空呢? 這時就要檢查 Handler 的 Callback 介面類型的成員變數是否為空了。如果不為空則調用 mCallback 的handleMessage()方法。關於Callback介面,一會兒再說。
繼續往下看,最後一條語句才是 Handler 的handleMessage()方法。註意兩個 handleMessage() 方法, 不是同一個對象的調用哦。
現在說說Callback介面是做什麼的。還記得之前講過的 Handler 三種常用構造函數中的第二種嗎?
public Handler(Callback callback);
通過傳入一個 Callback 來初始化 Handler. 簡而言之
Callback是一種可以選擇性截斷消息處理的機制。
假定如下場景:
class LooperThread extends Thread {
public Handler mHandler;
private Handler.Callback mCallback;
public void run() {
Looper.prepare();
mCallback = new Handler.Callback() {
//強制性實現 Callback 介面中的handleMessage(),返回值是布爾類型
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "callbackHandleMSG", Toast.LENGTH_SHORT)
.show();
return msg.what == 1;// msg.what等於1則截斷消息
}
};
mHandler = new Handler(mCallback) {
//選擇性重寫 Handler 中的 handleMessage(),不重寫函數體為空
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, "handleMessage", Toast.LENGTH_SHORT).show();
}
};
Looper.loop();
}
}
截斷機制是如何工作的呢?
if (mCallback.handleMessage(msg)) {
return;//截斷消息
}
沒錯,如果 Callback 的 handleMessage() 方法返回真,則 return,便不會繼續調用Hanlder 中重寫的 handleMessage() 方法了。
以上就是 Handler 和 Looper 協同工作的整個流程。
接下來看一下我們是如何通過 Handler 來更新 UI 的。
我們平時寫 Android 應用時,會把一個應用的入口認為是 onCreate() ,其實 Android 和其他Java程式一樣,他也是有主函數的。這個主函數位於 ActivityThread 這個類中。
函數的樣子就是public static void main(String[] args){...},我截取一段源碼大家看一下:
public static void main(String[] args) {
.
.
.
Looper.prepareMainLooper();//會調用Looper.prepare()
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
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();//Looper開始工作
...
}
這就是我們常說的 Android 應用程式的主線程也就是UI線程。可以看到,他幫我們初始化了一個 Looper 並調用了 loop() 函數。這就是為什麼我們可以在一個 Activity 中,直接創建 Handler 對象而不用初始化 Looper 的原因。(但在我們自己創建的子線程中必須手動初始化 Looper 並調用loop()函數)。於是當我們在子線程中完成耗時工作後,使用主線程中的 Handler 向主線程中的 MessageQueue 發送消息,由主線程中的 loop() 方法從 MessageQueue 中取出消息,並調用消息中 target 的 dispatchMessage() 方法,執行消息。這就保證了主線程既沒有加鎖機制的效率低下問題,也沒有因為不使用加鎖機制而帶來的許多子線程併發更改 UI 導致的界面混亂問題。通過主線程中 Looper 對象,井井有條得管理了其他子線程與主線程間的通信。
最後,說一下什麼是 HandlerThread. 還記得文章開頭給出的Handler定義嗎?
Handler 是 Android 系統提供的一種方便線程間進行通信的機制。我們普遍用 Handler 機制實現在子線程中更新 UI.
Handler是Google設計的一種線程間的通信機制,更新 UI 只是這種機制功能的體現。如果我們應用中有很多子線程,他們之間需要通信怎麼辦呢? 這時候我們就可以直接用這套機制而不用自己設計了。HandlerThread 正是這樣一個方便的類,他繼承自Thread類,並自帶了Handler工作環境中的Looper.源碼中是這樣介紹這個類的:
Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.
於是當我們想創建一個 Handler 並關聯一個我們自己的子線程中的 Looper 時,可以直接通過創建一個 HandlerThread 作為我們的子線程,並通過 HandlerThread中的 getLooper() 方法獲得 Looper 。這是程式結構更為緊湊並且避免了一些安全問題。
以上就是我對 Handler 的一些不是很深入的總結。雖然畫圖很辛苦,碼字很辛苦,但我相信我做到了全網最清晰易懂。希望對奮鬥中的你有所幫助。