我們知道安卓中的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所在的線程中進行處理。
好了上述就是本人理解的關於安卓消息循環機制的全部內容,看官如果覺得不錯請點個贊哦,也請看官多多支持本人的其它博客文章。