Handler,MessageQueue,Looper這四大金剛,
- Message : 消息載體 ;
- Handler : 發送和處理消息;
- MessageQueue :存儲消息;
- Looper:傳輸消息(MessageQueue —> Handler) ;
1. Looper的功能
Handler究其本質就是用來實現線程間通信。在Android開發過程中,我們通常在子線程(Thread) 中將消息發送給 主線程(MainThread), 而 主線程(MainThread) 需要去獲取到消息,進而才能去處理消息。
誰來保證主線程(MainThread)獲取到消息呢?
Looper,捨我其誰…
源碼中對於Looper類的解釋,已經很詳細啦。
Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call {@link #prepare} in the thread that is to run the loop,and then {@link #loop} to have it process messages until the loop is stopped.
該類用於爲一個線程執行消息輪詢。默認情況下線程是沒有用於消息輪詢的looper的,需要在消息輪詢的線程中調用prepare()方法來創建一個Looper對象,然後調用loop()去處理消息直到輪詢停止。
因爲處理消息的線程是隻負責處理消息的,壓根不知道消息什麼時候來。所以才需要Looper不停的刺探軍情,爲線程去輪詢消息。
2.Looper的使用前提
在Handler的前世今生1——ThreadLocal 談到TheadLocal來保證Thread和Looper之間忠貞不渝的愛情。
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
一個線程只有一個Looper
一個Looper只有一個MessageQueue.
Thread : Looper : MessageQueue = 1 : 1 : 1
顯而易見,Looper 從其對應的MessageQueue 中去輪詢消息
2.1 MainLooper
在開發過程中,有時候我們會通過getMainLooper() 來切換到UI線程上。而且我們在主線程中使用Handler時,並沒有調用prepare() 和 loop() 等方法。這中間發生了什麼有趣的事情呢?
這就需要我們去查看ActivityThread的main()方法。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// Install selective syscall interception
AndroidOs.install();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
//1. 調用了Looper的prepare()方法
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
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");
}
可以看出來,在Looper的使用方法上沒有什麼區別。
- prepareMainLooper()方法
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
- getMainLooper()方法
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
在Looper裏面,我們看到了synchronized + static的實際應用啦。
我們應該理解,
- 這裏保持同步,因爲它是主線程,肯定不能在多線程情況下出現問題;
- prepare(false),false 是因爲主線程不允許退出,除非應用退出。
3. Looper的loop()
上面講了那麼多,但是都沒有切入到Looper的功能。loop()方法纔是整個Looper類的核心。調用loop()才表示真正的發車啦。
實際上,我們都說Looper是負責消息輪詢的,若沒有消息則阻塞,該阻塞的邏輯實現是在MessageQueue的next()方法。以後會展開討論…
public static void loop() {
// 1. 取到looper對象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 2. 獲取MessageQueue
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
// 確定當前線程的身份是否是當前進程的
final long ident = Binder.clearCallingIdentity();
// 3. 無限輪詢
for (;;) {
// 這裏會阻塞,一定要記住,阻塞邏輯是在MessageQueue中實現的。而不是這裏
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// Make sure the observer won't change while processing a transaction.
// Observer就是一個監聽事件發送過程的接口
final Observer observer = sObserver;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
// 重點在這裏,此處的target其實就是Handler
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
// 消息複用機制
msg.recycleUnchecked();
}
}
3.1. loop()的流程
- 獲取Looper對象,然後拿到MessageQueue;
- 開啓輪詢,
2.1. 通過MessageQueue 的next() 獲取消息;
2.2. 表面上,若獲取的消息爲空,則退出循環(可能退出循環嗎?不可能的);
2.3. 通過Message.target(即Handler),調用 dispatchMessage(msg) 進行消息分發和處理。
2.4 調用Message 的recycleUnchecked() ,進行消息回收。
再次強調:loop()方法沒有消息則阻塞,阻塞的邏輯是在MessageQueue的next()實現的。
4. Looper還有退出功能
Looper在沒有消息的時候進行阻塞,終歸還是要消耗一部分資源的。所以除了主線程,其他線程還是需要退出循環功能的。
PS:值得我們注意的是,退出功能最終也是交由MessageQueue實現的。
public void quit() {
mQueue.quit(false);
}
推薦使用quitSafely()
public void quitSafely() {
mQueue.quit(true);
}
至於兩者的區別,跟線程池的退出機制類似…
5. 關於loop()方法的考點
5.1 loop()死循環爲什麼沒有阻塞主線程?
我們會看到ActivityThread的Looper.loop()方法是在main()方法的最後。而Android本身就是靠事件驅動的。所以Looper.loop()不斷地接收並處理消息。我們的代碼都是在這個循環中執行的,所以當然不會阻塞啦。具體可以自行百度。
5.2 loop()沒有消息時是如何處理的?
其實還是想強調一下:loop()的阻塞邏輯是在MessageQueue中實現的。
6. 總結
Looper只是用來負責消息輪詢的,與負責發送和接收處理消息的Handler是沒有任何關聯的。這一點也可以通過源碼來證實。涉及到的對應關係如下:
Looper : Thread : MessageQueue = 1 : 1 : 1
Looper : Handler = 1 : N
Looper 和 Handler是沒有任何聯繫的。
Looper 本身的阻塞功能和退出功能都是MessageQueue實現的。
歡迎大家繼續收看 Handler的前世今生3——Message