Anroid訊息機制原理分析
前言
最近通過微信公眾號推文以及部落格文章學習了Android的訊息機制的原理,然後抽空寫下了這篇文章,對自己學到知識進行梳理,也方便以後自己查閱。
因為Android的UI是執行緒不安全的,而我們處理完成非同步任務的時候,常常都需要更新我們的UI介面,這樣,就產生了一個矛盾:也就是我的子執行緒需要更新UI,而這個操作又必須在主執行緒中完成。所以,就有了我們接下來要分析的訊息機制。
訊息機制的大概流程:
啟動系統後,會在主執行緒建立一個Looper物件,然後通過這個Looper物件開啟一個死迴圈,迴圈會不斷地從訊息佇列中(MessageQueue)中取出待處理的訊息(Message),並回調Handler中的方法處理訊息。訊息佇列中的訊息是從哪裡來的呢?沒錯,就是通過Handler傳送過去的。流程也可用下面的圖表示出來:
訊息機制原始碼分析:
我們在使用到訊息機制進行更新UI的時候的操作步驟是怎麼樣的呢?
- 在主執行緒中建立一個Handler物件,傳入一個Handler.Callback介面的匿名類物件,並重寫該物件的handlerMessage()方法。
- 在子執行緒中建立一個Message物件,把要傳送的訊息放在Message物件中,然後呼叫Handler的sendMessage()方法把Message插入到MessageQueue中
- 最後主執行緒中重寫的handlerMessage()方法會被呼叫,更新UI的操作就完成啦
我們就跟著上面的步驟去看一下訊息機制裡面都做了些什麼吧。沒錯,我們就要去看看原始碼了,其實看原始碼不難,跟著思路來走就不會亂了。首先進入Handler的構造方法:
//我們傳入Handler.Callback物件呼叫的構造方法 public Handler(Callback callback) { this(callback, false); } 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()); } } //獲取Looper物件 //如果Looper物件為空,就直接丟擲異常,提示使用者沒有使用Looper.prepare()建立Looper物件 //我們還沒建立過Looper物件,會不會報錯呢?不急,我們接著往下看 ,肯定與Looper.prepare()有關 mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } //獲取MessageQueue訊息佇列 //其實MessageQueue裡面的資料結構是一個單鏈表 mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
好啦,這樣,我們的一個Handler物件就建立好了(Looper.prepare()這個方法我們後面會講到,先順著思路走下去),並且我們可以看到,Handler中持有Looper和MessageQueue的引用。接下來我們就要用到這個Handler物件往訊息佇列中插入訊息了,讓我們看看Handler.sendMessage(),看它是如何往訊息佇列中插入訊息的
//我們使用時呼叫的方法
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) {
//target其實就是Handler物件,通過msg.target=this實現Message與Handler的繫結
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//最後,呼叫了MessageQueue的enqueueMessage方法
return queue.enqueueMessage(msg, uptimeMillis);
}
//下面的原始碼中省略了一些無關、影響閱讀的原始碼,想看完整原始碼的可以自行去AS中檢視
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
Message p = mMessages;
if (p == null ) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
} else {
Message prev;
//前面說過,MessageQueue資料結構是單鏈表,下面就是遍歷單鏈表,找到連結串列尾部
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;//把訊息插入到尾部
}
}
return true;
}
這時候,我們就已經成功把訊息(Message)插入到MessageQueue中了,並且我們知道了Handler把自己封裝進了Message當中。走完這兩步,我們好像走進了一個死衚衕,走不下去了?先來看看下面幾個問題:
- Looper物件是在哪裡被建立的?
- Handler中的handlerMessage()方法是怎麼被回撥的?
我們在上面的步驟中,沒有建立過Looper物件,而Looper物件卻已經存在了,那唯一的一種可能就是,當我們啟動系統的時候,系統已經幫我們建立好Looper物件了。我們在ActivityThread類中找到main()方法,ActivityThread類是一個隱藏類,我們可以在SDK資料夾中查找出來:
public static void main(String[] args) {
......
Looper.prepareMainLooper(); //建立一個Looper物件
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop(); //開啟迴圈
throw new RuntimeException("Main thread loop unexpectedly exited");
}
果然,我們能在main()發現Looper。main()方法中通過Looper.prepareMainLooper()建立一個Looper物件,prepareMainLooper()方法實際上就是呼叫Looper.prepare(),沒錯,就是我們之前發現的方法,接下來讓我們看看這個方法做了什麼吧
public static void prepareMainLooper() {
prepare(false);
......
}
......
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));
}
很簡單,prepare()中就是new了一個Looper,並把Looper set進ThreadLocal中(ThreadLocal是什麼來的?我暫時還沒有去了解,有時間會補充),所以Looper在系統啟動時就建立好了,我們建立Handler時才不會報錯
Looper建立完成之後,然後Looper.loop()就會開啟一個迴圈,開啟迴圈做什麼呢?就是之前說過的,從MessageQueue中不斷的取出訊息來
//剔除了一些影響閱讀的程式碼後,可以看到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 {
......
}
}
}
很明顯可以看出loop方法一直遍歷MessageQueue,阻塞執行緒,直到獲取到一個Message,然後呼叫Message的一個成員變數target的dispatchMessage方法。之前說過了,target其實就是Handler,dispatchMessage方法最終就呼叫我們重寫的Handler的handlerMessage方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
以上就是我們在主執行緒中訊息機制的原理,讓我們整理一下上面的知識:
- Handler:在訊息機制中,是作為訊息的傳送方和處理方。訊息在一個執行緒中通過Handler傳送到MessageQueue中。
- Looper:在訊息機制中,是作為訊息佇列的管家。不停的從訊息佇列中獲取訊息,獲取到訊息後,根據Message中繫結的Handler物件呼叫Handler中的dispatchMessage方法,進而呼叫到開發者重寫的handlerMessage方法進行訊息的處理
- MessageQueue和Message:MessageQueue就是存放Message的地方,實質上是一個單鏈表的結構,有新的Message來的時候,就把它放在表尾,當Looper來取訊息時就把表頭的Message給它。Looper拿到訊息後,可以從Message中拿到訊息的傳送方。
知道訊息機制的原理後,我們就可以輕鬆的在任何執行緒下使用handler了:
new Thread() {
@Override
public void run() {
Looper.prepare();
Looper.loop();
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
})
}
}.start();
- 首先用Looper.prepare()建立一個Looper並初始化Looper持有的MessageQueue
- 然後用Looper.loop()方法開啟迴圈,從MessageQueue中取訊息,並呼叫handler的dispatchMessage方法處理訊息,如果訊息佇列裡面沒有訊息,迴圈就會阻塞進入休眠狀態,等有訊息時就會被喚醒
- 最後再new一個Handler,Handler構造方法會獲取到Looper和Looper的MessageQueue物件,然後通過sendMessage方法往訊息佇列中插入訊息。
最後,我們對訊息機制有一些瞭解了,先寫這麼多吧,如有錯誤歡迎指出。