概述
什么是Android消息機(jī)制
Android中的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制以及Handler所附帶的MessageQueue和Looper的工作過(guò)程,這三個(gè)實(shí)際上是一個(gè)整體。-
Handler的作用,為什么有Handler
Handler的主要作用:負(fù)責(zé)跨線程通信。因?yàn)樵谥骶€程不能做耗時(shí)操作,而子線程不能更新UI,所以當(dāng)子線程中進(jìn)行耗時(shí)操作后需要更新UI時(shí),通過(guò)Handler將有關(guān)UI的操作切換到主線程中執(zhí)行。
系統(tǒng)為什么不允許在子線程中訪問(wèn)UI?
因?yàn)锳ndroid的UI控件不是線程安全的,如果在多線程中并發(fā)訪問(wèn)可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)。
為什么系統(tǒng)不對(duì)UI控件的訪問(wèn)加上鎖機(jī)制呢?
加上鎖機(jī)制會(huì)讓UI訪問(wèn)的邏輯變得復(fù)雜
鎖機(jī)制會(huì)降低UI訪問(wèn)的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行。
鑒于這兩個(gè)缺點(diǎn),最簡(jiǎn)單且高效的方法就是采用單線程模型來(lái)處理UI操作。
- 幾個(gè)相關(guān)概念
Message:消息,被傳遞和處理的數(shù)據(jù)。其中包含了消息ID,消息處理對(duì)象以及處理的數(shù)據(jù)等,由MessageQueue統(tǒng)一列隊(duì),終由Handler處理。
Handler:處理者,負(fù)責(zé)Message的發(fā)送及處理。
Handler的主要作用:(有兩個(gè)主要作用)
1. Handler.sendMessage(),在工作線程中發(fā)送消息;
2. Handler.handleMessage(),在主線程中獲取、并處理消息。MessageQueue:消息隊(duì)列,本質(zhì)是一個(gè)單鏈表,用來(lái)存放Handler發(fā)送過(guò)來(lái)的消息,并按照FIFO規(guī)則執(zhí)行。當(dāng)然,存放Message并非實(shí)際意義的保存,而是將Message串聯(lián)起來(lái),等待Looper的抽取。
Looper:消息泵或循環(huán)器,通過(guò)Looper.loop()不斷從MessageQueue中抽取Message。因此,一個(gè)MessageQueue需要一個(gè)Looper。
Thread:線程,負(fù)責(zé)調(diào)度整個(gè)消息循環(huán),即消息循環(huán)的執(zhí)行場(chǎng)所。
一個(gè)Thread只能有一個(gè)Looper,一個(gè)MessageQueue,可以有多個(gè)Handler。
-
工作流程
handler工作流程.jpg
- Handler通過(guò)sendMessage()發(fā)送Message時(shí),Looper的成員變量MessageQueue會(huì)通過(guò)enqueueMessage()向MessageQueue中添加一條消息。
此時(shí)Message會(huì)將Handler對(duì)象賦值給Message對(duì)象的target參數(shù)。
Looper通過(guò)loop()方法開(kāi)啟循環(huán)后,不斷輪詢(xún)調(diào)用MessageQueue的next()方法來(lái)獲取Message。
-
通過(guò)調(diào)用Messag目標(biāo)Handler的dispatchMessage()方法去傳遞消息,目標(biāo)Handler收到消息后調(diào)用handleMessage()方法處理消息
簡(jiǎn)單來(lái)說(shuō),Handler通過(guò)sendMessage()方法將Message發(fā)送到Looper的成員變量MessageQueue中,之后Looper通過(guò)loop()方法不斷循環(huán)遍歷MessageQueue從中讀取Message,最終回調(diào)給Handler處理。
消息機(jī)制分析
- ThreadLocal
概念:
ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類(lèi),通過(guò)它可以在指定的線程中存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)以后,只有在指定線程中可以獲取到存儲(chǔ)的數(shù)據(jù),對(duì)于其他線程來(lái)說(shuō)則無(wú)法獲取到數(shù)據(jù)。-
使用場(chǎng)景:
- 一般來(lái)說(shuō),當(dāng)某些數(shù)據(jù)是以線程為作用域并且不同線程具有 不同的數(shù)據(jù)副本的時(shí)候,就可以考慮采用ThreadLocal。
- 復(fù)雜邏輯下的對(duì)象傳遞。
-
ThreadLocal 的set和get方法原理
-
set原理
Threadlocal.set -
get原理
Threadlocal.get
-
-
消息隊(duì)列
消息隊(duì)列(MessageQueue)主要包含了兩個(gè)操作:插入和讀取。盡管被稱(chēng)為隊(duì)列,但是它的內(nèi)部實(shí)現(xiàn)是通過(guò)一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)的消息列表,單鏈表在插入和刪除上比較有優(yōu)勢(shì)。
- 插入
插入的方法是enqueueMessage,作用是往消息隊(duì)列中插入一條消息。主要操作是單鏈表的插入操作。
插入的規(guī)則:
會(huì)根據(jù)message的延遲時(shí)間來(lái)進(jìn)行插入,無(wú)延遲的會(huì)放到鏈表的頭部,
有延遲的,延遲時(shí)間越長(zhǎng)越靠后
- 讀取
讀取對(duì)應(yīng)的方法是next,作用是從消息隊(duì)列中取出一條消息并將其從消息隊(duì)列中移除。next方法是一個(gè)無(wú)限循環(huán)的方法,如果消息隊(duì)列沒(méi)有消息,那么next方法會(huì)一直阻塞,當(dāng)有新的消息到來(lái)時(shí),next方法會(huì)返回這條信息并將其從單鏈表中移除。
-
Looper
Looper在Android的消息機(jī)制中扮演者消息循環(huán)的角色,具體來(lái)說(shuō),它會(huì)不停地從MessageQueue中查看是否有新的消息,如果有新消息就會(huì)立刻處理,否則就會(huì)一直阻塞在那里。
- Looper的使用
UI線程會(huì)自動(dòng)創(chuàng)建Looper,子線程需自行創(chuàng)建。
通過(guò)Looper.prepare()即可為當(dāng)前線程創(chuàng)建一個(gè)Looper。
//子線程中需要自己創(chuàng)建一個(gè)Looper
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//為子線程創(chuàng)建Looper
Handler handler = new Handler();
Looper.loop(); //開(kāi)啟消息輪詢(xún)
}
}).start();
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));
}
所創(chuàng)建的Looper會(huì)保存在ThreadLocal(線程本地存儲(chǔ)區(qū))中,它不是線程,作用是幫助Handler獲得當(dāng)前線程的Looper。
ThreadLocal.set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
獲取當(dāng)前線程,然后獲取當(dāng)前線程的ThreadLocalMap,通過(guò)map.set方法,將ThreadLocal和當(dāng)前的Lopper對(duì)象存到map中。
ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
所以一個(gè)線程只會(huì)有一個(gè)ThreadLocal和一個(gè)Lopper,并且Looper是唯一的,不可被替換的。
在這里我們看到了Looper的構(gòu)造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
我們看到它會(huì)創(chuàng)建一個(gè)MessageQueue,然后將當(dāng)前線程的對(duì)象保存起來(lái)。
- 除了prepare(),還提供prepareMainLooper(),本質(zhì)也是通過(guò)prepare(),主要是給主線程也就是ActivityThread創(chuàng)建Looper使用的。
- 由于主線程Looper比較特殊,所以Looper提供了一個(gè)getMainLooper的方法,通過(guò)它可以在任何地方獲取到主線程的Looper。
- 無(wú)論是主線程還是子線程,Looper只能被創(chuàng)建一次,即一個(gè)Thread只有一個(gè)Looper。
- Looper的退出
- quit:直接退出Looper
- quitSafely:只是設(shè)定一個(gè)退出標(biāo)記,然后把消息隊(duì)列中的已有消息處理完畢后才安全地退出。
在子線程中,如果手動(dòng)為其創(chuàng)建了Looper,那么在所有的事情完成以后應(yīng)該調(diào)用quit方法來(lái)終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于等待的狀態(tài),而如果退出Looper以后,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper。
-
loop方法原理
looper原理.jpg
-
Handler
Handler的主要工作包含消息的發(fā)送和接收過(guò)程。
- 發(fā)送有兩種方式 send 和 post
//send方式的Handler創(chuàng)建
Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//如UI操作
mTextView.setText(new_str);
}
};
//send
mHandler.sendEmptyMessage(0);
//post方式
private Handler mHandler = new Handler();
mHandler.post(new Runnable() {
@Override
public void run() {
mTextView.setText(new_str);
}
});
post方式最終還是通過(guò)一系列send方法實(shí)現(xiàn)的
public final boolean sendMessageAtFrontOfQueue(Message msg) {
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, 0);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
從上邊的代碼可以發(fā)現(xiàn),Handler發(fā)送消息的過(guò)程僅僅是向消息隊(duì)列插入了一條信息,MessageQueue的next方法就會(huì)返回這條信息給Looper,Looper收到消息后就開(kāi)始處理了,最終消息由Looper交給Handler處理,即Handler的dispatchMessage方法會(huì)被調(diào)用,這是Handler就進(jìn)入了處理消息的階段。
- 處理消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
最終會(huì)調(diào)用Handler的handleMessage方法來(lái)處理消息。
知識(shí)點(diǎn)延伸
-
Handler泄露的原因及正確寫(xiě)法
如果直接在Activity中初始化一個(gè)Handler對(duì)象,會(huì)報(bào)如下錯(cuò)誤:
This Handler class should be static or leaks might occur
原因是:
在Java中,非靜態(tài)內(nèi)部類(lèi)會(huì)持有一個(gè)外部類(lèi)的隱式引用,可能會(huì)造成外部類(lèi)無(wú)法被GC;
比如這里的Handler,就是非靜態(tài)內(nèi)部類(lèi),它會(huì)持有Activity的引用從而導(dǎo)致Activity無(wú)法正常釋放。
當(dāng)一個(gè)消息是一個(gè)延遲消息的時(shí)候,這個(gè)Message會(huì)一直存在MessageQueue中,因?yàn)镸essage的target對(duì)應(yīng)的就是Handler,然后Handler持有Activity的引用,所以就會(huì)導(dǎo)致了Activity無(wú)法被正常釋放。
解決方法:
將 Handler 定義成靜態(tài)的內(nèi)部類(lèi),在內(nèi)部持有Activity的弱引用,并在Acitivity的onDestroy()中調(diào)用handler.removeCallbacksAndMessages(null)及時(shí)移除所有消息。
private static class MyHandler extends Handler {
//創(chuàng)建一個(gè)弱引用持有外部類(lèi)的對(duì)象
private final WeakReference<MainActivity> content;
private MyHandler(MainActivity content) {
this.content = new WeakReference<MainActivity>(content);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity= content.get();
if (activity != null) {
switch (msg.what) {
case 0: {
activity.notifyUI();
}
}
}
}
}
-
Message可以如何創(chuàng)建?哪種效果更好,為什么?
- 直接生成實(shí)例Message m = new Message
- Message msg=Message.obtain();
- Message msg=handler.obtainMessage();
而后兩者是直接在消息池中取出一個(gè)Message實(shí)例,這樣做就可以避免多生成Message實(shí)例。
-
Looper死循環(huán)為什么不會(huì)導(dǎo)致應(yīng)用卡死?
- 主線程的主要方法就是消息循環(huán),一旦退出消息循環(huán),那么你的應(yīng)用也就退出了,Looer.loop()方法可能會(huì)引起主線程的阻塞,但只要它的消息循環(huán)沒(méi)有被阻塞,能一直處理事件就不會(huì)產(chǎn)生ANR異常。
- 造成ANR的不是主線程阻塞,而是主線程的Looper消息處理過(guò)程發(fā)生了任務(wù)阻塞(即Looper中有消息,但是消息沒(méi)有得到及時(shí)的處理),無(wú)法響應(yīng)手勢(shì)操作,不能及時(shí)刷新UI。
- 阻塞與程序無(wú)響應(yīng)沒(méi)有必然關(guān)系,雖然主線程在沒(méi)有消息可處理的時(shí)候是阻塞的,但是只要保證有消息的時(shí)候能夠立刻處理,程序是不會(huì)無(wú)響應(yīng)的。
為什么主線程可以直接new Handler()
因?yàn)樵贏ctivityMainThread的main()方法里調(diào)用了Looper.prepareMainLooper()方法。
在其他線程如果要使用Handler,必須要調(diào)用Looper.prepare()方法。子線程中維護(hù)的Looper,消息隊(duì)列無(wú)消息的時(shí)候的處理方案是什么?有什么用?
next()中的:nativePollOnce(ptr, nextPollTimeoutMillis); 阻塞線程
quit()中的:nativeWake(mPtr); 喚醒線程既然可以存在多個(gè)Handler往MessageQueue中添加數(shù)據(jù)(發(fā)消息時(shí)各個(gè)Handler可能處于不同線程),那它內(nèi)部是如何確保線程安全的?
這里主要關(guān)注 MessageQueue 的消息存取即可,看源碼內(nèi)部的話,在往消息隊(duì)列里面存儲(chǔ)消息時(shí),會(huì)拿當(dāng)前的 MessageQueue 對(duì)象作為鎖對(duì)象,這樣通過(guò)加鎖就可以確保操作的原子性和可見(jiàn)性了。
消息的讀取也是同理,也會(huì)拿當(dāng)前的 MessageQueue 對(duì)象作為鎖對(duì)象,來(lái)保證多線程讀寫(xiě)的一個(gè)安全性。



