前言:
提到Android消息機制大家應該都不陌生项炼,在日常開發(fā)中都不可避免地要涉及到這方面的內(nèi)容。從開發(fā)的角度來說示绊,Handler 是 Android 消息機制的上層接口锭部,使得我們在開發(fā)過程中只需要和 Handler 交互即可。而 Handler 的使用過程也很簡單面褐,通過它可以輕松地將一個任務切換到 Handler 所在的線程中去執(zhí)行拌禾。
但是很多人認為 Handler 的作用就是更新 UI ,這的確沒有錯展哭,但是更新 UI 僅僅是 Handler 的一個特殊的使用場景蹋砚。具體來說應該是這樣的:有時候需要在子線程中進行耗時的 I/O 操作扼菠,可能會是讀取文件亦或許訪問網(wǎng)絡等,當耗時操作完成以后可能需要在 UI 上做一些改變坝咐,由于 Android 開發(fā)規(guī)范的限制循榆,我們并不能在子線程中訪問 UI 控件,否則就會觸發(fā)程序異常墨坚,這個時候通過 Handler 就可以將更新 UI 的操作切換到主線程中執(zhí)行秧饮。因此,本質上來說泽篮, Handler 并不是專門用于更新 UI 的盗尸,只是常被開發(fā)者用來更新 UI 。
Android的消息機制概述
Android 的消息機制主要是指 Handler 的運行機制以及 Handler 所附帶的 MessageQueue 和 Looper 的工作過程帽撑,這三者實際是一個整體泼各,只不過我們在開發(fā)過程中比較多地接觸角到 Handler 而已。
Handler 的主要作用就是將一個任務切換到某個指定的線程中去執(zhí)行亏拉】垓撸可能你會問: Android 為什么要提供這個功能 ? 或者說 Android 為什么需要提供在某個具體的線程中執(zhí)行任務 這個功能及塘? 主要是因為 Android 規(guī)定訪問 UI 只能在主線程中進行莽使,如果在子線程中訪問 UI ,程序就會拋出異常笙僚。
ViewRootImpl 對 UI 操作做了驗證芳肌,這個驗證是由 ViewRootImpl 的checkThread 方法來完成的。如下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
由于 ViewRootImpl 的 checkThread 的這一點限制肋层,導致了必須要在主線程中訪問 UI ,但是 Android 又建議不要在主線程中進行耗時操作亿笤,否則的話會導致程序無法響應,即:ANR栋猖。所以责嚷,考慮到這種情況,假如我們需要從服務端獲取到一些數(shù)據(jù)掂铐、信息并將其顯示在 UI 上罕拂,這個時候必須要在子線程中 做耗時的聯(lián)網(wǎng)操作來獲取數(shù)據(jù),待獲取到數(shù)據(jù)后又不能在子線程中直接訪問 UI 全陨,而此時如果沒有 Handler 爆班,那么我們的確沒有辦法將訪問 UI 的工作切換到主線程中去執(zhí)行。因此辱姨,系統(tǒng)之所以提供了 Handler 柿菩,主要原因就是為了解決在子線程中無法訪問主線程 UI 的矛盾。
在延伸一點雨涛,系統(tǒng)為什么不允許在子線程中訪問 UI 呢枢舶?
這主要是因為 Android 的 UI 控件不是線程安全的懦胞,如果在多線程中 并發(fā)訪問 可能會導致 UI 控件處于不可預期的狀態(tài)。
可能你會說:那為什么系統(tǒng)不對 UI 控件的訪問加上鎖機制呢凉泄?
缺點有兩個:
首先加上鎖機制會讓 UI 訪問的邏輯變的復雜躏尉;
其次,鎖機制會降低 UI 訪問的效率后众,因為鎖機制會阻塞某些線程的執(zhí)行胀糜。
鑒于這兩點,最簡單且高效的方法就是采用單線程模型來處理 UI 操作蒂誉,對于開發(fā)者來說也很簡單教藻,只是需要通過 Handler 切換一下 UI 訪問的執(zhí)行線程即可。
好吧右锨,啰里啰嗦說了這么多括堤,別耐煩,原諒程序猿的我嘴笨不會說绍移。下面簡單的說一下 Handler 的工作原理悄窃。敲小黑板了!5欠颉广匙!
Handler 創(chuàng)建時會采用當前線程的 Looper 來構建內(nèi)部的消息循環(huán)系統(tǒng)允趟,如果當前系統(tǒng)沒有 Looper 那么就會報錯恼策。
解決這個問題其實也很簡單,只需要在當前線程創(chuàng)建 Looper 即可潮剪,或者在一個有 Looper 的線程中創(chuàng)建 Handler 也行涣楷。具體的接下來會進行介紹說明。
Handler 創(chuàng)建完畢后抗碰,此時其內(nèi)部的 Looper 以及 MessagerQueue 就可以和 Handler 一起協(xié)同工作了狮斗,然后會通過 Handler 的 post 方法將一個 Runnable
投遞到 Handler 內(nèi)部的 Looper 中去處理。當然弧蝇,也可以通過 Handler 的 send 方法發(fā)送一個消息碳褒,這個消息同樣會在 Looper 中去處理。其實看疗, post 方法最終也是通過 send 方法來完成的沙峻。
當 Handler 的 send 方法被調用時,會調用 MessagerQueue 的 enqueueMessag 方法將這個消息放入消息隊列中两芳,然后 Looper 發(fā)現(xiàn)有新的消息到來時摔寨,就會處理這個消息,最終消息中的 Runnable 或者 Handler 的 handleMessage 方法就會被調用怖辆。
注意: Looper 是運行在創(chuàng)建 Handler 所在的線程中的是复,這樣一來删顶,Handler 中的業(yè)務邏輯就被切換到創(chuàng)建 Handler 所在的線程中去執(zhí)行了。
Android的消息機制分析
下面將對 Android 消息機制的實現(xiàn)原理做一個較為全面的分析淑廊。由于 Android 的消息機制實際就是 Handler 的運行機制逗余,所以,將主要圍繞 Handler 的工作工程來分析 Android 的消息機制蒋纬,主要包括: Handler猎荠、MessageQueue 和 Looper 。同時為了更好的理解 Looper 的工作原理蜀备,還會介紹一下 ThreadLocal关摇。
ThreadLocal
- 先簡單的說一下 ThreadLocal。
Handler 的運行需要底層的 MessageQueue 和 Looper 的支撐碾阁。而 Looper 中有一個特殊的概念输虱,那就是 ThreadLocal。
ThreadLocal 不是線程脂凶,它的作用是可以在每個線程中存儲數(shù)據(jù)宪睹。我們知道,Handler 創(chuàng)建的時候會采用當前線程的 Looper 來構造消息循環(huán)系統(tǒng)蚕钦。那么 Handler 內(nèi)部是如何獲取到當前線程的 Looper 呢亭病?
這就要使用 ThreadLocal 了,ThreadLocal 可以在不同的線程中互不干擾地存儲并提供數(shù)據(jù)嘶居,通過 ThreadLocal 可以輕松獲取每個線程的 Looper 罪帖。需要注意的是,線程是默認沒有 Looper 的邮屁,如果需要使用 Handler 就必須為線程創(chuàng)建 Looper整袁。
而我們經(jīng)常提到的主線程,也就是 UI 線程佑吝,它就是 ActivityThread 坐昙,ActivityThread 被創(chuàng)建時就會初始化 Looper ,這也就是在主線程中默認可以使用 Handler 的原因芋忿。
- ThreadLocal的工作原理炸客。
接下來又是理論知識,恐怕會讓看文章的你比較蒙圈或是感覺抽象戈钢,可以簡單看看理解一下痹仙。
ThreadLocal 是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲數(shù)據(jù)逆趣,存儲以后蝶溶,只有在指定線程中可以獲取到存儲的數(shù)據(jù),而對于其他線程來說則無法獲取到數(shù)據(jù)。
而平時的開發(fā)中抖所,ThreadLocal使用的地方少之又少梨州,但是在某些特殊的場景下,通過 ThreadLocal 可以輕松地實現(xiàn)一些看起來很復雜的功能田轧,這點在 Android 源碼中也有體現(xiàn)暴匠,比如:
Looper、ActrivityThread以及 AMS 中都用到了 ThreadLocal傻粘。
具體到 ThreadLocal 的使用場景每窖,就不好統(tǒng)一描述。一般來說弦悉,當某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)副本的時候窒典,就可以采用 ThreadLocal 。
比如 Handler 稽莉,它需要獲取當前線程的 Looper 瀑志,而這個 Looper 的作用域就是線程并且不同線程具有不同的 Looper 。這個時候通過 ThreadLocal 就可以輕松實現(xiàn) Looper 在線程中存取污秆。
而如果不采用 ThreadLocal劈猪,那么系統(tǒng)就必須提供一個全局的哈希表共 Handler 查找指定線程的 Looper 。這樣的話還必須提供一個類似于 LooperManager 的類了良拼。
但是系統(tǒng)并沒有這么做而是選擇了 ThreadLocal 战得,這就是 ThreadLocal 的好處。
ThreadLocal 還有一個使用場景是在復雜邏輯下的對象傳遞庸推。此處就不在說明了常侦。
下面通過實際的例子來演示 ThreadLocal 的真正的含義。首先定義一個 ThreadLocal 對象予弧,此處選擇的一個 Boolean 類型的刮吧。如下:
private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<Boolean>();
然后分別在主線程湖饱、子線程1和子線程2中設置和訪問它的值掖蛤,代碼如下:
mThreadLocal.set(true);
Log.e(TAG, "[Thread:main]mThreadLocal = " + mThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
super.run();
mThreadLocal.set(false);
Log.e(TAG ,"[Thread#1]mThreadLocal = " + mThreadLocal.get());
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
super.run();
Log.e(TAG ,"[Thread#2]mThreadLocal = " + mThreadLocal.get());
}
}.start();
在上面的代碼中,我們在主線程中設置 mThreadLocal 的值為 true 井厌,在子線程1中設置 mThreadLocal 的值為 false 蚓庭,在子線程2中不設置 mThreadLocal 的值。然后分別在3個線程中通過 get() 方法獲取 mThreadLocal 的值仅仆,根據(jù)前面對 ThreadLocal 的描述器赞,這個時候,主線程中應該是 true 子線程1中應該是 false 墓拜,而子線程2中由于沒有設置值港柜,應該是 null ,運行代碼,打印日志如下:
E/JiaYang: [Thread:main]mThreadLocal = true
E/JiaYang: [Thread#1]mThreadLocal = false
E/JiaYang: [Thread#2]mThreadLocal = null
從上面的日志可以看出夏醉,雖然是在不同的線程中訪問的是同一個 ThreadLocal 對象爽锥,但是它們 通過 ThreadLocal 獲取到的值卻是不一樣的,這也就是 ThreadLocal 的奇妙之處畔柔。
ThreadLocal 之所以有這么奇妙的效果氯夷,是因為不同線程訪問同一個 ThreadLocal 的 get 方法,ThreadLocal 內(nèi)部會從各自的線程中取出一個數(shù)組靶擦,然后再從數(shù)組中根據(jù)當前 ThreadLocal 的索引去查找出對應的 value 的值腮考。很顯然,不同線程中的數(shù)組是不同的玄捕,這就是 ThreadLocal 可以在不同的線程中維護一套數(shù)據(jù)的副本并且彼此互不干擾踩蔚。
- 簡單的說了 ThreadLocal 的使用方法和工作過程后,接下里簡單分析 ThreadLocal 內(nèi)部實現(xiàn)枚粘。
ThreadLocal 是一個泛型類寂纪,其定義為 public class ThreadLocal<T> ,只要弄清楚 ThreadLocal 的 get 和 set 方法就可以明白它的工作原理赌结。
首先看 ThreadLocal 的 set 方法捞蛋,如下(源碼:Android -21):
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
在上面的 set 方法中,首先會通過 values 方法來獲取當前線程中的 ThreadLocal 數(shù)據(jù)柬姚,其獲取的方式也是很簡單的拟杉,在 Thread 類的內(nèi)部有一個成員專門用于存儲線程的 ThreadLocal 的數(shù)據(jù): ThreadLocal.Values localValues ,因此獲取當前線程的 ThreadLocal 數(shù)據(jù)就顯得異常簡單了量承。
如果 localValues 的值為 null 搬设,那么就需要對其進行初始化,初始化后再將 ThreadLocal 的值進行存儲撕捍。
接下來看一下 ThreadLocal 的值是如何在 localValues 中進行存儲的拿穴。
在localValues 內(nèi)部有一個數(shù)組:private Object[] table,ThreadLocal 的值就存儲在這個 table 的數(shù)組中忧风。
下面看一下 localValues 是如何使用 put 方法將 ThreadLocal 的值存儲到 table 數(shù)組中的默色,如下:
/**
* Sets entry for given ThreadLocal to given value, creating an
* entry if necessary.
*/
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
上面的 put 方法實現(xiàn)了數(shù)據(jù)的存儲過程。此處不去分析其具體算法狮腿,但是我們可以得出一個存儲規(guī)則腿宰。那就是:ThreadLocal 的值在 table 數(shù)組中的存儲位置總是 ThreadLocal 的 reference 字段所標識的對象的下一個位置,比如 ThreadLocal 的 reference 對象在 table 數(shù)組中的索引為 index 缘厢,那么 ThreadLocal 的值在 table 數(shù)組中的索引就是 index + 1吃度。最終 ThreadLocal 的值將會被存儲到 table 數(shù)組中:table[index + 1]= value 。
上面簡單的分析了 ThreadLocal 的 set 方法贴硫,下面說一下 get 方法椿每,如下:
/**
* Returns the value of this variable for the current thread. If an entry
* doesn't yet exist for this variable on this thread, this method will
* create an entry, populating the value with the result of
* {@link #initialValue()}.
*
* @return the current value of the variable for the calling thread.
*/
@SuppressWarnings("unchecked")
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
可以發(fā)現(xiàn), ThreadLocal 的 get 方法的邏輯比較清晰,它同樣是取出當前線程的 local-Values 對象间护,如果這個對象為 null 那么就返回初始值删壮,初始值由 ThreadLocal 的 initialValue 方法來描述,默認情況下為 null 兑牡,當然也可以重寫這個方法央碟,它的默認實現(xiàn)如下所示:
/**
* Provides the initial value of this variable for the current thread.
* The default implementation returns {@code null}.
*
* @return the initial value of the variable.
*/
protected T initialValue() {
return null;
}
如果 localValues 對象不為 null ,那就取出它的 table 數(shù)組并找出 ThreadLocal 的 reference 對象在 table 數(shù)組中的位置均函,然后 table 數(shù)組中的下一個位置所存儲的數(shù)據(jù)就是 ThreadLocal 的值亿虽。
- 總結:從 ThreadLocal 的 set 和 get 方法可以看出,它們所操作的對象都是當前線程的 localValues 對象的 table 數(shù)組苞也,因此在不同線程中訪問同一個 ThreadLocal 的 set 和 get 方法洛勉,它們對 ThreadLocal 所做的讀、寫操作僅限于各自線程的內(nèi)部如迟,這就是為什么 ThreadLocal 可以在多個線程中互不干擾地存儲和修改數(shù)據(jù)收毫,理解 ThreadLocal 的實現(xiàn)方式有助于理解 Looper 的工作原理。
MessageQueue消息隊列的工作原理
Android 中的消息隊列指的是 MessageQueue 殷勘。MessageQueue 主要包含兩個操作:插入和讀取此再。讀取操作本身會伴隨著刪除操作,插入和讀取對應的方法分別為:enqueueMessage 和 next 玲销。
enqueueMessage 的作用是往消息隊列中插入一條消息输拇,而 next 的作用是從消息隊列中取出一條消息并將其從消息隊列中移除。
MessageQueue 盡管被稱之為消息隊列贤斜,但是它的內(nèi)部實現(xiàn)并不是用的隊列策吠,實際上它是通過一個 單鏈表的數(shù)據(jù)結構來維護消息列表,單鏈表在插入和刪除上比較有優(yōu)勢瘩绒。下面看一下 enqueueMessage 和 next 方法的實現(xiàn)猴抹。
- enqueueMessage 源碼如下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
從 enqueueMessage 的實現(xiàn)可以看出,它的主要操作其實就是單鏈表的插入操作锁荔。此處不過多的解釋蟀给。
接下來看一下 next 方法的實現(xiàn), next 的主要邏輯如下:
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
}
...
}
}
可以發(fā)現(xiàn) next 方法是一個無限循環(huán)的方法堕战,如果消息隊列中沒有消息坤溃,那么 next 方法會一直阻塞在這里拍霜,只有當有新消息到來時嘱丢,next 方法會返回這條消息并將其從單鏈表中移除。
Looper的工作原理
Looper 在 Android 的消息機制中扮演著消息循環(huán)的角色祠饺,具體來說就是它會不停地從 MessageQueue 中查看是否有新消息越驻,如果有新消息就會立刻處理,否則就會一直阻塞在那里。
首先缀旁,來看一下 Looper 的構造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
在其構造方法中會創(chuàng)建一個 MessageQueue 消息隊列记劈,然后將當前線程的對象保存起來。
- 我們知道 Handler 的工作需要 Looper并巍,但是沒有 Looper 的線程就會報錯目木,那么如何創(chuàng)建呢?其實也很簡單懊渡,通過 Looper.prepare() 方法即可為當前線程創(chuàng)建一個 Looper 刽射,接著通過 Looper.loop()來開啟消息循環(huán)。如下:
new Thread("Thread#1"){
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler();
Looper.loop();
}
}.start();
估計會疑惑 此時 Handler 最后在 handleMessage 或者 Runnable run 方法下是在哪個線程剃执?我們以post 方法為例:
new Thread("Thread#1"){
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler();
mHandler.post(new Runnable() {
@Override
public void run() {
Log.e("JiaYang", "HandlerThread:" + Thread.currentThread());
}
});
Looper.loop();
}
}.start();
運行代碼誓禁,打印日志:
E/JiaYang: HandlerThread:Thread[Thread#1,5,main]
看到這 估計會說:你這是在Activity 下寫的,默認已經(jīng)有Looper 肾档,那好摹恰,我在一個外部的 Adapter 下寫一個 子線程:
public class MainAdapter extends BaseAdapter {
private final String[] mStrings;
private final Activity mActivity;
public MainAdapter(String[] strings, Activity context) {
mActivity = context;
mStrings = strings;
new Thread("Thread#1"){
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler();
mHandler.post(new Runnable() {
@Override
public void run() {
Log.e("JiaYang", "HandlerThread:" + Thread.currentThread());
}
});
Looper.loop();
}
}.start();
}
...
}
此 Adapter 是MainActivity 下的一個 Adapter ,運行代碼打印日志:
E/JiaYang: HandlerThread:Thread[Thread#1,5,main]
- Looper 除了 prepare 方法外怒见,還提供了 prepareMainLooper方法俗慈,此方法主要是給主線程也就是 ActivityThread 創(chuàng)建 Looper 使用的,其本質也是通過 prepare 方法來實現(xiàn)的遣耍。由于主線程的 Looper 比較特殊姜盈,所以 Looper 提供了一個 getMainLoopter 方法,通過這個方法可以在任何地方獲取到主線程的 Looper 配阵。
Looper 也是可以退出的馏颂, Looper 提供了 quit 和 quitSafely 方法來退出一個 Looper 。二者的區(qū)別是:
quit:會直接退出 Looper 棋傍。
quitSafely:只是設定一個退出標記救拉,然后把消息隊列中的已有消息處理完畢后才完全地退出。
Looper 退出后瘫拣,通過 Handler 發(fā)送的消息會失敗亿絮,此時,Handler 的 send 方法會返回 false麸拄。在子線程中派昧,如果手動為其創(chuàng)建了 Looper ,那在所有的事情完成以后應該會調用 quit 方法來終止消息循環(huán)。否則拢切,這個子線程就會一直處于等待狀態(tài)蒂萎,而如果退出 Looper 后,這個線程就會立刻終止淮椰,因此五慈,建議不需要的時候終止 Looper 纳寂。
- Looper 中最重要的一個方法就是 loop 方法,只有調用了 loop 以后泻拦,消息循環(huán)系統(tǒng)才會真正的起作用毙芜,源碼如下:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public 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();
}
}
Looper 的 loop 方法的工作過程也很好理解。loop 方法就是一個死循環(huán)争拐,唯一跳出死循環(huán)的方式是:MessageQueue 的 next 方法返回了 null 腋粥。
當 Looper 的 quit方法被調用時,Looper就會調用 MessageQueue 的 quit 或者 quitSafely 方法來通知消息隊列退出架曹,當消息隊列被標記為退出狀態(tài)時灯抛,它的 next 方法就會返回 null。也就是說:Looper必須退出音瓷,否則 loop方法會一直無線循環(huán)下去对嚼。loop方法會調用 MessageQueue的next 方法來獲取新消息,而next是一個阻塞操作绳慎,當沒有消息的時候纵竖,next 方法會一直阻塞在那里,這也會導致 loop 方法一直阻塞在那里杏愤。
如果 MessageQueue 的 next 方法返回了新消息靡砌,Looper就會處理這條消息:msg.target.dispatchMessage(msg) ,這里的 msg.target 是發(fā)送這條消息的 Handler 對象珊楼,這樣通殃,Handler 發(fā)送的消息最終又交給它的 dispatchMessage方法來處理了。
但是不同的是:Handler 的 dispatchMessage 方法是在創(chuàng)建 Handler 時所使用的 Looper 中執(zhí)行的厕宗,這也就成功的將代碼邏輯切換到指定的線程中去執(zhí)行了画舌。
Handler 的工作原理
Handler 的工作主要包含消息的發(fā)送和接收過程。消息的發(fā)送可以通過 post 的一系列方法 以及 send 方法來實現(xiàn)已慢,post 的一系列方法最終是通過 send 的一系列方法來實現(xiàn)的曲聂。
發(fā)送一條消息的典型過程源碼如下:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以發(fā)現(xiàn)洋闽, Hanlder 發(fā)送消息的過程 僅僅是向消息隊列中插入了一條消息泡徙, MessagerQueue 的 next 方法就會返回這個消息給 Looper, Looper 接收到消息后就開始處理睛廊,最終消息由 Looper 交由 Handler 處理膜楷,即 Handler 的 dispatchMessage 方法會被調用旭咽,這時, Handler 就進入了處理消息的階段赌厅。
- dispatchMessager 的實現(xiàn)源碼如下:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
- Handler 處理消息的過程如下:
- 首先穷绵,檢查 Message 的 callback 是否為 null ,不為null 就通過 handleCallback 來處理消息察蹲。Message 的 callback 是一個 Runnable 對象请垛,實際上就是 Handler 的 post 方法所傳遞的 Runnable 參數(shù)催训。handleCallback 的邏輯也很簡單洽议,源碼如下:
private static void handleCallback(Message message) {
message.callback.run();
}
- 其次宗收,檢查 mCallback 是否為 null,不為 null 就調用 mCallback 的 handleMessage 方法來處理消息亚兄, Callback 是一個接口混稽,定義如下:
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
通過 Callback 可以采用如下方法來創(chuàng)建 Handler 對象: Handler handler = new Handler(callback) 。那么 Callback 的意義又是什么呢审胚?
源碼中的注釋已經(jīng)作了說明:可以用來創(chuàng)建一個 Handler 的實例但并不需要派生 Handeler 的子類匈勋。
而我們在平時開發(fā)的過程中最常見的方式就是派生一個 Handler 的子類并重寫其 handleMessage 方法來處理具體的消息。Callback 只是給我們提供了另外一種使用 Handler 的方式膳叨。當我們不想派生子類的時候洽洁,就可以通過 Callback 來實現(xiàn)。
- 最后菲嘴,調用 Handler 的 handleMessage 方法來處理消息饿自。Handler 處理消息的過程可以歸納為一個流程圖(未用思維導圖作圖,湊合看吧):
Handler 還有一個特殊的構造方法龄坪,就是通過一個特定的 Looper 來構造 Handler 昭雌,通過這個構造方法可以實現(xiàn)一些特殊的功能。具體實現(xiàn)如下:
/**
* Use the provided {@link Looper} instead of the default one.
*
* @param looper The looper, must not be null.
*/
public Handler(Looper looper) {
this(looper, null, false);
}
我們來看一下 Handler 的默認構造方法 public 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;
}
通過這個構造可以看出烛卧,如果線程沒有 Looper 的話,就會拋出Can't create handler inside thread that has not called Looper.prepare()這個異常妓局,這也就說明了為什么在沒有 Looper 子線程中創(chuàng)建 Handler 會引發(fā)程序異常的原因总放。
寫在最后:
- 說了這么多,估計還是暈頭轉向的好爬,最后间聊,用自己的話總結一下吧:
使用 sendMessage() 發(fā)送消息后,通過 Hanlder 將消息發(fā)送給 消息隊列 MessageQueue 抵拘。
在 Handler 發(fā)送消息的時候哎榴,使用 message.target = this 為 Handelr 發(fā)送的消息 Message 貼上當前 handler 一個標簽。
MessageQueue 調用 enqueueMessage 方法僵蛛,將消息插入到消息隊列中尚蝌。
消息隊列中發(fā)現(xiàn)有新消息的到來時,MessageQueue 的內(nèi)部走 next 方法無限循環(huán)充尉。而Looper 發(fā)現(xiàn)有新消息到來飘言,就會無限循環(huán)消息隊列,也就是 Looper.loop 方法驼侠。
在MessageQueue next 方法返回消息并將消息從消息隊列中刪除后姿鸿,Looper.loop 此時獲得 MessageQueue 對象谆吴,從中取出 next 返回的消息。當 MessageQueue next 方法返回 null也就是沒有消息 的時候苛预,next 會一直阻塞在那里句狼,而Looper.loop 方法就會停止,并也阻塞在那里热某。
Loop.loop 從消息隊列中取出消息后腻菇,會調用 msg.target.dispatchMessage(msg) 進行消息分發(fā)。
此時 msg.target 就是剛才發(fā)送消息的 handler 對象昔馋,最后等于又調用了 handler 的 dispatchMessage(msg) 方法筹吐。(而 dispatchMessage(msg) 上面最后也說了實現(xiàn)原理。)
在創(chuàng)建 handler 的時候 覆寫的方法 handleMessage 秘遏,對分發(fā)的消息進行了處理丘薛。
最后,在消息使用完畢后邦危,在 Looper.loop 方法中調用了 msg.recyclerUnchecked() 方法洋侨,將消息回收。即將消息的所有字段恢復為初始狀態(tài)铡俐。