為什麼要使用Handler
-
因為在Android中訪問UI只能在主執行緒中進行,如果在子執行緒中執行,則程式會丟擲異常。
// ViewRootImpl.java void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } 複製程式碼
-
為什麼不允許在子執行緒中訪問UI?
因為Android的UI並不是執行緒安全的,如果在多執行緒中執行UI的操作,那麼UI的狀態是不可控的,這個時候就會出現各種問題。 那麼最好的辦法就是隻能在一個執行緒中執行UI的操作。 而Android主執行緒中又不能執行耗時操作,因為那樣就會導致程式的ANR。 所以Android提供了Handler這樣的機制,用來在子執行緒中執行耗時操作之後,傳送訊息給主執行緒,主執行緒再執行UI的更新操作。
Handler機制原理分析
這裡我們以Android UI執行緒的Handler來分析。
Handler的建立
Android的應用入口是ActivityThread.main()
函式,UI執行緒的Handler就是在這個函式中建立的。
public static void main(String[] args) {
Looper.prepareMainLooper();
thread.attach(false);
ActivityThread thread = new ActivityThread();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
}
複製程式碼
當main函式執行之後,應用就啟動了,從這個時候開始,Looper就會一直從訊息佇列中取出訊息並處理。而應用會通過Handler來不斷的新增訊息給訊息佇列。通過不斷的新增訊息和取出訊息,整個訊息機制就被運轉起來了。
Looper
從上面可以看到在Handler建立前後,通過Looper的prepare
和loop
方法,建立了Looper物件和開啟訊息迴圈。
-
建構函式
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 複製程式碼
- 建立了MessageQueue物件,這個物件是訊息佇列,主要用來存放我們的訊息,會在下面具體分析。
- 獲取當前的執行緒並儲存起來
-
prepare
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)); } 複製程式碼
- 判斷ThreadLocal中在當前執行緒是否已經存在了Looper物件,如果存在,則丟擲異常,所以可以得出在同一個執行緒中只能存在一個Looper物件。
- 在ThreadLocal存入當前執行緒的Looper物件。
- ThreadLocal物件是一個執行緒內部的資料儲存類,通過它可以在指定的執行緒儲存資料,當儲存資料之後,就只能在指定的執行緒中獲取到儲存的資料,其他執行緒是不能獲取到資料的。
-
loop
public static void loop() { final Looper me = myLooper(); // 獲取訊息佇列 final MessageQueue queue = me.mQueue; // 死迴圈來輪詢的從訊息佇列中獲取訊息 for (;;) { Message msg = queue.next(); // might block if (msg == null) { return; } try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } msg.recycleUnchecked(); } } 複製程式碼
- loop函式會不斷的從訊息佇列中取出訊息,當queue.next()為空的時候就直接阻塞住
- msg不為空,呼叫
msg.target.dispatchMessage(msg)
處理訊息,而msg.target
其實就是Handler物件,取出訊息之後就交給Handler來處理髮送的訊息了。
-
quit
public void quit() { mQueue.quit(false); } public void quitSafely() { mQueue.quit(true); } 複製程式碼
呼叫MessageQueue的quit方法來退出Looper。其中上面的是直接退出,下面的是安全的退出,也就是隻標記一下,當訊息佇列中的訊息全部執行完成之後安全的退出。
當我們在子執行緒中建立Looper和Handler的時候,如果我們不呼叫quit方法的話,子執行緒就會一直處於等待狀態,所以我們需要呼叫quit方法退出Looper。
MessageQueue
在Looper中大量的使用到了MessageQueue,而MessageQueue主要就是兩個操作插入訊息和讀取訊息並刪除訊息,我們來一起分析下:
-
建構函式
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); } 複製程式碼
在建構函式中,呼叫了
nativeInit
方法在Native層建立了NativeMessageQueue和Looper,並將Looper設定給當前的執行緒。這個時候Java層和Native層都有了MessageQueue和Looper。 在Native層的Looper中,建立了一個管道,本質上就是一個檔案,一個管道中有讀和寫兩個檔案操作符,通過讀和寫來將訊息寫入和讀取。 -
enqueueMessage 插入訊息
boolean enqueueMessage(Message msg, long when) { synchronized (this) { msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; } 複製程式碼
enqueueMessage其實就是構建好msg,並插入到單連結串列中。
-
next 讀取訊息並刪除訊息
Message next() { for (;;) { nativePollOnce(ptr, nextPollTimeoutMillis); } } 複製程式碼
next方法是一個無限迴圈方法,當訊息佇列中沒有訊息的時候,next方法會被阻塞在這裡,當有新的訊息的時候,next方法會最終返回這條訊息並從單連結串列中移除。
Handler
-
建構函式
public Handler(Callback callback, boolean async) { 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 = callback; mAsynchronous = async; } 複製程式碼
- 首先獲取Looper物件,如果Looper物件為空,則丟擲異常,所以當我們在子執行緒中建立Handler的時候,首先要通過
Looper.prepare()
來建立子執行緒的Looper物件。 - 通過Looper獲取到MessageQueue
- 首先獲取Looper物件,如果Looper物件為空,則丟擲異常,所以當我們在子執行緒中建立Handler的時候,首先要通過
-
傳送訊息
Handler的傳送訊息主要是post的方法和send的方法,而他們最終呼叫的都是:
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); } 複製程式碼
Handler主要就是向訊息佇列中插入了一條訊息,這個時候MessageQueue就會呼叫插入的方法來插入訊息。
-
讀取訊息
當Handler傳送訊息之後,MessageQueue就會通過next方法讀取出這個訊息,那麼Looper的loop就不會被阻塞住,取出訊息之後就呼叫了
msg.target.dispatchMessage(msg)
方法,最終交給Handler的dispatchMessage
來執行。/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } 複製程式碼
-
檢查Message的callback是否為空,不為空則通過
handleCallback
來處理,而callback就是通過post傳遞過來的Runnable物件。直接呼叫run方法執行。private static void handleCallback(Message message) { message.callback.run(); } 複製程式碼
-
檢查mCallback是否為空,不為空則呼叫
mCallback.handleMessage
方法處理訊息。而mCallback
就是我們在建立Handler的時候傳遞過去的callback。public interface Callback { public boolean handleMessage(Message msg); } public Handler(Callback callback) { this(callback, false); } 複製程式碼
-
呼叫
handleMessage(msg)
處理訊息。
-