Handler
在android開發中可謂隨處可見,不論你是一個剛開始學習android的新人,還是昔日的王者,都離不開它。關于 handler
的源碼已經很前人分享過了。如果我沒能給大家講明白可以參考網上其他人寫的。
注:文中所貼源碼都有精簡過,并非完整源碼,只保留主思路,刪減了一些非法校驗細節實現
目錄
- 簡單使用方法
- 源碼流程分析
簡單使用方法
應用層開發時handle
常要用于線程切換調度和異步消息、更新UI等,但不僅限于這些。
使用方法:略
哇哈哈哈,不要打我。為了不占用篇幅,想必識標題來者理當熟悉。若有不明之處且看其他偏基礎點的教程便可。
源碼流程分析
大王,且先隨我看小的從網上盜來的一張圖。handler
發送Message
(消息)至MessageQueue
(模擬隊列),由Looper
(循環器)不斷循環取出。然后通知Handler
處理。這便是整個的消息機制。沒有多復雜。
關鍵對象源碼分析:
- Looper 消息輪訓器
- MessageQueue 消息暫存隊列(單鏈表結構)
- Message 消息
- Handler 收發消息工具
- ThreadLocal (本地線程數據存儲對象)
ThreadLocal
先說ThreadLocal
的作用是不同的線程擁有該線程獨立的變量,同名對象不會被受到不同線程間相互使用出現異常的情況。
即:你的程序擁有多個線程,線程中要用到相同的對象,但又不允許線程之間操作同一份對象。那么就可以使用ThreadLocal
來解決。它可以在線程中使用mThreadLocal.get()
和mThreadLocal.set()
來使用。若未被在當前線程中調用set
方法,那么get
時為空。
在Looper中是一個靜態變量的形式存在,并在每個線程中擁有獨立的Looper對象,沒有則為空。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
如若還不明白可單獨做了解,弄明白這個是必須的,否則后面會云里霧里。不知為何hanlder
可以做到跨線程消息切換。我姑且當做讀者已熟悉這點。
這里Looper
是最為重要的一環,我們先來看這個,其余幾個對象源碼分析的意義不大,后面小節會在消息流程中分析到。就省略了。如果非要糾結解析可以自己去翻閱一下源碼即可。
Looper 關鍵源碼
記住了,Looper
主要做兩件事。1.創建消息隊列。 2.循環取隊列中的消息分發。
后面一個小節會講什么時候創建,見流程分析。
構造函數
在 Looer
創建的時候初始化了MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//創建消息隊列
mThread = Thread.currentThread(); //獲取當前線程
}
**創建Looper **
其中分為在主線程中創建,和子線程創建,但都是借助ThreadLocal
來實現跨線程Looper
對象的存儲。
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));
}
開啟循環
可以看到loop方法
中是一個死循環在不斷的取消息。但注意當無消息時循環并不會做無用功,而是阻塞等待消息。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 取出消息,無消息則阻塞
if (msg == null) { return; }
msg.target.dispatchMessage(msg);//發送消息 其中target就是Handler
}
}
消息流程分解:
主線程中Looper的創建
1.
ActivityThread
創建在main方法
中調用Looper.prepareMainLooper();
創建出Looper
,而后將創建的Looper
存于線程變量中(ThreadLocal
),再將主線程中的Looper
單獨存一份,因為他是主線程的Looper
。(實際它調用的是Looper.prepare()
,我們也可以在子線程中使用時,用它來創建Looper
)。2.在
main方法
的最后調用Looper.loop();
來開啟循環。
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop();
}
這便是,為什么handler
可以做異步的原因了,因為在主UI線程創建的時候,就早已為UI線程創建了一個Looper
,并開啟了循環。
注:請思考一個問題,能不能在子線程中直接new handler
發送消息?如果不可以?有沒有辦法解決?(切莫去搜一下看到某行代碼添加完就可以了便不管為什么這樣了,應當分析內部原理。)
handler如何收發消息
- 1.
new Handler()
時構造方法從Looper.myLooper();
獲取當前線程中的Looper
對象,然后取出MessageQueue
對象,以備后面發消息用。
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
- 2
handler
通過sendMessage(msg)
將消息發出,消息最終走向queue.enqueueMessage(msg, uptimeMillis);
這里的queque
便是我們前面從handler
構造方法中Looper
里取到消息隊列。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
- 3.
enqueueMessage方法
里其中還會將當前發消息的handler
存于msg
的target
中。當Looper
輪訓到這條消息時,便會使用到。我們往下再看一眼之前Looper.loop()
方法。最終調用了msg.target.dispatchMessage(msg);
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 取出消息,無消息則阻塞
if (msg == null) { return; }
msg.target.dispatchMessage(msg);//發送消息 其中target就是Handler
}
}
4.自此dispatchMessage()
中調用handleMessage(msg);
回調。消息就送達了。收工。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
再來看看這幅圖,按照我上面的思路流程在跟著圖走一走。看看我們分析得是否正確。
最后附一張以前學習Hanlder手寫筆記,其實這份筆記光看的話,可能對讀者沒什么很大做用。但對我的幫助很大。我要表達的是一個學習思路,像類似這種源碼分析最好自己拿筆寫寫畫畫,映象會深刻很多。知識過久了會忘記,光靠死記若非常人,很難過目不忘。自己寫一遍就完全不一樣了,就算過了許久已淡忘這些,打開自己的筆記看一眼就會明白。而不用從頭來學一遍。
用我的理解來解釋這種現象是學習的過程中可能坑坑洼洼,消磨掉不少時間。這種筆記會成為最后總結出來的結晶,與腦子里的印象流關聯在一起。何必再費力氣每次都從頭溫習,不如直接看以前自己的總結豈不快哉?
先生曰:小伙子長得眉目清新秀,這字真是丑得像雞爪子爬,各位受委屈了。
下一篇我們將分解HandlerThread的工作原理和做用。
本文參考:
AndroidFramework官方源碼
Handler 之 源碼解析
如何下次找到我?
- 關注我的簡書
- 本篇同步Github倉庫:https://github.com/BolexLiu/DevNote (可以關注)