前言
在看過了網(wǎng)上那么多的Android的異步消息處理機制的文章之后傲隶,總是覺得不夠系統(tǒng),要么是copy來copy去的代碼窃页,要么是凌亂的結(jié)構(gòu)跺株,讓人看的云里霧里的复濒,也可能是沒有看懂并轉(zhuǎn)化成自己的東西。官方文檔也沒有詳細的去解釋其之間的關(guān)系乒省,只有的類注釋巧颈,然后我們就只能Read the Fucking Source Code !其實我們只要把官方文檔的解釋加上源碼就能理清這個機制的原理袖扛。
PS:在此強調(diào)一點就是砸泛,去學習android的東西,一定要看官方文檔蛆封,官方文檔才是最原汁原味的唇礁,網(wǎng)上的文章都是別人消化過的,不是你的理解惨篱,所以一定要去官網(wǎng)都挨個擼一遍垒迂,做到心中有數(shù),這樣以后查起文檔就有的放矢了妒蛇。
1. Android的線程
在說異步消息處理機制之前,一定要先去看一下這篇文章《Android的單線程》楷拳,只有看了這篇文章你才知道為什么會有異步消息處理機制绣夺。
2. 異步消息處理機制類
首先我們要了了解一下這幾個類:
- Handler
- Looper
- Message
- MessageQueue
在介紹之前,我想大家最一開始使用的都是從Handler欢揖,即使沒看過源碼陶耍,也知道大概的使用方式了,所以使用方法這里我不做贅述她混,下面一一介紹:
Handler
我按照官方文檔英文翻譯一下烈钞,如果大家英語好的話也可以去原文地址去看:
一個Handler允許你發(fā)送或處理Message和Runnable對象到線程的消息隊。每一個Handler實例都和一個單一的線程相關(guān)聯(lián)坤按,就是消息隊列所在的線程毯欣。當你創(chuàng)建一個新的Handler對象,從那時起臭脓,它就會綁定到線程所創(chuàng)建的消息隊列中酗钞,它會發(fā)送消息或者Runnable接口到消息隊列,當他們從消息隊列出來之后便依次執(zhí)行来累。
然后Handler有2種主要的使用:
- 執(zhí)行Message對象和在未來特定時刻執(zhí)行Runnable接口砚作;
- sendMessage(Message)
- sendEmptyMessage(int)
- sendEmptyMessageDelayed(int,long)
- sendEmptyMessageAtTime(int)
- sendMessageDelayed(int,long)
- sendMessageAtTime(int,long)
- sendMessageAtFrontOfQueue(Message)
- 在一個不同的線程加入一個動作使之執(zhí)行而不是你自己去實現(xiàn)。
- post(Runnable)
- postAtTime(Runnable,long)
- postAtTime(Runnable,object,long)
- postDelayed(Runnable,long)
- postAtFrontOfQueue(Runnable)
然后我們在Handler.handleMessage Callback中處理回傳回來的消息嘹锁。
Looper
Looper這個類大家肯定也很熟悉葫录,還記得你第一次使用Looper,拋出RuntimeException :"Only one Looper may be created per thread"嗎领猾?沒錯就是你在某一個已經(jīng)綁定了一個Looper對象的線程中調(diào)用Looper.prepare()時產(chǎn)生的米同。
下面看一段典型的代碼:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}}
Looper的作用其實就是一個消息循環(huán)器骇扇,它不停的從消息池中取出消息,然后在當前線程中分發(fā)消息窍霞。但是使用的時候一定要注意匠题,要綁定到一個線程,先調(diào)用prepare再調(diào)用loop但金。
那么Looper.prepare()究竟做了什么韭山,我們看源碼:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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));
}
首先我們看第一個函數(shù)prepare()的注釋:
注釋:Initialize the current thread as a looper.This gives you a chance to create handlers that then reference
this looper, before actually starting the loop. Be sure to call loop after calling this method, and end it by
calling quit翻譯:初始化當前的線程作為一個looper對象。在真正的開始消息循環(huán)之前冷溃,給你機會去創(chuàng)建一個或多個handler來指向到這個looper對象钱磅。但是要確保當調(diào)用完這個方法之后一定要調(diào)用Loop方法,然后調(diào)用quit來結(jié)束它似枕。
然后我們再看第二個函數(shù)的具體實現(xiàn)盖淡,我們看到了有一個類ThreadLocal貌似我們從未見過,也沒有在實際項目當中使用過凿歼,但是如果有的同學如果看過《JAVA并發(fā)編程實戰(zhàn)》第三章節(jié)中的對象的共享褪迟,應(yīng)該會有印象。
不過沒關(guān)系答憔,我們還是用過類注釋來了解它的作用:
注釋:Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same 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 null values.
翻譯:實現(xiàn)了一個線程局部存儲味赃,就是每一個線程都有自己對應(yīng)的值。所有的線程共享ThreadLocal對象虐拓,但
是當訪問的時候心俗,每一個線程會看到不同的值。一個線程的改變不會影響其他線程蓉驹。這種實現(xiàn)還支持存儲null值城榛。
然后我們在看ThreadLocal所包含的方法:
發(fā)現(xiàn)和WeakReference的結(jié)構(gòu)很像!然后我們繼續(xù)看下源碼:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
定位到ThreadLocalMap,在看下源碼:
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
...下面代碼省略
}}
看到?jīng)]态兴,原來是ThreadLocal內(nèi)部使用弱引用WeakReference來存儲當前線程對象狠持,以ThreadLocal為Key,value為Object瞻润。另外值得注意的一點是工坊,上面的Entry繼承自WeakReference,這樣的好處就是能夠及時回收Looper中創(chuàng)建的ThreadLocal對象敢订。
接下來是Looper.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;
}
//...中間省略代碼
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//...中間省略代碼
msg.recycleUnchecked();
}
}
首先第一行,Looper.myLooper():
public static Looper myLooper() {
return sThreadLocal.get();
}
也就是從sThreadLocal靜態(tài)對象中取出在調(diào)用Looper.prepare函數(shù)時存放的Looper對象楚午。取出之后昭齐,調(diào)用Looper中的已經(jīng)初始化好的對象MessageQueue取出消息池中的消息Message進行操作,取出之后就要將消息進行分發(fā)了矾柜,此時調(diào)用Message.target.dispatchMessage(msg)阱驾,追溯到源碼:
public final class Message implements Parcelable {
Handler target;
}
原來Message內(nèi)部持有一個Handler對象就谜,那么這個對象是在什么時候進行賦值的呢,繼續(xù)看Handler源碼:
public class Handler {
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}}
msg.target = this
就是這句里覆!
我們搞明白了Handler在什么時候傳遞給Message之后丧荐,我們就明白消息是怎么回調(diào)的了,下面看dispatchMessage(Message msg)
函數(shù)的實現(xiàn):
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
這里有if else判斷是如果回調(diào)函數(shù)不為空喧枷,則處理回調(diào)函數(shù)虹统,否則處理消息,很簡單隧甚,這里其實再次強調(diào)了Handler兩種主要用法车荔,sendMessage & post Runnable接口。
Message & MessageQueue
由于MessageQueue實際就是操作的Message對象戚扳,所以把他們結(jié)合在一起來說忧便。
剛才講到Handler有sendMessage(message)功能,看下源碼帽借,原來是Handler把Message對象發(fā)送到MessageQueue中珠增,下面看個流程圖:
最終定位到了下面的代碼:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
queue.enqueueMessage(msg, uptimeMillis),繼續(xù)看MessageQueue里面的這個函數(shù)
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//此處省略若干代碼...
Message p = mMessages;
boolean needWake;
synchronized (this) {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
return true;
}
我們不需完全看懂完整的代碼砍艾,我們只看核心的部分切平,首先for循環(huán)部分是在不停的取出消息,然后取出消息調(diào)用了nativeWake函數(shù)辐董,查看源碼后發(fā)現(xiàn):
private native static void nativeWake(long ptr);
是本地的實現(xiàn),我們也不需要care(當然如果你有興趣可以去查看源碼)禀综,只需要知道這個函數(shù)是喚醒消息隊列再次處理消息即可简烘,而整個的消息的循環(huán)是采用Sleep-Wakeup機制。也就是說當隊列中沒有消息的時候定枷,并不會不停的去取消息孤澎,而是進行休眠,休眠的意思大家想想自己的電腦就明白了欠窒,當再次點擊電源鍵時覆旭,能夠立刻喚醒進行工作,此處消息隊列的處理也是一樣岖妄,當消息隊列中有消息的時候調(diào)用nativeWake來喚醒Looper線程型将。
另外值得注意的是,Message內(nèi)部是采用消息池的機制荐虐,這樣在獲取的消息的時候回優(yōu)先從消息池中取出可用的消息對象七兜,如果沒有再進行初始化,這樣的好處就避免了不必要的內(nèi)存開銷福扬。另外腕铸,Message類也提供了上述代碼靜態(tài)工廠方法Message.obtain()惜犀。
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
所以大家在使用的時候最好使用Message.obtain()
來構(gòu)造Message對象。
3. 異步消息處理機制
說到這里狠裹,可以用一張圖來總結(jié)整個的流程:
經(jīng)過以上的幾個關(guān)鍵的類的介紹虽界,原理也大概明白了,其中間過程發(fā)生了什么涛菠,在什么時候發(fā)生莉御,大家可以在源碼中仔細查看。最后總結(jié)一下:
- Android應(yīng)用程序的消息處理機制由消息循環(huán)碗暗、消息發(fā)送和消息處理三個部分組成的颈将;
- Android應(yīng)用程序的消息處理內(nèi)部是使用ThreadLocal來將一個Looper對象綁定到一個線程上;
- Android應(yīng)用程序的主線程進入空閑等待狀態(tài)時不處理消息言疗,來了消息之后利用本地nativeWake函數(shù)來進行喚醒晴圾;
- Android應(yīng)用程序的消息采用消息池機制來存取消息国觉。