安卓中的訊息迴圈機制Handler及Looper詳解
我們知道安卓中的UI執行緒不是執行緒安全的,我們不能在UI執行緒中進行耗時操作,通常我們的做法是開啟一個子執行緒在子執行緒中處理耗時操作,但是安卓規定不允許在子執行緒中進行UI的更新操作,通常我們會通過Handler機制來完成該功能,即當子執行緒中耗時操作完成後,在子執行緒中通過Handler向主執行緒傳送訊息,在主執行緒中的Handler的handleMessage方法中處理接受到的訊息。這就是安卓中的訊息機制,安卓中的訊息機制主要是指Handler的執行機制,但是Handler的執行需要底層的MessageQueue和Looper機制的支撐。
下面我們來詳解講解安卓中的訊息迴圈機制,即Handler,MessageQueue,Looper機制。注意本部落格屬於安卓進階內容,所以一些基礎性的東西如果看官不懂情請自行百度解決。
首先我們來看一個標準的使用Handler機制的程式碼示範例子:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // handle messages here } }; Looper.loop(); } }
這段程式碼大家肯定都不陌生,因為這是安卓中使用Handler機制的一個最標準的程式碼示範,也許看官可能會說我在使用Handler的時候沒使用Looper.prepare()與Looper.loop()
語句,那時因為我們使用Handler通常是在UI執行緒中,而UI執行緒已經預設為我們做好了這些準備工作,至於這個待會後面會講到。之所以選擇在一個子執行緒中建立Handler的例子來講解,是因為這樣能讓我們更加清楚的明白安卓訊息迴圈中Handler,MessageQueue,Looper這三者之間的關係,因為在主執行緒中很多細節都被掩藏起來了。
首先從這段程式碼可以看到這裡涉及兩個概念(事實上安卓中的訊息迴圈機制涉及三個重要概念),這裡先簡單介紹下
Looper:訊息迴圈,用於從MessageQueue訊息佇列中取出訊息進行處理。
Handler:訊息傳送與處理,Handler通過sendMessage向訊息佇列MessageQueue中傳送一條訊息,通過handlerMessage來處理訊息
MessageQueue:訊息佇列,用來存放Handler傳送的訊息,它僅僅用來存放訊息
首先講解一下安卓中的訊息迴圈的整體過程,然後從上述的示範程式碼上進行詳細的講解。
安卓中的訊息迴圈是使用Looper這個類來實現的,而Looper是基於執行緒的,即一個Looper物件與建立它的執行緒相關聯,當使用 Looper.prepare()語句時它會建立一個Looper物件和一個MessageQueue物件,然後在建立Handler時會先獲取到Handler所在的執行緒的Looper物件(如果呼叫的是無參建構函式時),通過這個Looper物件同時可以獲得其MessageQueue物件,正因為如此相當於Handler與Looper相關聯。這樣通過Handler傳送的訊息才知道應該通過哪個Looper去進行處理。這是理解安卓整個訊息迴圈機制核心,即MeaageQueue與Looper相關聯,Handler與Looper相關聯,從這裡也可以看出Looper是安卓訊息迴圈機制的核心。
下面以上述示範程式碼為例進行詳細講解。
首先我們來看一下 Looper.prepare()語句,即看一下prepare的原始碼:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
從這裡可以看到prepare()函式作了兩件事:建立一個looper物件,然後通過 sThreadLocal.set(new Looper());語句將其加入到 sThreadLocal中,這裡就不得不提一下安卓訊息迴圈機制中一個重要的概念:ThreadLocal,因為這涉及到Looper物件與執行緒相關聯的功能的實現。ThreadLocal它的作用是在不同的執行緒中訪問同一個ThreadLocal物件時通過ThreadLocal獲取到的值是不一樣的,即ThreadLocal中儲存的值是基於執行緒的。我們來看一下ThreadLocal的set方法:
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
可以看到set函式首先獲取當前的執行緒,然後通過當前執行緒產生一個Valuse物件,然後通過values的put方法將value(即Looper物件)與this(即ThreadLocal物件)相關聯,通過這段程式碼我們應該知道Looper是與執行緒相關的,因為set方法會通過執行緒產生Valuse物件,然後通過Valuse物件put方法,將Looper儲存起來。
接下來我們來看Handler handler=new Handler();這個語句,我們來看一下Handler()這個無參建構函式。
public Handler() {
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 = null;
}
從這裡我們可以看到首先呼叫了 Looper.myLooper();方法,該方法是返回當前執行緒所關聯的Looper,且可以看到如果獲取到的mLooper為null則會丟擲異常,這也說明建立Handler之前必選先建立Handler物件,獲得了Looper物件之後,我們就可以獲取到與Looper相關聯的MessageQueue物件,即 mQueue = mLooper.mQueue;前面講過建立Looper時會建立訊息佇列MessageQueue。這段程式碼我們重點來看一下Looper.myLooper();這個方法,通過這個函式獲取到了與當前執行緒相關聯的Looper物件,正因為如此,Handler物件傳送的訊息才知道應該被那個Looper處理。我們來看一下其程式碼:
public static Looper myLooper() {
return sThreadLocal.get();
}
可以看到他是通過sThreadLocal.get()方法來取得Looper物件,這個get與我們上面講述的ThreadLocal的set方法是相對應的,一個用來儲存資料,一個用來取出資料。
我們來看一下sThreadLocal.get()方法:
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);
}
可以看到sThreadLocal.get()方法先獲取當前執行緒,然後通過當前執行緒構造Values物件,然後通過valuse返回其儲存的資料(當然該資料為Looper物件),也正因為如此在Handler中獲取到的Looper物件與我們在當前執行緒中建立的Looper物件是同一個物件,這是保證Handler物件傳送的資訊被正確的Looper所處理的關鍵。
最後看一下Looper.loop();語句,這個語句的作用是開啟Looper迴圈,我們來看一下其原始碼:
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
if (msg.target == null) {
return;
}
if (me.mLogging!= null) me.mLogging.println(
">>>>> Dispatching to " + msg.target + " "
+ msg.callback + ": " + msg.what
);
msg.target.dispatchMessage(msg);
if (me.mLogging!= null) me.mLogging.println(
"<<<<< Finished to " + msg.target + " "
+ msg.callback);
msg.recycle();
}
}
}
可以看到loop方法首先獲取當前Looper物件,然後獲取該Looper物件的MessageQueue物件,然後在一個while死迴圈中不斷的通過 queue.next();從訊息佇列中取出一個訊息,然後通過訊息msg.target這個屬性來呼叫dispatchMessage(msg);來分派訊息,msg.target這個屬性本質上是傳送訊息給這個MessageQueue的Handler物件,即通過此語句就將Handler傳送的訊息交給它的dispatchMessage(msg);方法來處理,這樣就將程式碼邏輯切換到建立該Handler的執行緒中去執行了。
我們來看一下Handler的dispatchMessage(msg);方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到在該方法中呼叫了 handleMessage(msg);,而這個正是我們在Handler中處理訊息的回撥方法。最後就是我們使用sendMessage來發送訊息的過程,Handler提供了多個傳送訊息的函式,最終都會呼叫sendMessageAtTime()方法,我們來看一下其原始碼:
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent = false;
MessageQueue queue = mQueue;
if (queue != null) {
msg.target = this;
sent = queue.enqueueMessage(msg, uptimeMillis);
}
else {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
}
return sent;
}
可以看到在該方法中最終呼叫了MessageQueue的enqueueMessage(msg, uptimeMillis);方法,顧名思義,其作用是將一個訊息入隊,它的原始碼不重要,我們只需知道它是通過將一個訊息插入到一個單向連結串列中的方式來完成的入隊操作即可。
在前面我們說過在主執行緒中我們是不需要建立一個Looper的,這是因為這些工作安卓系統幾經幫我們做好了,安卓中的主執行緒即ActivityThread,主執行緒的入口函式為main,我們來看一下其原始碼:
public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
EventLogger.setReporter(new EventLoggingReporter());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到在該方法中呼叫了Looper.prepareMainLooper()方法,而Looper.prepareMainLooper()方法會呼叫Looper.prepare()方法。所以在主執行緒中我們不需自己建立一個Looper物件。好了,通過上述的講解相信看官對安卓中的訊息機制已經非常的清楚了,下面總結一下:
1Looper物件是安卓訊息迴圈的核心,Looper物件是基於執行緒的,我們在建立一個Looper物件時會通過ThreadLocal物件將我們建立的Looper與其所在的執行緒相關聯起來,具體來說是通過 sThreadLocal.set(new Looper());語句將其儲存起來,另外在建立一個Looper物件時其內部會幫我們自動建立了一個訊息佇列MessageQueue物件。
2Looper的作用是通過一個while死迴圈來不斷的檢視訊息佇列MessageQueue中是否存在訊息Message,若存在則會通過 msg.target.dispatchMessage(msg);將訊息交給傳送訊息的Handler物件來進行處理。
3我們在建立一個Handler物件時,其內部首先會獲得當前執行緒所關聯的Looper物件,即呼叫 Looper.myLooper();方法,具體來說是通過sThreadLocal.get()方法來完成的,
這樣就保證了在Handler中獲取到的Looper物件與我們在當前執行緒中建立的Looper物件是同一個物件,從而保證Handler物件傳送的資訊被正確的Looper所處理。
4另外在建立一個Handler物件時,其內部會通過獲取到的Looper物件來獲取與Looper物件相關聯的MessageQueue物件,這樣通過sendMessage傳送的訊息會發送到這個MessageQueue物件中,這也是保證Handler物件傳送的資訊被正確的MessageQueue所處理的關鍵(其本質是通過3來完成的,因為MessageQueue的建立是在Looper內部完成的,即MessageQueue是與Looper相關聯的)。
5MessageQueue僅僅用來儲存訊息而已,訊息的分派是通過Looper來完成的,在Looper的loop迴圈中會通過一個while死迴圈來不斷的檢視訊息佇列MessageQueue中是否存在訊息Message,若存在則會通過 msg.target.dispatchMessage(msg);將訊息交給傳送訊息的Handler物件來進行處理,這樣就將程式碼的邏輯切換到Handler所在的執行緒中進行處理。
用圖示表示如下:好了上述就是本人理解的關於安卓訊息迴圈機制的全部內容,看官如果覺得不錯請點個贊哦,也請看官多多支援本人的其它部落格文章。