前言
Handler
是Android的訊息機制,他能夠很輕鬆的線上程間傳遞資料。由於Android開發規範的限制,我們不能在主執行緒執行耗時操作(如網路,IO操作等),不能在子執行緒更新UI,所以Handler
大部分用來在耗時操作與更新UI之間切換。這讓很多人誤以為Handler
就是用來更新UI的,其實這只是它的一小部分應用。
開始
我相信大多數人對Handler
的用法已經爛熟於心了,這篇文章不會去探討Handler
的使用,而是著重從原始碼上分析Handler
的執行機制。
想要了解Handler
的執行機制,我們需要了解 MessageQueue
,Message
,Looper
這幾個類。
MessageQueue
的意思就是訊息佇列,它儲存了我們需要用來處理的訊息Message
。Message
是訊息類,內部存在一個Bundle
物件和幾個public
欄位儲存資料,MessageQueue
作為一個訊息佇列不能自己處理訊息,所以需要用到Looper
。Looper
是一個迴圈裝置,他負責從不斷從MessageQueue
裡取出Message
,然後回撥給Handler
的handleMessage
來執行具體操作。Handler
在這裡面充當的角色更像是一個輔助類,它讓我們不用關係MessageQueue
和Looper
的具體細節,只需要關係如何傳送訊息和回撥的處理就行了。
上面講了幾個關鍵類在Handler
執行機制中的職責,相對大家對Handler機制有個粗略的瞭解。
我相信各位看官在閱讀這篇文章前都是帶著問題的,我們將通過問題來解答大家的疑惑。
分析
Looper
在分析Looper
之前,我們還需要知道ThreadLocal
這個類,如果對ThreadLocal
還不太瞭解,可以去看我的另一篇文章《ThreadLocal詳解》。
Looper是如何建立?
Handler
執行的執行緒和它持有的Looper
有關。每個Thread
都可以建立唯一的Looper物件。
//為當前執行緒建立Looper物件的方法。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//使用ThreadLocal來儲存當前執行緒的Looper物件,這保證了每個執行緒有且僅有一個Looper物件。
//這裡做了非空判斷,所以在同一個執行緒prepare方法是不允許被呼叫兩次的
//第一次建立好的Looper物件不會被覆蓋,它是唯一的。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
複製程式碼
那麼主執行緒的Looper
物件是怎麼建立的呢?
public static void prepareMainLooper() {
//其實主執行緒建立Looper和其他執行緒沒有區別,也是呼叫prepare()。
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//但是Looper用sMainLooper這個靜態變數將主執行緒的Looper物件儲存了起來
//可以通過getMainLooper()獲取,儲存MainLooper其實非常有作用,下面會講到。
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
複製程式碼
Looper是如何從MessageQueue取出訊息並分發的?
Looper分發訊息的主要邏輯在loop方法裡
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
//保證當前執行緒必須有Looper物件,如果沒有則丟擲異常,呼叫Looper.loop()之前應該先呼叫Looper.prepare().
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//Looper需要不斷從MessageQueue中取出訊息,所以它持有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();
for (;;) {
//這裡開始執行死迴圈,queue通過呼叫next方法來取出下一個訊息。
//很多人很疑惑死迴圈不會相當耗費效能嗎,如果沒有那麼多訊息怎麼辦?
//其實當沒有訊息的時候,next方法會阻塞在這裡,不會往下執行了,效能問題不存在。
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
//這裡滿足了死迴圈跳出的條件,即取出的訊息為null
//沒有訊息next不是會阻塞嗎,怎麼會返回null呢?
//其實只有MessageQueue停止的時候(呼叫quit方法),才會返回null
//MessageQueue停止後,呼叫next返回null,且不再接受新訊息,下面還有詳細介紹。
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//這裡的msg.target是Handler物件,分發訊息到Handler去執行。
//有人問主執行緒可以建立這麼多Handler,怎麼保證這個Handler傳送的訊息不會跑到其它Handler去執行呢?
//那是因為在傳送Message時,他會繫結傳送的Handler,在此處分發訊息時,也只會回撥傳送該條訊息的Handler。
//那麼分發訊息具體在哪個執行緒執行呢?
//我覺得這個不該問,那當然是當前方法在哪個執行緒呼叫就在哪個執行緒執行啦。
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
//這裡對Message物件進行回收,會清空所有之前Message設定的資料。
//正是因為Message有回收機制,我們在建立訊息的時候應該優先選擇Message.obtain().
//如果傳送的訊息足夠多,Message快取的Message物件不夠了,obtain內部會呼叫new Message()建立一個新的物件。
msg.recycleUnchecked();
}
}
複製程式碼
Looper 分發的訊息在哪個執行緒執行?
先給大家展示一段Looper
文件上的示例程式碼
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); //建立LooperThread的Looper物件
mHandler = new Handler() {
public void handleMessage(Message msg) {
//處理髮送過來的訊息
}
};
Looper.loop(); //開始迴圈訊息佇列
}
}
複製程式碼
上面這段程式碼相信很多人都寫過,這是一段在子執行緒建立Handler的案例,其中handleMessage
所執行的執行緒為LooperThread
,因為Looper.loop()
執行在LooperThread
的run
方法裡。可以在其他執行緒通過mHandler
傳送訊息到LooperThread
如果不呼叫Looper.prepare()
直接new Handler()
會怎麼樣呢?
我們可以檢視Handler
的原始碼看看無參構造是如何執行的
public Handler() {
//呼叫兩參構造
this(null, 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()啊,怎麼在主執行緒不會拋異常呢?這個看下一個問題。
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.prepare()
直接new Handler()
就會丟擲異常`
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");`
複製程式碼
那麼主執行緒的Looper
在哪裡建立的呢?首先它是建立了的,因為Looper.getMainLooper() != null
,其實MainLooper
建立的時間比我們想象的早,它在ActivityThread
類裡面,ActivityThread
是Android
的啟動類,main
方法就在裡面(如果有人問你Android有沒有main方法,你應該知道怎麼回答了吧),而MainLooper
就是在main
方法裡面建立的。
上程式碼:
//android.app.ActivityThread
public final class ActivityThread {
...
public static void main(String[] args) {
SamplingProfilerIntegration.start();
// 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();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
Security.addProvider(new AndroidKeyStoreProvider());
// 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>");
//注意這裡,這裡建立了主執行緒的Looper
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");
}
}
複製程式碼
MainLooper可以用來做什麼
判斷當前執行緒是否為主執行緒
因為Looper是在某一執行緒唯一的,那麼可以在麼做。如果
public static boolean isMainThread() {
//如果當前執行緒的Looper和MainLooper是同一個物件,那麼可以認為當前執行緒是主執行緒
return Looper.myLooper() == Looper.getMainLooper() ;
}
複製程式碼
但是也有人說下面這樣也可以
public static boolean isMainThread() {
//這個方法其實是不準確的,執行緒的名稱是可以隨便更改的。
return Thread.currentThread().getName().equals("main");
}
複製程式碼
所以用Looper
來判斷主執行緒是很好的做法
建立執行在主執行緒的Handler
Handler
除了有無參構造,還有一個可以傳入Looper
的構造。通過指定Looper
,可以在任意地方建立執行在主執行緒的Handler
class WorkThread extends Thread{
private Handler mHandler;
@Override
public void run() {
super.run();
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//執行在主執行緒
}
};
mHandler.sendEmptyMessage(0);
}
}
複製程式碼
Looper的quit方法和quitSafely方法有什麼區別
下面是Looper
兩個方法的原始碼
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
複製程式碼
可以看出實際上是呼叫的MessageQueue
的quit
方法
下面是MessageQueue
的原始碼
//android.os.MessageQueue
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
//如果呼叫的是quitSafely執行removeAllFutureMessagesLocked,否則removeAllMessagesLocked。
if (safe) {
//該方法只會清空MessageQueue訊息池中所有的延遲訊息,
//並將訊息池中所有的非延遲訊息派發出去讓Handler去處理,
//quitSafely相比於quit方法安全之處在於清空訊息之前會派發所有的非延遲訊息。
removeAllFutureMessagesLocked();
} else {
//該方法的作用是把MessageQueue訊息池中所有的訊息全部清空,
//無論是延遲訊息(延遲訊息是指通過sendMessageDelayed或通過postDelayed等方法傳送的需要延遲執行的訊息)還是非延遲訊息。
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
複製程式碼
無論是呼叫了quit
方法還是quitSafely
方法,MessageQueue
將不再接收新的Message
,此時訊息迴圈就結束,MessageQueued
的next
方法將返回null
,結束loop()
的死迴圈.這時候再通過Handler
呼叫sendMessage
或post
等方法傳送訊息時均返回false
,表示訊息沒有成功放入訊息佇列MessageQueue
中,因為訊息佇列已經退出了。
Message
Message.obtain()和new Message()如何選擇
Message
提供了obtain
等多個過載的方法來建立Message
物件,那麼這種方式和直接new
該如何選擇。下面看看obtain
的程式碼。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message(); //只有當從物件池裡取不出Message才去new
}
void recycleUnchecked() {
//清除所有使用過的痕跡
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
//回收到物件池
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
複製程式碼
從上面程式碼可以看出,通過obtain
方法是從物件池取,而new
是建立了一個新的物件。我們應該使用obtain
來建立Message
物件,每次使用完後都會自動進行回收,節省記憶體。
未完待續......