文章目錄
異步消息處理機制
異步消息處理機制概述
1.作用:跨線程間消息傳遞。一般用於開啓子線程執行耗時操作後,需要在執行結束更新UI線程。主線程的UI控件非線程安全,因此android系統不允許直接在子線程更新UI。
2.四元素
異步消息處理機制中主要包括Handler、Looper、MessageQueue、Message。
Handler:消息的處理者與發送者。通過sendmessage
可以發送消息;通過handlemessage
進行處理信息。
MessageQueue:信息隊列,內部其實是一個單鏈表,只負責存儲消息,不負責發送消息。
Looper:消息泵,通過loop
方法持續的從消息隊列取出消息,併發送至消息對應的Handler。
Message:消息。用來存儲一定的消息,並在線程中傳遞。具有msg.what字段,以此區分不同Message。
3.關係:
1.一個線程只可以有一個Looper,因爲Looper類中有一個靜態變量ThreadLocal,線程每創建一個新looper時,
都會將這個looper set到ThreaLocal中,ThreadLocal可以通過線程來找到對應looper。
如果一個線程有多個looper就無法找到對應的拿一個Looper了。
2.一個線程可以有多個Handler
3.一個Looper中只有一個MessageQueue
4.Handler可以根據線程找到對應的Looper。
5.MessageQueue中存放多個Message,這些Message可以來自多個Handler。
注意:Handler可以在任意線程發送消息,這些消息會發送到Handler所關聯的MessageQueue。Handler在處理時是在它關聯的Looper線程中處理消息的。
舉個例子:我們首先創建一個Handler對象handler1,其關聯的Looper就是主線程的looper1,我們在子線程使用handler1發送信息,信息會發送到looper1的messageQueue中。looper1將信息發送給handler1,handler1在主線程執行UI操作。
異步消息處理機制的工作流程
1.當Handler對象使用sendMessage
發送message後,會調用handler所綁定的looper實例中的MessageQueue的enqueueMessage
方法,將message添加進MessageQueue。
2.Looper的loop
方法會持續調用MessageQueue的next
方法去取出消息。取出消息後,會調用msg對應的Handler的dispatchMessage
方法。
3.在dispatchMessage
方法中,會調用handlemessage
方法,進行UI操作。
異步消息處理機制的具體原理
1.ThreadLocal工作原理
當某些數據以線程爲作用域,且不同線程具有不同的數據副本時,我們使用ThreadLocal。
ThreadLocal和信息機制的關係在於,Looper類中有一個靜態變量,這個靜態變量的類型就是ThreadLocal類。當我們爲線程創建Looper時,其實調用了threadLocal的set
方法。Handler可以通過threadlocal對象找到當前線程與之對應的Looper。
ThreadLocal的原理是爲每個線程都創建了一個數組來存儲值。當ThreadLocal使用get函數獲取當前線程的值的時候,首先會根據當前線程找到對應的存儲數組,並通過ThreadLocal的下標在數組中找到存儲的值。
ThreadLocal主要函數有set與get,首先來看set函數。
set函數
可以看到在set函數中,首先通過currentThread()
方法得到當前線程。在Thread
內部有一個Values
類專門用於存儲線程中的ThreadLocal值。接下來通過values(currentThread)
獲取當前線程對應的Values對象。之後判斷values對象是否爲null,如果爲null就初始化,否則就調用put函數去設置當前ThreadLocal的值。
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
可以看到在put方法中,會將ThreadLocal的值放置於table的refrence+1
下標處。
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
...
}
}
get函數
get函數的邏輯比較清晰,也是首先獲取當前線程,並找到當前線程對應的Values對象,進而從table數組中取出reference+1
位置的結果。
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
從ThreadLocal的set和get方法可以看出,他們所操作的對象都是當前線程的localValues對象和table數組,因此在不同線程中訪問同一個ThreadLocal的set和get方法,他們對ThreadLocal所做的讀寫操作僅限於各自內部,這就是爲什麼ThreadLocal可以在多個線程中互不干擾的存儲和修改數據。
2.Lopper工作原理
主線程中可以不創建Looper,但是在子線程中需要手動創建Looper,否則直接在子線程創建的Handler找不到匹配的Looper會報錯。
主線程不需要創建Looper是因爲已經自動創建了。
//主線程中不需要自己創建Looper
public static void main(String[] args) {
......
Looper.prepareMainLooper();//爲主線程創建Looper,該方法內部又調用 Looper.prepare()
......
Looper.loop();//開啓消息輪詢
......
}
而在子線程需要調用Looper類的靜態方法prepare
方法與loop
方法。
//子線程中需要自己創建一個Looper
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//爲子線程創建Looper
Looper.loop(); //開啓消息輪詢
}
}).start();
在prepare
方法中我們創建新的Looper,並將Looper存儲至ThreadLocal中,以便Handler可以通過ThreadLocal找到當前線程對應的Looper。可以從下述代碼看到Looper類有一個全局變量ThreadLocal,並在prepare
方法中執行sThreadLocal.set(new Looper())
操作。同時在Looper的構造函數中可以看到會創建一個消息隊列MessageQueue。
public class Looper {
// 每個線程中的Looper對象其實是一個ThreadLocal,即線程本地存儲(TLS)對象
private static final ThreadLocal sThreadLocal = new ThreadLocal();
// Looper內的消息隊列
final MessageQueue mQueue;
// 當前線程
Thread mThread;
// 。。。其他屬性
// 每個Looper對象中有它的消息隊列,和它所屬的線程
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
// 我們調用該方法會在調用線程的TLS中創建Looper對象
public static final void prepare() {
if (sThreadLocal.get() != null) {
// 試圖在有Looper的線程中再次創建Looper將拋出異常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
// 其他方法
}
loop
方法是一個阻塞的方法,會持續的訪問MessageQueue的next方法,直到next方法返回message,之後會將調用message對應Handler(msg.target
)的dispatchmessage
方法,將message發送給Handler去處理。這裏當消息隊列被標記爲退出狀態的時候,他的next方法就會返回null,也就是說,Looper必須退出,否則loop方法就會無限循環下去。
public static void loop() {
......
for (;;) {//死循環
Message msg = queue.next();
if (msg == null) {
return;
}
......
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
}
2.消息隊列MessageQueue的工作原理
MessageQueue主要有兩個核心函數enqueueMessage
方法與next
方法,分別對應着讀取與插入操作。MessageQueue的內部是一個單鏈表實現的。
enqueueMessage
會向鏈表尾部中插入一個元素,而next函數會從鏈表頭部移出一個元素。next
方法是一個阻塞方法,當消息隊列中沒有消息就會一直阻塞在這裏,當有消息後,會將消息移出鏈表。
3.Handler的工作原理
Handler負責對消息的發送與接收,Handler有兩種創建方式。不過不管是post還是send方式,最終調用的都是sendMessage方法發送廣播。
//第一種:send方式的Handler創建
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//如UI操作
}
};
//第二種:post方式的Handler創建
Handler handler = new Handler();
sendMessage
方法會調用Handler對應的MessageQueue的enqueueMessage
來將消息添加至消息隊列。此時Looper會通過next方法獲取到這個消息,並調用消息對應Handler的dispatchMessage
方法。
Handler發送Message的代碼流程:Handler.sendMessage()->Handler.sendMessageDelayed()->Handler.sendMessageAtTime()->MessageQueue.enqueueMessage()
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);
}
在dispatchMessage
中,首先,他會檢查Message的callback
是否爲null,不爲null就通過handlerCallback
來處理消息,Message的callback是一個Runnable對象,實際上就是Handler的post方法所傳遞Runnable參數,handlerCallback的邏輯也很簡單。最終都會調用handleMessage方法去處理message。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
異步消息處理機制的使用
開啓子線程
開啓子線程首先要new一個Thread
,並在其內部實現一個Runnable
接口,重寫其run
函數。一般在android中我們開啓子線程是用子線程來完成耗時操作,比如網絡請求。
new Thread(new Runnable() {
@Override
public void run() {
//耗時操作的邏輯
}
}).start();
異步消息處理機制
在android中只要在主線程纔可以進行UI操作,但在子線程中,我們可能在獲取網絡數據結束後要更新一些控件的信息,因此Android提供了異步消息處理機制,主要包括Handler(信息的發送者和接收者)與Message(信息的載體,可以攜帶少量信息在不同線程交換),MessageQueue(信息隊列),Looper
代碼流程
首先在子線程中創建一個Message
對象,併爲它的what
字段賦值,賦值後通過sendMessage(message)
發送消息。之後在主線程中,創建Handler
對象,並實現其內部的handleMessage(Message msg)
方法,並根據msg.what
去匹配接收到的是哪個Message
,並最終更新UI。
圖片來源
一個實例。
public static final int UPDATE_TEXT =1;
/*創建Handler實例並重寫handleMessage方法*/
private Handler handler=new Handler(){
public void handleMessage(Message msg){
switch (msg.what){
case UPDATE_TEXT:
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
/*開啓的子線程*/
new Thread(new Runnable() {
@Override
public void run() {
Message message=new Message();
message.what=UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
總結(複習必看)
1.消息機制是android用來實現線程間通信的,最常用的場景就是讓子線程執行一個耗時操作,執行後,需要更新UI控件,這時就需要用消息機制。而UI控件是線程不安全的,所以不能讓UI控件直接在子線程更新。
2.消息機制是指Handler的工作模式。其中包含四個部分,Handler:消息的發出者、執行者;MessageQueue:消息隊列,用來存儲信息;Looper:消息崩,用來取出MessageQueue的信息併發送給消息對應的Handler處理;Message:消息的載體,具有Message.what字段來匹配Message。
3.四個部分的關係:
一個線程只能有一個Looper,但可以有多個Handler;MessageQueue中可以存儲多個Message,這些Message可以來自不同的Handler;Looper中只有一個MessageQueue;Handler可以在任意線程發送消息至Handler所綁定的MessageQueue,但Handler只會在所綁定的Looper所在線程對信息進行處理。
4.消息機制工作流程:
1)Handler會通過sendMessage方法發出消息並調用所綁定的MessageQueue的enqueueMessage方法將消息放入消息隊列MessageQueue;
2)Looper的loop方法會持續的調用MessagQueue的next方法,此時next方法會返回message,之後調用message所對應的Handler的dispatchMessage方法交由Handler處理,
3)Handler會調用handleMessage方法進行具體操作。
5.ThreadLocal在信息機制的作用及原理:
Looper類中存在靜態的ThreadLocal對象,並在每次爲線程創建Looper對象時,都會將Looper對象存入ThreadLocal對象中。ThreadLocal用於幫助Handler找到當前線程對應的Looper,進而找到其MessageQueue,並調用其enqueueMessage方法。
ThreadLocal類爲每個線程創建了一個數組,當使用set函數時,會根據當前線程找到對應的數組,並通過ThreadLocal對象將其存入數組中的特定下標reference+1
處。而get函數時同理,也會根據當前線程找到存儲的對應數組,並根據ThreadLocal對象找到數組的對應下標reference+1
,並將數值取出。ThreadLocal在不同線程下變更的是不同數組,所以ThreadLocal可以在多個線程中互不干擾的存儲和修改數據。
6.Looper的工作原理與使用
想在子線程創建Handler(此Handler與子線程綁定)並使用,需要首先創建Looper,否則會報錯。Looper創建包括兩個靜態方法:Looper.prepare
方法與Looper.loop
方法。
prepare方法中,會爲當前線程創建一個新的Looper實例,在Looper構造方法中還會創建MessageQueue。
loop方法中,會持續地調用MessageQueue的next方法,直至next方法返回了消息,此方法是個阻塞方法。但當消息隊列被標記位退出狀態時,可以由MessageQueue的next方法返回一個null,來停止這個持續的死循環。
7.MessageQueue的工作原理
MessageQueue內部其實是個單鏈表,主要包含兩個方法:enqueMessage
與next
方法。enqueueMessage
方法會在Handler發送消息後調用並將消息插入鏈表;而**next
方法是一個阻塞方法,會持續的去尋找鏈表中是否有元素,並刪除元素。**
8.Handler的工作原理
Handler有兩種創建方式,一種是post一種是send,send方式比較常用,兩種方式最終都會調用sendMessage方法將消息傳送。之後會調用當前handler所匹配的MessageQueue的enqueueMessage
方法將消息添加至鏈表中。
當Looper調用消息隊列的next
函數返回msg後,會調用msg匹配Handler的dispatchMessage
方法交由Handler處理,最終Handler會調用handlemessage
進行實際操作。
9.消息機制的具體使用實例
首先在主線程創建一個Handler實例,並重寫其handleMessage方法。開啓一個子線程,並在子線程耗時操作結束後使用Handler實例調用sendMessage發送消息。
10.Message的創建方式:
後兩種比較好,後兩種都是從Message池中返回一個Message實例,可以避免Message的重複創建。
1.Message = new Message();
2.Message = Message.obtain();
3.Message = handler1.obtainMessage();
11.使用Hanlder的postDelay()後消息隊列會發生什麼變化?
postDelay發送的消息並不是延遲一會在發送,而是 發送到MessageQueue後,直接阻塞線程。與MessageQueue的隊首元素根據觸發時間比較,始終讓觸發時間短的在隊首,讓觸發時間長的在隊尾。此時如果隊首就是delay的消息,就會將線程阻塞delay的時間,之後在執行該Message。
參考鏈接
本文中的圖出自:
android的消息處理機制(圖+源碼分析)——Looper,Handler,Message
要點提煉|開發藝術之消息機制