從開發(fā)的角度來(lái)講穆咐,Handler 是 Android 消息機(jī)制的上層接口。因此我們主要討論的是 Handler 的運(yùn)行機(jī)制字旭。
那么首先回答個(gè)問(wèn)題对湃,為什么要有 Handler 機(jī)制?
0. 為什么要有 Handler 機(jī)制遗淳?
回答這個(gè)問(wèn)題拍柒,首先我們得知道 Handler 有什么作用。
作用: Handler 的主要作用是將一個(gè) 任務(wù) 切換到 Handler 所在的線程中去執(zhí)行屈暗。
而 Android 規(guī)定訪問(wèn) UI 這個(gè) 任務(wù) 只能在主線程中進(jìn)行拆讯,如果在子線程中訪問(wèn) UI,就會(huì)拋出異常养叛。每次操作 UI 時(shí)會(huì)進(jìn)行驗(yàn)證往果,這個(gè)驗(yàn)證是通過(guò) ViewRootImpl 類里面的 checkThread 方法 完成的。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
只能在主線程就只能在主線程訪問(wèn)唄一铅,那為什么還要這個(gè) Handler 呢,因?yàn)?Android 又建議在主線程中不能進(jìn)行耗時(shí)操作堕油,否則會(huì)導(dǎo)致 ANR 潘飘。那考慮這樣一種情況,比如我們要從服務(wù)端拉取一些數(shù)據(jù)并將其顯示在 UI 上掉缺,這個(gè)必須在子線程中進(jìn)行拉取動(dòng)作卜录,拉取完畢后又不能在子線程中訪問(wèn) UI,那我該如何將訪問(wèn) UI 的工作切換到主線程中呢眶明?對(duì)艰毒,就是 Handler。
因此系統(tǒng)之所以提供 Handler搜囱,主要原因就是為了 解決在子線程中無(wú)法訪問(wèn) UI 的矛盾丑瞧。
Q 為什么不允許在子線程中訪問(wèn) UI柑土?
因?yàn)?Android 的 UI 控件不是線程安全的,如果在 多線程中并發(fā)訪問(wèn) 可能會(huì)導(dǎo)致 UI 控件處于不可預(yù)期的狀態(tài)绊汹。
Q 那為什么不對(duì) UI 控件的訪問(wèn)加上 鎖機(jī)制呢稽屏?
- 首先加鎖會(huì)讓 UI 訪問(wèn)的邏輯變得復(fù)雜
- 其次鎖機(jī)制會(huì)降低 UI 訪問(wèn)的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行西乖。
1. Looper狐榔、MessageQueue、Message获雕、Handler
消息機(jī)制的模型主要由以上四個(gè)類組成:
- Message:消息 分為 硬件產(chǎn)生的消息(如按鈕薄腻、觸摸等) 和 軟件生成的消息。
- MessageQueue:消息隊(duì)列 届案,內(nèi)部采用 單鏈表 的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ) 消息列表庵楷。
- Handler:消息處理者,主要用于向 消息隊(duì)列 發(fā)送各種消息事件 和 處理相應(yīng)消息事件萝玷。
- Looper:消息循環(huán)嫁乘,不斷循環(huán)執(zhí)行(Looper.loop),用于從 消息隊(duì)列中取出 消息球碉,并按分發(fā)機(jī)制將取出的消息分發(fā)給 消息處理者蜓斧。
2. 示意圖
下圖完整類圖取自 http://gityuan.com/2015/12/26/handler-message-framework/
下圖為簡(jiǎn)化版本:
針對(duì)簡(jiǎn)化版做個(gè)解釋:
1、整個(gè)消息機(jī)制從 Looper 開始睁冬。我們都知道使用Handler 時(shí)挎春,如果沒(méi)有 Looper 會(huì)拋出異常, 這個(gè)在源碼中很清楚。所以整個(gè)消息機(jī)制這個(gè)大機(jī)器啟動(dòng)的源頭就是 Looper豆拨, 通過(guò)調(diào)用 Looper.prepare() 我們會(huì) new 一個(gè) Looper 對(duì)象直奋,并存放在 ThreadLocal 里,這個(gè) ThreadLocal 稍后解釋施禾。而在 Looper 的構(gòu)造函數(shù)中脚线,會(huì) new MessageQueue 對(duì)象。
2弥搞、調(diào)用 Looper.loop() 啟動(dòng)整個(gè)消息機(jī)制邮绿。 在調(diào)用 loop 方法后, Looper 就開始了自己的 無(wú)限循環(huán)之路攀例, 會(huì)一直從 MessageQueue 中取 Message船逮, 這個(gè)操作對(duì)應(yīng)的代碼是 loop 方法里的 queue.next(), MessageQueue 中的 next 也是一個(gè) 無(wú)限循環(huán)粤铭,如果 MessageQueue 中沒(méi)有消息挖胃, 那么 next 方法就會(huì)阻塞在這里,相應(yīng)的 Looper 也會(huì)阻塞。 當(dāng)有新消息到來(lái)時(shí)酱鸭, 會(huì)喚醒他們吗垮。 至此,整個(gè)消息機(jī)制已經(jīng)運(yùn)轉(zhuǎn)起來(lái)了凛辣,就等 消息 發(fā)過(guò)來(lái)了抱既。
3、Handler 發(fā)送消息扁誓。 首先我們得 new 一個(gè) handler 才能發(fā)送消息防泵,在我們 new handler 的時(shí)候,會(huì)將當(dāng)前線程的 Looper 取出來(lái)蝗敢,同時(shí) 得到 Looper 里的 MessageQueue捷泞。 有了消息隊(duì)列,我們就能往隊(duì)列里插入數(shù)據(jù)了寿谴。 handler 的消息發(fā)送最終都是調(diào)用的 MessageQueue.enqueueMessage() 方法锁右。 這樣我們就把一個(gè) Message 發(fā)送到了 MessageQueue 里。噢讶泰,對(duì)了咏瑟,Message 是自己構(gòu)建的,這個(gè)就不說(shuō)了痪署。
4码泞、此時(shí) 消息隊(duì)列里有了 Message,那么 MessageQueue 的next 就會(huì)把剛剛那個(gè) 消息返回給Looper狼犯, Looper 收到 Message 后余寥, 會(huì)開始 消息的分發(fā),就是調(diào)用 Message.target.dispatchMessage(msg)悯森, 這個(gè) Message 就是剛剛發(fā)送的 Message宋舷,那 target 就是 Message 里持有的 Handler 對(duì)象。因此 這個(gè)消息的處理 又回到了 Handler 這里瓢姻。 那么 Message 是什么時(shí)候持有的 Handler 對(duì)象呢祝蝠,沒(méi)錯(cuò),就是在 Handler 發(fā)送消息時(shí)幻碱,即調(diào)用 enqueueMessage 的時(shí)候续膳。這個(gè)方法內(nèi)部 第一行代碼就是
msg.target = this;
,這樣就把 Handler 賦給了 Message 的 target 變量收班。
上述是整個(gè) 消息機(jī)制的 大致流程,嗯谒兄,這么長(zhǎng)估計(jì)沒(méi)人看摔桦,我自己都不想看。
看過(guò)這么一大段后,對(duì)于 Handler 的主要作用 想必還是一頭霧水邻耕。 還記得 Handler 的主要作用嗎鸥咖?
Handler主要作用:將一個(gè)任務(wù)切換到 Handler 所在的線程中去執(zhí)行。
了解流程后兄世,我知道了一個(gè) 消息 怎么發(fā)給消息隊(duì)列啼辣, 然后 Handler 會(huì)自己處理這個(gè)消息。但是這個(gè)線程是怎么切換的御滩,完全不知道鸥拧。
上述簡(jiǎn)化圖中有個(gè)灰色的塊塊, ThreadLocal削解。 對(duì)富弦,它就是關(guān)鍵。
3. ThreadLocal 工作原理
ThreadLocal 是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類氛驮。 還是自己去找定義看吧腕柜,這里就不寫了,我們還是關(guān)心它有什么用矫废。
它的主要作用就是:可以在不同的線程中維護(hù)一套數(shù)據(jù)的副本并且彼此互不干擾盏缤。
這又是什么意思,別慌蓖扑,這里有個(gè)例子可以方便理解唉铜。
public class ThreadLocalSample {
private ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<>();
public void test() {
//1、在主線程中設(shè)置 mThreadLocal 的值為 true
mThreadLocal.set(true);
System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
//1赵誓、在子線程1中設(shè)置 mThreadLocal 的值為 false
mThreadLocal.set(false);
System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get());
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
//1打毛、在子線程2中 不設(shè)置 mThreadLocal,那么get得到的值應(yīng)該為 null
System.out.println(Thread.currentThread().getName() + " : mThreadLocal = " + mThreadLocal.get());
}
}.start();
}
public static void main(String[] args) {
new ThreadLocalSample().test();
}
}
這段代碼執(zhí)行的結(jié)果如下:
從結(jié)果可知俩功,雖然在不同的線程訪問(wèn)的是同一個(gè) ThreadLocal 對(duì)象幻枉,但是他們通過(guò) ThreadLocal 獲取到的值卻是不一樣的。
這是為什么呢诡蜓?
3.1 ThreadLocal.Values
ThreadLoacal 內(nèi)部有個(gè) 靜態(tài)內(nèi)部類 Values熬甫,Values 內(nèi)部維護(hù)的是一個(gè) Object [ ] ,當(dāng)我們通過(guò) ThreadLoacal 進(jìn)行 set() 方法調(diào)用時(shí)蔓罚,實(shí)際是在 Values.put 椿肩。 當(dāng)然不同版本的api,實(shí)現(xiàn)不一樣豺谈,比如最新版本把Values改為了ThreadLocalMap郑象,并且內(nèi)部維護(hù)的是 Entry [ ] 數(shù)組。但是原理都一樣茬末,這里就 Values 分析簡(jiǎn)單點(diǎn)厂榛。
/**
* Sets the value of this variable for the current thread. If set to
* {@code null}, the value will be set to null and the underlying entry will
* still be present.
*
* @param value the new value of the variable for the caller thread.
*/
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
可以看到盖矫,當(dāng)我們調(diào)用 set 方法時(shí), 會(huì)先取得當(dāng)前的 Thread击奶, 然后把 值 put 進(jìn)去辈双。 那么中間那句 Values values = values(currentThread);
是怎么回事呢。 看 Thread 源碼柜砾。
/**
* Normal thread local values.
*/
ThreadLocal.Values localValues;
你可以看到 Thread 的成員變量里持有 ThreadLocal.Values 湃望。 所以當(dāng)我們 set 時(shí),會(huì)先從當(dāng)前線程那里獲取到 Values 對(duì)象痰驱, 也就是說(shuō)我們實(shí)際是在給 每個(gè)線程的 Values 賦值证芭。那么 values(currentThread) 做了什么呢。
/**
* Gets Values instance for this thread and variable type.
*/
Values values(Thread current) {
return current.localValues;
}
很簡(jiǎn)單萄唇,就是返回 線程的 localValues 變量檩帐。 那么當(dāng)我們第一次 set 時(shí), 這個(gè) Values 肯定為空另萤, 那么就會(huì)調(diào)用 values = initializeValues(currentThread);
來(lái)進(jìn)行初始化湃密。
那么 ThreadLocal 類里的 initializeValues 又做了什么呢。
/**
* Creates Values instance for this thread and variable type.
*/
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
沒(méi)錯(cuò)四敞,直接 new Values()泛源,并且把它賦給 Thread 類的 localValues 變量。 這樣當(dāng)前線程就擁有了 ThreadLocal.Values 忿危,當(dāng)下次set時(shí)达箍,就會(huì)從這個(gè)線程中取出這個(gè) Values 并對(duì)它進(jìn)行賦值。 每個(gè)線程的 Values 是不一樣的铺厨。 那 get 就不用說(shuō)了缎玫,也是從當(dāng)前線程中取出這個(gè) Values ,然后獲取相應(yīng)的值解滓,具體可自行查看源碼赃磨。
到這里 ThreadLocal 就分析的差不多了,想必有了個(gè)大概印象洼裤,針對(duì)上述例子給個(gè)圖理解邻辉。
4. Looper 中的ThreadLocal
分析過(guò) ThreadLocal 后,看 Looper 就不一樣了腮鞍。
再來(lái)看一看 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 就是 ThreadLocal 的引用,如下
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
這段我們說(shuō)過(guò)移国,會(huì)new 一個(gè) looper吱瘩, 同時(shí)會(huì)把這個(gè) looper 賦值給 ThreadLocal,我們知道 ThreadLocal 調(diào)用 set 方法的時(shí)候迹缀,會(huì)先獲取當(dāng)前線程使碾,然后調(diào)用 Values.put 皱卓, 那么這個(gè)時(shí)候,我們就把 主線程 與 Looper 綁定在一起了部逮。
Handler 的構(gòu)造方法如下:
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;
}
我們可以看到首先會(huì)獲取 Looper 對(duì)象, 而 Looper.myLooper() 的實(shí)現(xiàn)很簡(jiǎn)單嫂易,就一句話 return sThreadLocal.get();
這樣兄朋,如果你是在主線程中 new 的 handler, 那么你也就會(huì)在 主線程中 處理消息了怜械。
5. Handler 中的dispatchMessage 消息分發(fā)
這個(gè)方法比較簡(jiǎ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);
}
}
- Message 里的 callback 是個(gè) Runnable缕允, 如果不為空的話峡扩,就會(huì)調(diào)用 handleCallback , 這里面也就一句話
message.callback.run();
- mCallback 是 Handler類 的一個(gè)內(nèi)部接口
public interface Callback {
public boolean handleMessage(Message msg);
}
- handleMessage障本, 當(dāng)我們自己 new handle 時(shí)會(huì)重寫這個(gè)方法教届,用來(lái)處理 message, 這個(gè)方法在 Handler 里是空實(shí)現(xiàn).
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}