今天來扒一扒Android的消息分發(fā)機制和多線程切換過程匠题,也就是我們常常看到用的Handler但金,Message(還有兩個不太看得到的MessageQueue和Looper)它們的原理韭山。額,當然有人會說還有AsyncTask這玩意兒,怎么說呢钱磅,反正我是基本沒用過AsyncTask巩踏,也沒見誰喜歡用這個,總感覺使用起來沒Handler+Message方便续搀,然而我用的更多的是Handler+Runnable塞琼,當然實際上Runnable還是被包裝成了Message。
先簡單介紹一下這幾個類吧:
-
Handler:
處理者禁舷,一般在主線程創(chuàng)建(在工作線程也可以創(chuàng)建彪杉,這個下一篇會詳細說到),處理各種線程發(fā)送過來的* Message牵咙,根據(jù)Message內(nèi)容在主線程做不同的處理派近。
-
Message:
消息體,在多線程中擔任一個內(nèi)容載體的角色洁桌,包含了消息的類型渴丸,參數(shù),數(shù)據(jù)等內(nèi)容另凌,其中還包括一個重要的對象谱轨,那就是它將會被發(fā)送給那個handler。
-
?MessageQueue:
消息隊列吠谢,所有發(fā)送給handler處理的消息都會保存在消息隊列中土童,其內(nèi)部使用鏈表的形式維護這些message。
-
Looper:
這個怎么說呢工坊,它的英文解釋為一個打環(huán)的裝置献汗,我也不知道該怎么翻譯,它的作用是可以讓線程一直活著王污,而不是執(zhí)行完一個功能代碼后就死掉了罢吃,每個Looper的實例有一個MessageQueue和當前線程對象,正是這兩個類讓線程可以一直活著昭齐,也就是Looper打環(huán)的裝置的意思吧尿招。我們的主線程就是因為它才可以一直執(zhí)行而不退出。
上面說到Looper使我們的主線程可以一直運行司浪,究竟是咋回事泊业,上代碼0颜印0∫住!
public static void main(String[] args) {
······
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
······
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
沒錯饮睬,就是我們的主線程初始化的地方租谈,也就是應用程序啟動的入口。我們的應用對于Android系統(tǒng)而言,說白了也就是一段程序代碼割去,那么程序啟動的時候當然得從啟動方法開始了窟却,它就是ActivityThread中大名鼎鼎的main()方法,是不是想起來java中的main()方法呻逆?我們來看一下這里到底做了哪些事夸赫。首先就是調(diào)用了Looper的靜態(tài)方法preoareMainLooper():
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
這個是源碼原封不動的貼上來的,包括注釋咖城,可以看到這個方法應該由系統(tǒng)調(diào)用茬腿,它根據(jù)當前線程初始化一個Looper對象,并且是應用的"main looper"宜雀,也就是方法名所表現(xiàn)的切平,接著看方法里的第一行調(diào)用。
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
這里有個sThreadLocal辐董,它是一個靜態(tài)屬性:static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();悴品,也就是全局就一個,每個線程最多對應一個Looper對象简烘,如果有苔严,Looper對象都會保存在sThreadLocal中,我們簡單的把它理解為一個Map就行孤澎,key為當前的線程邦蜜,value為一個Looper實例,因為線程(也就是key)在方法調(diào)用的時候內(nèi)部可以取到亥至,所以對它進行g(shù)et悼沈,set操作的時候不需要傳key,直接傳value就行姐扮,那么這個方法就很好理解了絮供,如果之前調(diào)用過這個方法,sThreadLocal.get()則不為null,直接拋異常(也就說明了一個線程最多只能對應一個Looper對象)茶敏,否則new一個Looper對象存到sThreadLocal中壤靶,Looper對象初始化的同時也初始化了一個MessageQueue,并且持有了當前線程的引用惊搏。
好贮乳,繼續(xù)看prepareMainLooper方法,判斷sMainLooper是否為null恬惯,第一次調(diào)用當然為null向拆,所以sMainLooper = myLooper();
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
很簡單,直接調(diào)用sThreadLocal.get()方法酪耳,很眼熟是吧浓恳,我們剛剛調(diào)用過它的set()方法,這個時候還在剛才的線程中呢,所以這里取出來的就是我們剛剛放進去的new Looper()颈将,可以稍微看一下這個過程中有個參數(shù)就quitAllowed梢夯,最終被傳到了MessageQueue的構(gòu)造器中,應該可以猜到它表示這個MessageQueue是否可以被退出或者說這個線程是否能被結(jié)束掉晴圾,當然因為我們的線程是主線程颂砸,所以傳false。目前為止我們已經(jīng)完成了prepareMainLooper()方法死姚,它所做的事其實很簡單沾凄,就是將我們的線程作為key在sThreadLocal中保存了一個不可退出的Looper對象,同時賦值給sMainLooper知允,為什么還要單獨定義一個sMainLooper呢撒蟀?首先sMainLooper是一個靜態(tài)屬性,且有一個getMainLooper()靜態(tài)方法直接返回sMainLooper,所以我猜測是因為主線程的Looper對象獲取比較頻繁温鸽,所以單獨作為一個屬性直接讀取保屯,省的每次從sThreadLocal中去取,以此減小開銷涤垫。
下面放一張圖作為prepareMainLooper的總結(jié)吧姑尺,對著圖看應該清晰很多:
回頭繼續(xù)看main()方法,接下去new了一個ActivityThread實例thread蝠猬,并且調(diào)用了attach()方法切蟋,然后將thread.getHandler()返回給sMainThreadHandler,這幾行代碼主要是初始化了我們的應用進程和Android系統(tǒng)的通信基礎(chǔ)榆芦,也就是Binder組件柄粹,使得我們的應用進程和Android系統(tǒng)能夠通信,這里我們只要知道個大概匆绣,就是Android系統(tǒng)會發(fā)送消息給我們的應用的ActivityThread(好吧驻右,其實是它的一個內(nèi)部類ApplicationThread),然后由ActivityThread包裝成Message放入sMainLooper的MessageQueue中等待執(zhí)行崎淳。如何執(zhí)行堪夭,請往下看......
再接下去是Looper.loop()方法
/**
* 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;
······
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
······
msg.target.dispatchMessage(msg);
······
msg.recycleUnchecked();
}
}
可以看到以上代碼邏輯也十分簡單,獲取當前線程的Looper對象拣凹,進而獲取Looper對象中的MessageQueue保存為queue森爽,然后進入死循環(huán),每次都從queue中取下一個Message嚣镜,得到Message后直接調(diào)用Message對象中保存的目標handler的dispatchMessage()方法爬迟,然后回收Message進入下一次循環(huán)。至此祈惶,我們的主線程才成為了真正意義上的主線程雕旨。上面說到Android系統(tǒng)會給我們應用發(fā)消息扮匠,然后消息被包裝成Message保存在MessageQueue中捧请,而在loop()方法中我們又去取下一個Message凡涩,是不是發(fā)現(xiàn)了什么?不錯疹蛉,我們應用中的各種系統(tǒng)方法的調(diào)用其實都是都將Android系統(tǒng)發(fā)送的消息包裝成Message保存到我們的應用主線程綁定的sMainLooper中的MessageQueue中活箕,然后由這個死循環(huán)從MessageQueue中取依次出Message消息進而執(zhí)行對應的方法,這樣一個完整的消息傳遞過程就打通了可款!
老樣子育韩,看圖:
我個人認為以上內(nèi)容已經(jīng)是Handler和Message完成多線程切換和消息傳遞的核心內(nèi)容了,主要也就是Looper類的用處和用法闺鲸,看懂了這些內(nèi)容再看整個流程會很輕松筋讨,所以如果還沒理解的話建議再看幾遍,或者直接去看看Looper的源碼更好摸恍。
其實還一個比較關(guān)鍵的步驟:如何從MessageQueue取Message悉罕,不過個人感覺就算這個過程對于理解整個消息傳遞機制影響不大,只要知道MessageQueue以鏈表的形式維護這每個進入隊列的Message就行了立镶。有興趣的小伙伴也可以看看源碼壁袄,我就在這里貼一下MessageQueue.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;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
本來只想寫一篇的,不過感覺好像有點長了媚媒,這樣的話就再寫一篇吧嗜逻,這篇就當做是理論知識的一個補充,接下去講講順著實際應用過程的線索缭召,一步步是如何將消息放入MessageQueue到最后執(zhí)行的~
Android消息分發(fā)及多線程切換之Handler栈顷、Message的細枝末節(jié)(二)http://www.reibang.com/p/a842b8d815d8