深入探索Android中的Handler
什么是Handler
Handler是Android消息機制的上層接口,它為我們封裝了許多底層的細節,讓我們能夠很方便的使用底層的消息機制。Handler的最常見應用場景之一便是通過Handler在子線程中間接更新UI。Handler的作用主要有兩個:一是發送消息;二是處理消息,它的運作需要底層Looper和MessageQueue的支撐。
MessageQueue即消息隊列,它的底層用單鏈表實現;Looper則負責在一個循環中不斷從MessageQueue中取消息,若取到了就交由Handler進行處理,否則便一直等待。關于Looper需要注意的一點是除了主線程之外的其他線程中默認是不存在Looper的。主線程中之所以存在,是因為在ActivityThread被創建時會完成初始化Looper的工作。
為什么使用Handler
總的來說, Handler的作用是將一個任務(從當前線程)切換到指定的線程中去執行 。我們知道Android只允許主線程去更新用戶界面,而主線程需要時刻保持較高的響應性,因此我們要把一些耗時任務交給工作者線程去執行。那么問題來了,如果工作者線程執行完任務后想要更新UI該怎么破?我們希望的是主線程能夠接收到工作者線程的通知,并且能根據工作者線程執行任務的結果對用戶界面進行相應的更新。Handler就能讓我們很方便的做到這些。Handler的工作過程大致如下圖所示:
我們針對上圖做下簡單解釋(詳細的分析請見后文):首先我們在主線程中創建Handler對象(使用主線程的Looper)并定義handleMessage方法,這個Handler對象默認會關聯主線程中的Looper。通過在工作者線程中使用該Handler對象發送消息,相應的消息處理工作(即handleMessage方法)會在主線程中運行, 這樣就成功地將更新UI任務從工作者線程切換到了主線程。
Handler的工作原理分析
總的來說,Handler對象在被創建時會使用當前線程的Looper來構建底層的消息循環系統(使用默認構造器的情況下),若當前線程不存在Looper,則會報錯。Handler對象創建成功后,就可以通過Handler的send或post方法發送消息了。調用send/post方法發送消息時,實際上會調用MessageQueue的enqueueMessage方法將該消息加入到MessageQueue中。之后Looper發現有新消息會取出,并把它交給Handler處理。下面我們通過分析相關源碼來詳細介紹這一過程。在這之前我們需要先了解一下ThreadLocal的工作原理。
ThreadLocal的內部工作機制
ThreadLocal是一個線程內部的數據存儲類。通過使用ThreadLocal,能夠讓同一個數據對象在不同的線程中存在多個副本,而這些副本互不影響。Looper的實現中便使用到了ThreadLocal。通過使用ThreadLocal,每個線程都有自己的Looper,它們是同一個數據對象的不同副本,并且不會相互影響。下面我們現在探索下ThreadLocal的工作原理,為分析Looper的工作原理做好鋪墊。
ThreadLocal使用示例
作為ThreadLocal的一個簡單示例,我們先創建一個ThreadLocal對象:
private ThreadLocal<Integer> mIntegerThreadLocal =
new ThreadLocal<Integer>();
然后創建兩個子線程,并在不同的線程中為ThreadLocal對象設置不同的值:
1 mIntegerThreadLocal.set(0);
2 Log.d(TAG, "In Main Thread, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
3
4 new Thread("Thread 1") {
5 @Override
6 public void run(){
7 mIntegerThreadLocal.set(1);
8 Log.d(TAG, "In Thread 1, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
9 }
10 }.start();
11
12 new Thread("Thread 2") {
13 @Override
14 public void run() {
15 Log.d(TAG, "In Thread 2, mIntegerThreadLocal = " + mIntegerThreadLocal.get());
16 }
17 }.start();
在以上代碼中,我們在主線程中設置mIntegerThreadLocal的值為0,在Thread 1中該設置為1,而在Thread 2中未設置。我們看一下日志輸出:
通過日志輸出我們可以看到,主線程與Thread 1的值確實分別為我們為他設置的,而Thread 2中由于我們沒有給它賦值,所以就為null。我們雖然在不同的線程中訪問同一個數據對象,卻可以獲取不同的值。那么ThreadLocal是如何做到這一點的呢?下面我們通過源碼來尋找答案。
ThreadLocal的工作原理
我們首先要知道,Thread類內部有一個專門用來存儲線程對象ThreadLocal數據的實例域,它的聲明如下:
ThreadLocal.Values localValues;
這樣一來,每個線程中就可以維護ThreadLocal對象的一個副本,而且這些副本不會互相干擾,ThreadLocal的get方法只要到localValues中去取數據就好了,set方法也只需操作本線程的localValues。我們來看一下set方法的源碼:
1 public void set(T value) {
2 Thread currentThread = Thread.currentThread();
3 Values values = values(currentThread);
4 if (values == null) {
5 values = initializeValues(currentThread);
6 }
7 values.put(this, value);
8 }
第3行通過values方法獲取到當前線程的localValues并存入values變量中,接下來在第4行進行判斷,若localValues為null,則調用initializeValues方法進行初始化,然后會調用put方法將value存進去。實際上,localValues內部有一個名為table的Object數組,ThreadLocal的值就存在這個數組中。
了解了set方法的大致邏輯后,我們再來看一下get方法都做了些什么:
1 public T get() {
2 // Optimized for the fast path.
3 Thread currentThread = Thread.currentThread();
4 Values values = values(currentThread);
5 if (values != null) {
6 Object[] table = values.table;
7 int index = hash & values.mask;
8 if (this.reference == table[index]) {
9 return (T) table[index + 1];
10 }
11 } else {
12 values = initializeValues(currentThread);
13 }
14
15 return (T) values.getAfterMiss(this);
16 }
第4行中,獲取localValues。第5行若判斷為null,則表示未進行設置(比如上面例子中的線程2),就會返回默認值;若判斷非空就先獲取table數組,然后再計算出index,根據index返回ThreadLocal的值。
經過以上對get和set方法的源碼的分析,我們了解到了這兩個方法實際上對不同的線程對象會分別操作它們內部的localValues,所以能夠實現多個ThreadLocal數據對象的副本之間的互不干擾。了解了ThreadLocal的實現原理,下面我們來探索下Looper是怎么借助ThreadLocal來實現的。
Looper的內部工作機制
在介紹Looper的工作機制之前,我們先來簡單的介紹下MessageQueue。MessageQueue對消息隊列進行了封裝,在它的內部使用單鏈表來保存消息。MessageQueue主要支持以下兩個操作:
- enqueueMessage:向消息隊列中插入一個消息。
- next:從消息隊列中取出一個消息(會從隊列中刪除該消息)。next方法內有一個無限循環,若消息隊列為空,它會阻塞在這直到取到消息。
大致了解了MessageQueue后,讓我們一起來探索Looper的內部工作機制,看看它是如何漂亮的完成將任務切換到另一個線程這個工作的。我們首先來看一下Looper的構造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
我們可以看到Looper的構造方法中創建了一個MessageQueue對象。之前我們提到過Handler只有在存在Looper的線程中才能創建,而我們看到Looper的構造方法是private的,那么我們怎么為一個線程創建Looper呢?答案是使用Looper.prepare方法,這個方法的源碼如下:
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));
}</code></pre>
我們可以看到prepare方法內部調用了Looper的構造器來為當前線程初始化Looper,而且當前的線程的Looper已經初始化的情況下再調用prepare方法會拋出異常。
創建了Looper后,我們就可以開始通過Looper.loop方法進入消息循環了( 注意,主線程中我們無需調用loop方法,因為ActivityThread的main方法中已經為我們調用了 )。我們來看一下這個方法的源代碼:
ublic static void loop() {
final Looper me = myLooper();
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();
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);
}
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();
}
}</code></pre>
通過以上代碼我們可以看到,在第13行會進入一個無限循環。接著在第14行,調用了MessageQueue的next方法,之前我們介紹過這個方法會一直阻塞直到從消息隊列中取出一個消息。退出這個無限循環的唯一方法就是MessageQueue返回null。這可以通過調用Looper的quit方法來實現。當Looper的quit/quitSafely方法被調用時,會導致MessageQueue的quit/quitSafely方法被調用,這會導致消息隊列被標記為“退出”狀態,如此一來,MessageQueue的next方法就會返回null了。這告訴了我們,如果我們不調用Looper的quit方法,他就會在loop方法中的循環里一直運行下去。
若在第14行中成功從MessageQueue中取得了一個消息,接下來就會對這個消息進行處理。第27行調用了msg.target的dispatchMessage方法,其中msg.target指的是發送這條消息的Handler對象,也就是說這里調用的是發送消息的Handler對象的dispatchMessage方法。注意,Handler的dispatchMessage方法實在創建該Handler時所使用的線程中執行的,這樣一來,便成功地將任務切換到了Looper所在線程。接下來,我們以分析dispatchMessage方法的源碼為切入點研究一下Handler的工作原理。
Handler的內部工作機制
首先,我們接著上一步,看一下dispatchMessage方法的源碼:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我們可以看到,這個方法中會首先判斷msg.callback是否為null,若不為null則調用handleCallback方法。msg.callback是一個Runnable對象,實際上就代表著我們調用post方法放入MessageQueue中的Runnable對象。也就是說,若我們post了一個Runnable對象,就會調用handleCallback方法,這個方法的源碼如下:
private static void handleCallback(Message message) {
message.callback.run();
}
從以上代碼我們可以看到,這個方法就是簡單的調用了Runnable對象的run方法讓它開始運行。
回到dispatchMessage方法的代碼,若msg.callback為null,就會判斷mCallback是否為null,若不為null則調用mCallback的handleMessage方法,否則調用handleMessage方法。實際上這兩個handleMessage方法都是我們創建Handler對象時定義的消息處理函數,只不過分別對應了兩種不同的創建Handler對象的方式。調用mCallback的handleMessage方法表示我們創建Handler對象時傳入了一個實現了Callback接口的的對象,而調用handleMessage方法表示我們創建Handler對象時繼承了Handler類并重寫了handleMessage方法。那么mCallback是什么呢?讓我們先看一下Handler的構造方法:
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();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}</code></pre>
我們可以看到,mCallback被賦值為我們傳入的第一個參數callback,callback即為實現了Callback接口的對象,Callback接口中只有一個方法,那就是handleMessage方法。
主線程的消息循環
Android中的主線程也就是我們上面提到過的ActivityThread。我們上面介紹過,ActivityThread的main方法中會通過Looper.loop方法開啟循環,相關源碼如下:
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
通過以上代碼我們可以看到主線程在初始化時確實通過Looper.loop方法開啟了消息循環。那么主線程使用了哪個Handler來與MessageQueue進行交互呢?實際上它使用了ActivityThread.H,它的定義如下:
rivate class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
public static final int PAUSE_ACTIVITY_FINISHING= 102;
public static final int STOP_ACTIVITY_SHOW = 103;
public static final int STOP_ACTIVITY_HIDE = 104;
public static final int SHOW_WINDOW = 105;
public static final int HIDE_WINDOW = 106;
public static final int RESUME_ACTIVITY = 107;
public static final int SEND_RESULT = 108;
public static final int DESTROY_ACTIVITY = 109;
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int NEW_INTENT = 112;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;
...
public void handleMessage(Message msg) {
...
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
(msg.arg1&2) != 0);
maybeSnapshot();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
...
}
}</code></pre>
從以上源碼中我們可以看到,主線程使用的Handler中定義了一系列常量,代表了發生了各種事件(比如啟動Activity、暫停Activity、顯示Window)時應發給的主線程的消息標識。實際上這些消息是H在ApplicationThread中發送過來的。具體過程如下:ActivityThread通過ApplicationThread與AMS(Activity Manager Service)進行進程間通信(IPC)。AMS完成ActivityThread的請求后會回調ApplicationThread中的Binder方法,然后ApplicationThread會通過H發送消息到ActivityThread的MessageQueue中,之后H的handlerMessage方法便會根據發來的消息進行相應的處理。這樣就完成了將任務從ApplicationThread切換到ActivityThread的工作。
參考資料
1. Android SDK Sources
2. 《Android開發藝術探索》
來自:http://www.jianshu.com/p/dd1ee1093050