handler簡介
爲什麼需要handler
在介紹handler之前還得說說主線程。主線程運行所有的UI界面,設備會將用戶所有操作轉換爲消息並放入消息隊列中,然後主線程就位於一個循環中,在消息隊列取消息來一個個完成,每條消息處理用時不能超過5s,否則將ANR異常。所以對於超5s的任務都應該在子線程去完成,而子線程又沒有辦法對UI界面的內容進行操作,不然就報CalledFromWrongThreadException,於是就有了handler消息傳遞機制羅。那我們就可以在子線程進行耗時操作,然後把界面上需要的數據通過handler發送給主線程,然後主線程接受並處理,這樣就可以解決那些問題了。
再總結一下,handler是用來在各個線程之間發送數據,任何線程只要獲得另一個線程的handler,就可以通過handler向那個線程發送數據。
常用類
我們先大概瞭解一下他都設計到哪些常用的類,並且是負責做什麼。
- Message:消息。其中包含了消息ID,消息處理對象以及處理的數據等,由Handler處理。
- Handler:處理者。使用sendMessage()方法進行message的發送,最終由handleMessage()方法進行message處理。
- MessageQueue:消息隊列。用來存放Handler發送過來的消息,並按照FIFO規則執行。這裏的存放只是將Message串聯起來的,好讓Looper進行抽取。
- Looper:消息泵。使用Loop.loop()不斷地從MessageQueue中抽取Message執行。
- Thread:線程。負責整個消息循環的執行場所。
一個線程只會有一個MessageQueue和一個Looper,但可以有多個handler。
Handler主要成員變量
final Looper mLooper; //若構造方法沒傳此值,則Handler在哪個線程創建就與該線程的Looper綁定;若傳了就是傳進來的值
final MessageQueue mQueue; //mLooper的MessageQueue
final Callback mCallback; //內部接口,用來處理消息的
final boolean mAsynchronous;
Loop主要成員變量
/**
* Implements a thread-local storage, that is, a variable for which each thread
* has its own value. All threads share the same {@code ThreadLocal} object,
* but each sees a different value when accessing it, and changes made by one
* thread do not affect the other threads. The implementation supports {@code null} values.
* 這個是ThreadLocal類的官方解釋,大意就是它實現一個線程本地存儲變量,能讓每個線程擁有自己value。所有的線程可以共用同一個 ThreadLocal對象,並且每個線程可以通過這個對象獲取自己的value,而且在不影響其它線程的情況下修改value。
*/
//注意到這裏是static,則無論有多少Looper對象也只會有一個ThreadLocal對象,就像上面講的所有線程共用同一個ThreadLocal對象。通過sThreadLocal.get()得到各個線程的Looper對象(對應value),若子線程未調用Looper.prepare()就會Looper對象爲空。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // 主線程的Looper對象,android自動創建。
final MessageQueue mQueue; //與Looper對象關聯的消息隊列
final Thread mThread; //與Looper對象關聯的線程
Message的主要成員變量
long when; //消息發送的時間,也是根據它排隊進入消息隊列的,最早排最前
Handler target;//用來綁定發消息的handler對象,確保消息的執行也是此對象
Runnable callback; //若他不爲空,此消息分發成功就是處理callback任務,而不會讓handler處理了
Message next;//用來實現隊列這種鏈表結構
大體流程
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler() {
handleMessage(){}
};
Looper.loop();
}
}) ;
然後在另一個線程裏發消息。
詳細講解
- 第一步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));//爲每個線程設置自己的Looper對象
}
private Looper(boolean quitAllowed) {
//初始化成員變量
mQueue = new MessageQueue(quitAllowed);//爲每個Looper對象關聯一個MessageQueue對象
mThread = Thread.currentThread();//關聯此Looper對象所在線程
}
其實這個方法就是創建了個Looper對象,給Looper做了一些初始化操作,主要就是將Looper對象和所在線程綁定,初始化自己的MessageQueue。
2. 第二步創建handler對象
他有7個構造方法,我就只寫重點並只留構造方法裏的幾個主要地方吧。
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper(); //沒指定Looper的情況就會取Handler創建所在線程的這個Looper,在另一個構造方法可以指定Looper,那就綁定給定Looper的所在線程,
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.myLooper()方法
public static Looper myLooper() {
return sThreadLocal.get();//sThreadLocal前面有講,理解成單例吧,它能得到所在線程自己的value
}
其實這裏也就是給Handler對象進行初始化,綁定相應的Looper,也就相當於綁定了相應的MessageQueue和自己在的那個線程。
3. 第三步使用Handler對象發消息
有如下可發消息的方法
post(); postDelayed(); //把Runnable任務作爲消息加入到消息隊列
postAtTime();
sendEmptyMessage(); sendMessage(); sendMessageDelayed();
sendMessageAtTime();
這些方法最終都是運行sendMessageAtTime //指定時間爲uptimeMillis發送消息
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; //this是當前handler對象,要發送的消息msg與發送此msg的handler對象綁定了
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
//MessageQueue的enqueueMessage方法,根據when把消息加入消息隊列,只複製了重要代碼。
boolean enqueueMessage(Message msg, long when) {
msg.markInUse();
msg.when = when; //此消息要發送的時間
Message p = mMessages; //mMessages屬性代表MessageQueue的第一條消息
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果MessageQueue裏沒有消息,或者此消息執行時間小於等於0,則都將此消息設爲頭指針,也就是作爲第一條消息
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) { //將消息根據時間when早晚找到此消息需要插入隊列的位置
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // 消息插入
prev.next = msg;
}
}
return true;
}
不管handler以何種方式發消息,最終都會把這些消息根據msg.when,運行MessageQueue的enqueueMessage()存入消息隊列,這個消息隊列是在與此handler綁定的Looper對象裏面。
4.第四步Looper.loop()取消息;
public static void loop() {
final Looper me = myLooper(); //獲取所在線程的Looper對象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) { // 沒有消息就代表消息隊列已經取完了.
return;
}
***********msg.target.dispatchMessage(msg);//把從隊列取出的消息分發出去
msg.recycleUnchecked();//回收消息
}
}
//msg.target.dispatchMessage(msg)方法,Handler的方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); //如果msg的callback不爲空就代表這個消息有自己的任務,於是運行此方法,此方法實際就是msg.callback.run();
} else { //msg.callback爲空那就終於可以運行handleMessage了,你可以自己重寫它實現自己的需求
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
從此線程的Looper裏的MessageQueue裏取消息,取出一條就發送一條msg.target.dispatchMessage(msg),然後就可以相應的處理這些消息了。
常見問題
1.主線程中爲什麼沒有看到Looper對象?
Looper對象就是用來綁定所在線程,爲線程開啓一個消息循環,從而操作MessageQueue。默認情況下系統自動爲主線程創建Looper對象,開啓消息循環。因此主線程我們只會看到new Handler。而子線程就不行了。
2.handlerA發送的消息可以handlerB來處理嗎?
不可以。
不知你會不會有這樣疑問,不是一個線程只會有一個Looper對象一個MessagerQueue,可以有多個handler嘛,那說明多個handler共用一個MessagerQueue,這樣的話就隨便哪個handler發送的消息都在一個隊列裏羅,對的這都沒錯,那麼handlerA發的消息在這個隊列,handlerB用的也是這個隊列,那麼就能處理羅。其實若真正搞懂了這個機制也不會有這個問題,看看我的第三步發消息和第四步取消息裏在前面加了一串 * 的代碼,handlerA發消息時msg.target已經是handlerA了,而取消息時`msg.target.dispatchMessage(msg);則運行handlerA的dispatchMessage()方法,所以消息最終只會調用handlerA的相應處理消息的方法,(當然也可能會是消息本身處理掉)不管怎樣都跟handlerB沒關係。
3.使用handler在子線程發消息,主線程處理消息,爲什麼子線程不需要Loope對象呢?
其實只要確保取消息和存消息在同一個線程完成就行。表面上發消息是在子線程處理的,但實際發出的消息放入的隊列是根據此handler對象在創建時的線程決定的,而不是發送消息時的線程決定,那麼發消息時,給存放消息的隊列是主線程,而取消息也是主線程,主線程默認執行Loop.loop。簡單的說就是Handler對象在哪個線程創建哪個線程就得有Loop對象,除了主線程,它是默認創建了的。
4.主線程運行handler.post()這個方法裏面傳Runnable對象,裏面的run()方法是在哪個線程運行?
這是昨天我跟別人吹牛時他問的我一個問題,我當時覺得問的就有問題,於是補充了句handler是否是在主線程創建的,(其實準確說應該是handler創建時綁定的Looper對象是否是主線程的)若是那run()就在主線程運行。因爲Runnable都是當作一條消息放進隊列的嘛,並由與之相關的handler來處理,那消息隊列是在哪個線程,Looper就在哪個線程,而handler又與他們綁定了,那run方法就運行在哪個線程羅。所以就是主線程。但沒有底氣回答,總覺得哪裏有點不對,那就再來看看源碼吧。
運行handler.post(r);
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);//這裏就是以前說的把它作爲一條消息發出去嗎,重點我們再看看getPostMessage(r)
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;//原來message的callBack屬性在這種情況賦值的。
return m;
}
再重點看看handler消息分發方法的這幾行代碼
if (msg.callback != null) {//看到沒,前面我post(r)時把這個callBack賦值了,所以不爲空,那就運行下一句了
handleCallback(msg); //此方法實際就是msg.callback.run();終於看到run()方法運行了,那這個方法不就是運行於與handler綁定的那個線程,因爲這都是在Loop.loop()裏執行的操作。
所以之前我的大致想法是對的,run()方法是由與之綁定的handler處理,但當時不知道他最終是會去調用messaged的callBack的,現在算是對這條屬性也有進一步理解了。
最後,你若有關於handler的其它疑問,歡迎在評論裏諮詢。