一擒抛、消息機(jī)制流程簡介
在應(yīng)用啟動的時候仁讨,會執(zhí)行程序的入口函數(shù)main()蝶俱,main()里面會創(chuàng)建一個Looper對象,然后通過這個Looper對象開啟一個死循環(huán),這個循環(huán)的工作是犀变,不斷的從消息隊列MessageQueue里面取出消息即Message對象妹孙,并處理。然后看下面兩個問題:
循環(huán)拿到一個消息之后获枝,如何處理蠢正?
是通過在Looper的循環(huán)里調(diào)用Handler的dispatchMessage()方法去處理的,而dispatchMessage()方法里面會調(diào)用handleMessage()方法省店,handleMessage()就是平時使用Handler時重寫的方法嚣崭,所以最終如何處理消息由使用Handler的開發(fā)者決定。
MessageQueue里的消息從哪來懦傍?
使用Handler的開發(fā)者通過調(diào)用sendMessage()方法將消息加入到MessageQueue里面雹舀。
上面就是Android中消息機(jī)制的一個整體流程,也是 “Android中Handler粗俱,Looper葱跋,MessageQueue,Message有什么關(guān)系源梭?” 的答案娱俺。通過上面的流程可以發(fā)現(xiàn)Handler在消息機(jī)制中的地位,是作為輔助類或者工具類存在的废麻,用來供開發(fā)者使用荠卷。
對于這個流程有兩個疑問:
- Looper中是如何能調(diào)用到Handler的方法的?
- Handler是如何能往MessageQueue中插入消息的烛愧?
這兩個問題會在后面給出答案油宜,下面先來通過源碼,分析一下這個過程的具體細(xì)節(jié):
二怜姿、消息機(jī)制的源碼分析
首先main()方法位于ActivityThread.java類里面慎冤,這是一個隱藏類,源碼位置:frameworks/base/core/java/android/app/ActivityThread.java
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");
}
Looper的創(chuàng)建可以通過Looper.prepare()來完成沧卢,上面的代碼中prepareMainLooper()是給主線程創(chuàng)建Looper使用的蚁堤,本質(zhì)也是調(diào)用的prepare()方法。創(chuàng)建Looper以后就可以調(diào)用Looper.loop()開啟循環(huán)了但狭。main方法很簡單披诗,不多說了,下面看看Looper被創(chuàng)建的時候做了什么立磁,下面是Looper的prepare()方法和變量sThreadLocal:
static final ThreadLocal<Looper> sThreadLocal = new 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));
}
很簡單呈队,new了一個Looper,并把new出來的Looper保存到ThreadLocal里面唱歧。ThreadLocal是什么宪摧?它是一個用來存儲數(shù)據(jù)的類粒竖,類似HashMap、ArrayList等集合類几于。它的特點(diǎn)是可以在指定的線程中存儲數(shù)據(jù)蕊苗,然后取數(shù)據(jù)只能取到當(dāng)前線程的數(shù)據(jù),比如下面的代碼:
ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();
private void testMethod() {
mThreadLocal.set(0);
Log.d(TAG, "main mThreadLocal=" + mThreadLocal.get());
new Thread("Thread1") {
@Override
public void run() {
mThreadLocal.set(1);
Log.d(TAG, "Thread1 mThreadLocal=" + mThreadLocal.get());
}
}.start();
new Thread("Thread2") {
@Override
public void run() {
mThreadLocal.set(2);
Log.d(TAG, "Thread1 mThreadLocal=" + mThreadLocal.get());
}
}.start();
Log.d(TAG, "main mThreadLocal=" + mThreadLocal.get());
}
輸出的log是
main mThreadLocal=0
Thread1 mThreadLocal=1
Thread2 mThreadLocal=2
main mThreadLocal=0
通過上面的例子可以清晰的看到ThreadLocal存取數(shù)據(jù)的特點(diǎn)孩革,只能取到當(dāng)前所在線程存的數(shù)據(jù),如果所在線程沒存數(shù)據(jù)得运,取出來的就是null膝蜈。其實(shí)這個效果可以通過HashMap<Thread, Object>來實(shí)現(xiàn),考慮線程安全的話使用ConcurrentMap<Thread, Object>熔掺,不過使用Map會有一些麻煩的事要處理饱搏,比如當(dāng)一個線程結(jié)束的時候我們?nèi)绾蝿h除這個線程的對象副本呢?如果使用ThreadLocal就不用有這個擔(dān)心了置逻,ThreadLocal保證每個線程都保持對其線程局部變量副本的隱式引用推沸,只要線程是活動的并且 ThreadLocal 實(shí)例是可訪問的;在線程消失之后券坞,其線程局部實(shí)例的所有副本都會被垃圾回收(除非存在對這些副本的其他引用)鬓催。更多ThreadLocal的講解參考:Android線程管理之ThreadLocal理解及應(yīng)用場景
好了回到正題,prepare()創(chuàng)建Looper的時候同時把創(chuàng)建的Looper存儲到了ThreadLocal中恨锚,通過對ThreadLocal的介紹宇驾,獲取Looper對象就很簡單了,sThreadLocal.get()
即可猴伶,源碼提供了一個public的靜態(tài)方法可以在主線程的任何地方獲取這個主線程的Looper(注意一下方法名myLooper()课舍,多個地方會用到):
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Looper創(chuàng)建完了,接下來開啟循環(huán)他挎,loop方法的關(guān)鍵代碼如下:
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對象,然后取得Looper中的消息隊列final MessageQueue queue = me.mQueue;
办桨,然后下面是一個死循環(huán)筹淫,不斷的從消息隊列里取消息Message msg = queue.next();
,可以看到取出的消息是一個Message對象呢撞,如果消息隊列里沒有消息贸街,就會阻塞在這行代碼,等到有消息來的時候會被喚醒狸相。取到消息以后薛匪,通過msg.target.dispatchMessage(msg);
來處理消息,msg.target 是一個Handler對象脓鹃,所以這個時候就調(diào)用到我們重寫的Hander的handleMessage()方法了逸尖。
msg.target 是在什么時候被賦值的呢?要找到這個答案很容易,msg.target是被封裝在消息里面的娇跟,肯定要從發(fā)送消息那里開始找岩齿,看看Message是如何封裝的。那么就從Handler的sendMessage(msg)方法開始苞俘,過程如下:
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到最后的enqueueMessage()方法中msg.target = this;
盹沈,這里就把發(fā)送消息的handler封裝到了消息中。同時可以看到吃谣,發(fā)送消息其實(shí)就是往MessageQueue里面插入了一條消息乞封,然后Looper里面的循環(huán)就可以處理消息了。Handler里面的消息隊列是怎么來的呢岗憋?從上面的代碼可以看到enqueueMessage()里面的queue是從sendMessageAtTime傳來的肃晚,也就是mQueue。然后看mQueue是在哪初始化的仔戈,看Handler的構(gòu)造方法如下:
public Handler(Callback callback, boolean async) {
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());
}
}
mLooper = Looper.myLooper();
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;
}
mQueue的初始化很簡單关串,首先取得Handler所在線程的Looper,然后取出Looper中的mQueue监徘。這也是Handler為什么必須在有Looper的線程中才能使用的原因晋修,拿到mQueue就可以很容易的往Looper的消息隊列里插入消息了(配合Looper的循環(huán)+阻塞就實(shí)現(xiàn)了發(fā)送接收消息的效果)。
以上就是主線程中消息機(jī)制的原理凰盔。
那么飞蚓,在任何線程下使用handler的如下做法的原因、原理廊蜒、內(nèi)部流程等就非常清晰了:
new Thread() {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
- 首先Looper.prepare()創(chuàng)建Looper并初始化Looper持有的消息隊列MessageQueue趴拧,創(chuàng)建好后將Looper保存到ThreadLocal中方便Handler直接獲取。
- 然后Looper.loop()開啟循環(huán)山叮,從MessageQueue里面取消息并調(diào)用handler的 dispatchMessage(msg) 方法處理消息著榴。如果MessageQueue里沒有消息,循環(huán)就會阻塞進(jìn)入休眠狀態(tài)屁倔,等有消息的時候被喚醒處理消息脑又。
- 再然后我們new Handler()的時候,Handler構(gòu)造方法中獲取Looper并且拿到Looper的MessageQueue對象锐借。然后Handler內(nèi)部就可以直接往MessageQueue里面插入消息了问麸,插入消息即發(fā)送消息,這時候有消息了就會喚醒Looper循環(huán)去處理消息钞翔。處理消息就是調(diào)用dispatchMessage(msg) 方法严卖,最終調(diào)用到我們重寫的Handler的handleMessage()方法。
三布轿、通過一些問題的研究加強(qiáng)對消息機(jī)制的理解
源碼分析完了哮笆,下面看一下文章開頭的兩個問題:
- Looper中是如何能調(diào)用到Handler的方法的来颤?
- Handler是如何能往MessageQueue中插入消息的?
這兩個問題源碼分析中已經(jīng)給出答案稠肘,這里做一下總結(jié)福铅,首先搞清楚以下對象在消息機(jī)制中的關(guān)系:
Looper,MessageQueue项阴,Message滑黔,ThreadLocal,Handler
- Looper對象有一個成員MessageQueue环揽,MessageQueue是一個消息隊列略荡,用來存儲消息Message
- Message消息中帶有一個handler對象,所以Looper取出消息后薯演,可以很方便的調(diào)用到Handler的方法(問題1解決)
- Message是如何帶有handler對象的撞芍?是handler在發(fā)送消息的時候把自己封裝到消息里的秧了。
- Handler是如何發(fā)送消息的跨扮?是通過獲取Looper對象從而取得Looper里面的MessageQueue,然后Handler就可以直接往MessageQueue里面插入消息了验毡。(問題2解決)
- Handler是如何獲取Looper對象的衡创?Looper在創(chuàng)建的時候同時把自己保存到ThreadLocal中,并提供一個public的靜態(tài)方法可以從ThreadLocal中取出Looper晶通,所以Handler的構(gòu)造方法里可以直接調(diào)用靜態(tài)方法取得Looper對象璃氢。
帶著上面的一系列問題看源碼就很清晰了,下面是知乎上的一個問答:
Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死狮辽?
原因很簡單一也,循環(huán)里有阻塞,所以死循環(huán)并不會一直執(zhí)行喉脖,相反的椰苟,大部分時間是沒有消息的,所以主線程大多數(shù)時候都是處于休眠狀態(tài)树叽,也就不會消耗太多的CPU資源導(dǎo)致卡死舆蝴。
- 阻塞的原理是使用Linux的管道機(jī)制實(shí)現(xiàn)的
- 主線程沒有消息處理時阻塞在管道的讀端
- binder線程會往主線程消息隊列里添加消息,然后往管道寫端寫一個字節(jié)题诵,這樣就能喚醒主線程從管道讀端返回洁仗,也就是說looper循環(huán)里queue.next()會調(diào)用返回...
這里說到binder線程,具體的實(shí)現(xiàn)細(xì)節(jié)不必深究性锭,考慮下面的問題:
主線程的死循環(huán)如何處理其它事務(wù)赠潦?
首先需要看懂這個問題,主線程進(jìn)入Looper死循環(huán)后草冈,如何處理其他事務(wù)祭椰,比如activity的各個生命周期的回調(diào)函數(shù)是如何被執(zhí)行到的(注意這里是在同一個線程下臭家,代碼是按順序執(zhí)行的,如果在死循環(huán)這阻塞了方淤,那么進(jìn)入死循環(huán)后循環(huán)以外的代碼是如何執(zhí)行的)甲雅。
首先再看main函數(shù)的源碼
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
在Looper.prepare和Looper.loop之間new了一個ActivityThread并調(diào)用了它的attach方法,這個方法就是開啟binder線程的诫硕,另外new ActivityThread()的時候同時會初始化它的一個H類型的成員雕蔽,H是一個繼承了Handler的類。此時的結(jié)果就是:在主線程開啟loop死循環(huán)之前讳苦,已經(jīng)啟動binder線程带膜,并且準(zhǔn)備好了一個名為H的Handler,那么接下來在主線程死循環(huán)之外做一些事務(wù)處理就很簡單了鸳谜,只需要通過binder線程向H發(fā)送消息即可膝藕,比如發(fā)送 H.LAUNCH_ACTIVITY 消息就是通知主線程調(diào)用Activity.onCreate() ,當(dāng)然不是直接調(diào)用咐扭,H收到消息后會進(jìn)行一系列復(fù)雜的函數(shù)調(diào)用最終調(diào)用到Activity.onCreate()芭挽。
至于誰來控制binder線程來向H發(fā)消息就不深入研究了,下面是《Android開發(fā)藝術(shù)探索》里面的一段話:
ActivityThread 通過 ApplicationThread 和 AMS 進(jìn)行進(jìn)程間通訊蝗肪,AMS 以進(jìn)程間通信的方式完成 ActivityThread 的請求后會回調(diào) ApplicationThread 中的 Binder 方法袜爪,然后 ApplicationThread 會向 H 發(fā)送消息,H 收到消息后會將 ApplicationThread 中的邏輯切換到 ActivityThread 中去執(zhí)行薛闪,即切換到主線程中去執(zhí)行辛馆,這個過程就是主線程的消息循環(huán)模型。
這個問題就到這里豁延,更多內(nèi)容看知乎原文
最后
和其他系統(tǒng)相同昙篙,Android應(yīng)用程序也是依靠消息驅(qū)動來工作的。網(wǎng)上的這句話還是很有道理的诱咏。
文章參考:
《Android開發(fā)藝術(shù)探索》
Android中為什么主線程不會因為Looper.loop()里的死循環(huán)卡死苔可?
Android線程管理之ThreadLocal理解及應(yīng)用場景
Android 消息機(jī)制——你真的了解Handler
Android Handler到底是什么