Android是基於事件驅動,每一個觸摸事件或者是Activity的生命週期都是運行在Looper.looper()的控制之下,理解弄懂消息機制可以讓我們在開發的過程中更加得心應手。
Android的消息機制也是Handler機制,主要的作用是用來在不同線程之間的通信,通常使用在子線程執行完成一些耗時操作,需要回到主線程更新界面UI時,通過Handler將有關UI的操作切換到主線程。
工作流程
先來大致梳理下整個流程:
- 應用程序啓動的時候,在主線程中會默認調用了 Looper.preper()方法,初始化Looper對象並綁定到當前線程中,並在Looper內部維護一個MessageQueue
- 接着調用handler.sendMessage()發送消息,會通過MessageQueue.enqueueMessage()向MessageQueue中添加一條消息
- 主線程調用Looper.looper()開啓循環,不斷輪詢消息隊列,通過MessageQueue.next()取出消息
- 取出的message不爲空則調用msg.target.dispatchMessage()傳遞分發消息,目標handler收到消息後會執行handler.handlerMessage()方法處理消息
詳細分析
Looper
Looper的字面意思是“循環者”,它被設計用來使一個普通線程變成Looper線程 。所謂Looper線程就是執行循環工作的線程
Looper的創建(Looper.prepare())
在應用程序的入口ActivityThread.main()方法中系統已經幫我們創建好Looper對象
//主線程中不需要自己創建Looper
public static void main(String[] args) {
......
Looper.prepareMainLooper();//爲主線程創建Looper,該方法內部又調用 Looper.prepare()
......
Looper.loop();//開啓消息輪詢
......
}
子線程中的Looper是需要我們自己手動創建的
public class LooperThread extends Thread {
@Override
public void run() {
// 將當前線程初始化爲Looper線程
Looper.prepare();
// ...其他處理,如實例化handler
// 開始循環處理消息隊列
Looper.loop();
}
}
這樣你的線程就可以成爲Looper線程
在Looper的構造方法中還創建了MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- 無論是主線程還是子線程,Looper只能被創建一次,即一個Thread只有一個Looper。
- 所創建的Looper會保存在ThreadLocal(線程本地存儲區)中,它不是線程,作用是幫助Handler獲得當前線程的Looper。(ThreadLocal可以在不同的線程之中互不干擾地存儲並提供數據,通過ThreadLocal可以輕鬆獲取每個線程的Looper)
Looper.prepare()方法的作用
1.將當前線程變成Looper線程,在其內部維護一個消息隊列MQ
2.創建Looper對象並將Looper對象定義爲ThreadLocal
Looper.loop()
調用Looper.loop()後,Looper線程就開始真正的工作了。該方法是一個阻塞性的死循環,它不斷輪詢自己的MQ,並從中取出隊頭的消息執行。
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;
// 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 從MQ中取出消息
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
// 將真正的處理工作交給message的target(handler)處理分發消息
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
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.myLooper()得到當前線程looper對象:
public static @Nullable Looper myLooper() {
// 在任意線程調用Looper.myLooper()返回的都是那個線程綁定的looper
return sThreadLocal.get();
}
Handler
handler扮演了往MQ上添加消息和處理消息的角色(只處理由自己發出的消息),即通知MQ它要執行一個任務(sendMessage),並在loop到自己的時候執行該任務(handleMessage),整個過程是異步的。handler創建時會關聯一個looper,默認的構造方法將關聯當前線程的looper,如果當前線程還沒有初始化Looper,或者說當前線程還不是looper線程,會報RuntimeException,不過這也是可以set的。
public class handler {
final MessageQueue mQueue; // 關聯的MQ
final Looper mLooper; // 關聯的looper
final Callback mCallback;
// 其他屬性
public Handler() {
// 沒看懂,直接略過,,,
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());
}
}
// 默認將關聯當前線程的looper
mLooper = Looper.myLooper();
// looper不能爲空,即該默認的構造方法只能在looper線程中使用
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// 重要!!!直接把關聯looper的MQ作爲自己的MQ,因此它的消息將發送到關聯looper的MQ上
mQueue = mLooper.mQueue;
mCallback = null;
}
// 其他方法
}
Handler發出的message有如下特點
1.message.target爲該handler對象,這確保了looper執行到該message時能找到處理它的handler,即loop()方法中的關鍵代碼
msg.target.dispatchMessage(msg);
2.post發出的message,其callback爲Runnable對象
// 此方法用於向關聯的MQ上發送Runnable對象,它的run方法將在handler關聯的looper線程中執行
public final boolean post(Runnable r)
{
// 注意getPostMessage(r)將runnable封裝成message
return sendMessageDelayed(getPostMessage(r), 0);
}
Handler處理消息
// 處理消息,該方法由looper調用
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
// 如果message設置了callback,即runnable消息,處理callback!
handleCallback(msg);
} else {
// 如果handler本身設置了callback,則執行callback
if (mCallback != null) {
/* 這種方法允許讓activity等來實現Handler.Callback接口,避免了自己編寫handler重寫handleMessage方法。 */
if (mCallback.handleMessage(msg)) {
return;
}
}
// 如果message沒有callback,則調用handler的鉤子方法handleMessage
handleMessage(msg);
}
}
// 處理runnable消息
private final void handleCallback(Message message) {
message.callback.run(); //直接調用run方法!
}
// 由子類實現的鉤子方法
public void handleMessage(Message msg) {
}
重點
1.Handler 可以在任意線程中發送消息,這些消息會被添加到關聯的MQ上
2.Handler是在它關聯的Looper線程(Looper綁定的線程)中處理消息的
總結
1.handler可以在任意線程發送消息,這些消息會被添加到關聯的MQ上。
2.handler是在它關聯的looper線程中處理消息的。(handlerMessage()方法運行所在的線程是根據handler創建的時候綁定的Looper線程,和綁定的Looper所在的線程一致)
3.Android的主線程也是一個looper線程,我們在其中創建的handler默認將關聯主線程MQ
Message
message又叫task,封裝了任務攜帶的信息和處理該任務的handler
注意事項:
1.儘管Message有public的默認構造方法,但是你應該通過Message.obtain()來從消息池中獲得空消息對象,以節省資源。
2.如果你的message只需要攜帶簡單的int信息,請優先使用Message.arg1和Message.arg2來傳遞信息,這比用Bundle更省內存
3.擅用message.what來標識信息,以便用不同方式處理message。
爲了能夠更好的理解掌握消息機制,以下有幾道面試題
1.爲什麼一個線程只有一個Looper、只有一個MessageQueue?
因爲線程對應的Looper是在ThreadLocal裏面存儲,它是一個線程內部的數據存儲類,通過它可以在指定的線程中存儲數據,數據存儲以後,只有在指定線程中可以獲取到存儲的數據,對於其它線程來說無法獲取到數據。ThreadLocal它的作用是可以在不同的線程之中互不干擾地存儲並提供數據(就相當於一個Map集合,鍵位當前的Thead線程,值爲Looper對象)。另外,在looper創建的方法looper.prepare()中,會有一個判斷如果當前線程存在Looper對象,就會報RunTimeException,所以一個線程只有一個Looper,而MQ作爲Looper的成員變量自然也就只有一個。
2.如何獲取當前線程的Looper?是怎麼實現的?(理解ThreadLocal)
3.是不是任何線程都可以實例化Handler?有沒有什麼約束條件?
任何線程都可以實例化Handler,handler創建時會關聯一個looper,默認的構造方法將關聯當前線程的looper,如果當前線程還沒有初始化Looper,或者說當前線程還不是looper線程,會報RuntimeException。
4.Looper.loop是一個死循環,拿不到需要處理的Message就會阻塞,那在UI線程中爲什麼不會導致ANR?
在應用程序的入口ActivityThread裏面的main方法中會創建一個主線程的looper對象和一個大Handler,(這也是爲什麼直接在主線程拿Handler就有Looper的原因,在其他線程是要自己Looper.prepare())
Android是基於事件驅動的,通過looper.looper()不斷接收事件,處理事件,每一個觸摸事件或者是Activity的生命週期都是運行在Looper.looper()的控制之下,當收到不同Message時則採用相應措施:在H.handleMessage(msg)方法中,根據接收到不同的msg,執行相應的生命週期。如果它停止了,應用也就停止了。也就是說我們的代碼其實就是運行在這個循環裏面去執行的,當然就不會阻塞。
而所謂ANR便是Looper.loop沒有得到及時處理,一旦沒有消息,Linux的epoll機制則會通過管道寫文件描述符的方式來對主線程進行喚醒與睡眠,Android裏調用了linux層的代碼實現在適當時會睡眠主線程。
拓展
消息循環(死循環)的必要性:
對於線程既然是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,我們是絕不希望會被運行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出
ActivityThread的main方法主要就是做消息循環,一旦退出消息循環,那麼你的應用也就退出了。如果main方法中沒有looper進行循環,那麼主線程一運行完畢就會退出。
主線程在沒有事件需要處理的時候就是處於阻塞的狀態。想讓主線程活動起來一般有兩種方式:
第一種 :是系統喚醒主線程,並且將點擊事件傳遞給主線程;
第二種 :是其他線程使用主線程的Handler向MessageQueue中存放了一條消息,導致loop被喚醒繼續執行。
總結
Looer.loop()方法可能會引起主線程的阻塞,但只要它的消息循環沒有被阻塞,能一直處理事件就不會產生ANR異常。
looper.looper()阻塞會不會消耗大量的cpu資源
主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,並且往管道文件寫數據,主線程即被喚醒,從管道文件讀取數據,主線程被喚醒只是爲了讀取消息,當消息讀取完畢,再次睡眠,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生。因此loop的循環並不會對CPU性能有過多的消耗。
5.Handler.sendMessageDelayed()怎麼實現延遲的?結合Looper.loop()循環中,Message=messageQueue.next()和MessageQueue.enqueueMessage()分析。
Handler.sendMessageDelayed()內部調用sendMessageAtTime()把傳入的時間轉化成絕對時間when(延時的時間加上系統當前的時間),然後調用MessageQueue的enqueueMessage(),採用線程安全的方式將Message插入到消息隊列中,消息隊列的插入是由when順序排列,插入的新消息有三種可能成爲消息隊列的head:
(1)消息隊列爲空;
(2)參數when爲0,因爲此時when已經轉成絕對時間,所以只有AtFrontOfQueue(sendMessageAtFrontOfQueue直接把消息插入到隊列的頭部)系列的API纔會滿足這個條件;
(3)當前的head Message執行時間在when之後,即消息隊列中無需要在此Message之前執行的Message。
接着就是Looper.looper()啓動消息循環,循環開始調用messageQueue.next()從消息隊列中取出一個合理的消息。如果next()返回null,則looper()直接return,本次消息循環結束。如果消息不爲空則調用msg.target.dispatchMessage(msg)處理消息(msg.target就是Handler)
.next()取下一個消息的實際執行時間取決於上一個消息什麼時候處理完
在MessageQueue.next()中,如果在消息隊列中順序找到了一個消息msg(前文分析過,消息隊列的插入是由when順序排列,所以如果當前的消息沒有到執行時間,其後的也一定不會到),當前的系統時間小於msg.when,那麼會計算一個timeout,以便在到執行時間時wake up;如果當前系統時間大於或等於msg.when,那麼會返回msg給Looper.loop()。所以這個邏輯只能保證在when之前消息不被處理,不能夠保證一定在when時被處理。
(1)在Loop.loop()中是順序處理消息,如果前一個消息處理耗時較長,完成之後已經超過了when,消息不可能在when時間點被處理。
(2)即使when的時間點沒有被處理其他消息所佔用,線程也有可能被調度失去cpu時間片。
(3)在等待時間點when的過程中有可能入隊處理時間更早的消息,會被優先處理,又增加了(1)的可能性。
所以由上述三點可知,Handler提供的指定處理時間的api諸如postDelayed()/postAtTime()/sendMessageDelayed()/sendMessageAtTime(),只能保證在指定時間之前不被執行,不能保證在指定時間點被執行。
參考
https://blog.csdn.net/zhanglianyu00/article/details/70842494
http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html
https://www.jianshu.com/p/1c79fb5296b6