Android的消息機制指的是Handler的運作機制,本篇将總結Handler機制的相關知識點:
- 消息機制概述
-
消息機制分析
1.消息機制概述
a.作用:跨線程通信。
b.常用場景:當子線程中進行耗時操作後需要更新UI時,通過Handler将有關UI的操作切換到主線程中執行。
系統不建議在子線程通路UI的原因:UI控件非線程安全,在多線程中并發通路可能會導緻UI控件處于不可預期的狀态。而不對UI控件的通路加上鎖機制的原因有:
- 上鎖會讓UI控件變得複雜和低效
- 上鎖後會阻塞某些程序的執行
c.四要素:
- Message(消息):需要被傳遞的消息,其中包含了消息ID,消息處理對象以及處理的資料等,由MessageQueue統一列隊,最終由Handler處理。
- MessageQueue(消息隊列):用來存放Handler發送過來的消息,内部通過單連結清單的資料結構來維護消息清單,等待Looper的抽取。
-
Handler(處理者)
:負責Message的發送及處理。
- Handler.sendMessage():向消息池發送各種消息事件。
- Handler.handleMessage() :處理相應的消息事件。
- Looper(消息泵):通過Looper.loop()不斷地從MessageQueue中抽取Message,按分發機制将消息分發給目标處理者。
Thread(線程):負責排程整個消息循環,即消息循環的執行場所。
存在關系:
- 一個Thread隻能有一個Looper,可以有多個Handler;
- Looper有一個MessageQueue,可以處理來自多個Handler的Message;
- MessageQueue有一組待處理的Message,這些Message可來自不同的Handler;
- Message中記錄了負責發送和處理消息的Handler;
- Handler中有Looper和MessageQueue;
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZlBnauIDOkdjNzMmZyUjN0U2N1ImYjRzNmJDMkhTYjV2Y3QjYfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.jpeg)
01.png
02.png
d.實作方法:
- 在主線程執行個體化一個全局的Handler對象;
- 在需要執行UI操作的子線程裡執行個體化一個Message并填充必要資料,調用Handler.sendMessage(Message msg)方法發送出去;
- 複寫handleMessage()方法,對不同Message執行相關操作;
2.消息機制分析
a.工作流程:
- Handler.sendMessage()發送消息時,會通過MessageQueue.enqueueMessage()向MessageQueue中添加一條消息;
- 通過Looper.loop()開啟循環後,不斷輪詢調用MessageQueue.next();
- 調用目标Handler.dispatchMessage()去傳遞消息,目标Handler收到消息後調用Handler.handlerMessage()處理消息。
簡單來看,即Handler将Message發送到Looper的成員變量MessageQueue中,之後Looper不斷循環周遊MessageQueue從中讀取Message,最終回調給Handler處理。如圖:
03.png
b.工作原理:
- (1)Looper的建立:先從應用程式的入口函數ActivityThread.main()看起,在這裡(主線程)系統自動建立了Looper,主要方法:
//主線程中不需要自己建立Looper
public static void main(String[] args) {
......
Looper.prepareMainLooper();//為主線程建立Looper,該方法内部又調用 Looper.prepare()
......
Looper.loop();//開啟消息輪詢
......
}
注意:
- 子線程的Looper需要手動去建立,标準寫法是:
//子線程中需要自己建立一個Looper
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//為子線程建立Looper
Looper.loop(); //開啟消息輪詢
}
}).start();
- 無論是主線程還是子線程,Looper隻能被建立一次,即一個Thread隻有一個Looper。
- 所建立的Looper會儲存在ThreadLocal(線程本地存儲區)中,它不是線程,作用是幫助Handler獲得目前線程的Looper。更多講解見 ThreadLocal詳解
- (2)MessageQueue的建立:在Looper的構造函數建立了一個MessageQueue:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
(3)Message輪詢及處理:有了Looper和MessageQueue之後通過Looper.loop()開啟消息輪詢:
public static void loop() {
......
for (;;) {//死循環
Message msg = queue.next(); //用于提取下一條資訊,該方法裡同樣有個for(;;)死循環,當沒有可處理該Message的Handler時,會一直阻塞
if (msg == null) {
return;
}
......
try {
msg.target.dispatchMessage(msg);//如果從MessageQueue中拿到Message,由和它綁定的Handler(msg.target)将它發送到MessageQueue
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
}
現在就剩建立Handler及Message發送了。(4)先看Handler的建立:有兩種形式的Handler:
//第一種:send方式的Handler建立
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//如UI操作
}
};
//第二種:post方式的Handler建立
Handler handler = new Handler();
- (5) Message的發送:
對于send方式的Handler:建立好一個Message後,調用Handler的以下幾種常見的方法來發送消息:
sendEmptyMessage(); //發送空消息
sendEmptyMessageAtTime(); //發送按照指定時間處理的空消息
sendEmptyMessageDelayed(); //發送延遲指定時間處理的空消息
sendMessage(); //發送一條消息
sendMessageAtTime(); //發送按照指定時間處理的消息
sendMessageDelayed(); //發送延遲指定時間處理的消息
sendMessageAtFrontOfQueue(); //将消息發送到消息隊頭
對于post方式的Handler,可在子線程直接調用Handler的以下幾種常見方法,使得切換到主線程:
post(Runnable r)
postAtFrontOfQueue(Runnable r)
postAtTime(Runnable r, Object token, long uptimeMillis)
postAtTime(Runnable r, long uptimeMillis)
postDelayed(Runnable r, long delayMillis)
//例如,postDelayed()方法
handler.postDelayed(new Runnable() {
@Override
public void run() {
//如UI操作
}
},300);
通過以上各種Handler的發送方法,都會依次調用 Handler.sendMessageDelayed->Handler.sendMessageAtTime()->MessageQueue.enqueueMessage() 最終将Message發送到MessageQueue。
04.png
Q:Message可以如何建立?哪種效果更好,為什麼?
建立Message對象的時候,有三種方式,分别為:
1.Message msg = new Message();
2.Message msg2 = Message.obtain();
3.Message msg1 = handler1.obtainMessage();
這三種方式有什麼差別呢?
Message msg = new Message();這種就是直接初始化一個Message對象,沒有什麼特别的。
2)Message msg2 = 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();
}
從注釋可以得知,從整個Messge池中傳回一個新的Message執行個體,通過obtainMessage能避免重複Message建立對象。
Message msg1 = handler1.obtainMessage();
public final Message obtainMessage()
{
return Message.obtain(this);
}1234
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
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對象,是以建議用第二種或者第三種任何一個建立Message對象。
Q:主線程中Looper的輪詢死循環為何沒有阻塞主線程?
正如我們所知,在android中如果主線程中進行耗時操作會引發ANR(Application Not Responding)異常。
造成ANR的原因一般有兩種:
- 目前的事件沒有機會得到處理(即主線程正在處理前一個事件,沒有及時的完成或者looper被某種原因阻塞住了)
- 目前的事件正在處理,但沒有及時完成
為了避免ANR異常,android使用了Handler消息處理機制。讓耗時操作在子線程運作。
是以産生了一個問題,主線程中的Looper.loop()一直無限循環檢測消息隊列中是否有新消息為什麼不會造成ANR?
while (true) {
//取出消息隊列的消息,可能會阻塞
Message msg = queue.next(); // might block
...
//解析消息,分發消息
msg.target.dispatchMessage(msg);
...
}
顯而易見的,如果main方法中沒有looper進行循環,那麼主線程一運作完畢就會退出。
總結:ActivityThread的main方法主要就是做消息循環,一旦退出消息循環,那麼你的應用也就退出了。
因為Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點選觸摸或者說Activity的生命周期都是運作在 Looper.loop() 的控制之下,如果它停止了,應用也就停止了。隻能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
也就說我們的代碼其實就是在這個循環裡面去執行的,當然不會阻塞了。
如果某個消息處理時間過長,比如你在onCreate(),onResume()裡面處理耗時操作,那麼下一次的消息比如使用者的點選事件不能處理了,整個循環就會産生卡頓,時間一長就成了ANR。
而且主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,并且往管道檔案寫資料,主線程即被喚醒,從管道檔案讀取資料,主線程被喚醒隻是為了讀取消息,當消息讀取完畢,再次睡眠。是以loop的循環并不會對CPU性能有過多的消耗。
總結:Looer.loop()方法可能會引起主線程的阻塞,但隻要它的消息循環沒有被阻塞,能一直處理事件就不會産生ANR異常。