前言
Handler是Android的消息機(jī)制,他能夠很輕松的在線程間傳遞數(shù)據(jù)。由于Android開發(fā)規(guī)范的限制,我們不能在主線程執(zhí)行耗時操作(如網(wǎng)絡(luò),IO操作等),不能在子線程更新UI,所以Handler大部分用來在耗時操作與更新UI之間切換。這讓很多人誤以為Handler就是用來更新UI的,其實這只是它的一小部分應(yīng)用。
開始
我相信大多數(shù)人對Handler的用法已經(jīng)爛熟于心了,這篇文章不會去探討Handler的使用,而是著重從源碼上分析Handler的運行機(jī)制。
想要了解Handler的運行機(jī)制,我們需要了解 MessageQueue ,Message,Looper 這幾個類。
-
MessageQueue的意思就是消息隊列,它存儲了我們需要用來處理的消息Message。 -
Message是消息類,內(nèi)部存在一個Bundle對象和幾個public字段存儲數(shù)據(jù),MessageQueue作為一個消息隊列不能自己處理消息,所以需要用到Looper。 -
Looper是一個循環(huán)裝置,他負(fù)責(zé)從不斷從MessageQueue里取出Message,然后回調(diào)給Handler的handleMessage來執(zhí)行具體操作。 -
Handler在這里面充當(dāng)?shù)慕巧袷且粋€輔助類,它讓我們不用關(guān)系MessageQueue和Looper的具體細(xì)節(jié),只需要關(guān)系如何發(fā)送消息和回調(diào)的處理就行了。
上面講了幾個關(guān)鍵類在Handler運行機(jī)制中的職責(zé),相對大家對Handler機(jī)制有個粗略的了解。
我相信各位看官在閱讀這篇文章前都是帶著問題的,我們將通過問題來解答大家的疑惑。
分析
Looper
在分析Looper之前,我們還需要知道ThreadLocal這個類,如果對ThreadLocal還不太了解,可以去看我的另一篇文章《ThreadLocal詳解》。
Looper是如何創(chuàng)建?
Handler執(zhí)行的線程和它持有的Looper有關(guān)。每個Thread都可以創(chuàng)建唯一的Looper對象。
//為當(dāng)前線程創(chuàng)建Looper對象的方法。
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//使用ThreadLocal來存儲當(dāng)前線程的Looper對象,這保證了每個線程有且僅有一個Looper對象。
//這里做了非空判斷,所以在同一個線程prepare方法是不允許被調(diào)用兩次的
//第一次創(chuàng)建好的Looper對象不會被覆蓋,它是唯一的。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
那么主線程的Looper對象是怎么創(chuàng)建的呢?
public static void prepareMainLooper() {
//其實主線程創(chuàng)建Looper和其他線程沒有區(qū)別,也是調(diào)用prepare()。
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//但是Looper用sMainLooper這個靜態(tài)變量將主線程的Looper對象存儲了起來
//可以通過getMainLooper()獲取,存儲MainLooper其實非常有作用,下面會講到。
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
Looper是如何從MessageQueue取出消息并分發(fā)的?
Looper分發(fā)消息的主要邏輯在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();
//保證當(dāng)前線程必須有Looper對象,如果沒有則拋出異常,調(diào)用Looper.loop()之前應(yīng)該先調(diào)用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 (;;) {
//這里開始執(zhí)行死循環(huán),queue通過調(diào)用next方法來取出下一個消息。
//很多人很疑惑死循環(huán)不會相當(dāng)耗費性能嗎,如果沒有那么多消息怎么辦?
//其實當(dāng)沒有消息的時候,next方法會阻塞在這里,不會往下執(zhí)行了,性能問題不存在。
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
//這里滿足了死循環(huán)跳出的條件,即取出的消息為null
//沒有消息next不是會阻塞嗎,怎么會返回null呢?
//其實只有MessageQueue停止的時候(調(diào)用quit方法),才會返回null
//MessageQueue停止后,調(diào)用next返回null,且不再接受新消息,下面還有詳細(xì)介紹。
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對象,分發(fā)消息到Handler去執(zhí)行。
//有人問主線程可以創(chuàng)建這么多Handler,怎么保證這個Handler發(fā)送的消息不會跑到其它Handler去執(zhí)行呢?
//那是因為在發(fā)送Message時,他會綁定發(fā)送的Handler,在此處分發(fā)消息時,也只會回調(diào)發(fā)送該條消息的Handler。
//那么分發(fā)消息具體在哪個線程執(zhí)行呢?
//我覺得這個不該問,那當(dāng)然是當(dāng)前方法在哪個線程調(diào)用就在哪個線程執(zhí)行啦。
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對象進(jìn)行回收,會清空所有之前Message設(shè)置的數(shù)據(jù)。
//正是因為Message有回收機(jī)制,我們在創(chuàng)建消息的時候應(yīng)該優(yōu)先選擇Message.obtain().
//如果發(fā)送的消息足夠多,Message緩存的Message對象不夠了,obtain內(nèi)部會調(diào)用new Message()創(chuàng)建一個新的對象。
msg.recycleUnchecked();
}
}
Looper 分發(fā)的消息在哪個線程執(zhí)行?
先給大家展示一段Looper文檔上的示例代碼
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); //創(chuàng)建LooperThread的Looper對象
mHandler = new Handler() {
public void handleMessage(Message msg) {
//處理發(fā)送過來的消息
}
};
Looper.loop(); //開始循環(huán)消息隊列
}
}
上面這段代碼相信很多人都寫過,這是一段在子線程創(chuàng)建Handler的案例,其中handleMessage所執(zhí)行的線程為LooperThread,因為Looper.loop()執(zhí)行在LooperThread的run方法里??梢栽谄渌€程通過mHandler發(fā)送消息到LooperThread
如果不調(diào)用Looper.prepare()直接new Handler()會怎么樣呢?
我們可以查看Handler的源碼看看無參構(gòu)造是如何運行的
public Handler() {
//調(diào)用兩參構(gòu)造
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());
}
}
//獲取當(dāng)前線程的Looper,如果不創(chuàng)建Looper會拋出異常。
//主線程我也沒看到有調(diào)用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對象在哪里創(chuàng)建的?
從上一個問題可以看出如果不調(diào)用Looper.prepare()直接new Handler()就會拋出異常`
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");`
那么主線程的Looper在哪里創(chuàng)建的呢?首先它是創(chuàng)建了的,因為Looper.getMainLooper() != null,其實MainLooper創(chuàng)建的時間比我們想象的早,它在ActivityThread類里面,ActivityThread是Android的啟動類,main方法就在里面(如果有人問你Android有沒有main方法,你應(yīng)該知道怎么回答了吧),而MainLooper就是在main方法里面創(chuàng)建的。
上代碼:
//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>");
//注意這里,這里創(chuàng)建了主線程的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"));
}
//開啟消息循環(huán)
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
MainLooper可以用來做什么
判斷當(dāng)前線程是否為主線程
因為Looper是在某一線程唯一的,那么可以在么做。如果
public static boolean isMainThread() {
//如果當(dāng)前線程的Looper和MainLooper是同一個對象,那么可以認(rèn)為當(dāng)前線程是主線程
return Looper.myLooper() == Looper.getMainLooper() ;
}
但是也有人說下面這樣也可以
public static boolean isMainThread() {
//這個方法其實是不準(zhǔn)確的,線程的名稱是可以隨便更改的。
return Thread.currentThread().getName().equals("main");
}
所以用Looper來判斷主線程是很好的做法
創(chuàng)建運行在主線程的Handler
Handler除了有無參構(gòu)造,還有一個可以傳入Looper的構(gòu)造。通過指定Looper,可以在任意地方創(chuàng)建運行在主線程的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方法有什么區(qū)別
下面是Looper兩個方法的源碼
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
可以看出實際上是調(diào)用的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;
//如果調(diào)用的是quitSafely運行removeAllFutureMessagesLocked,否則removeAllMessagesLocked。
if (safe) {
//該方法只會清空MessageQueue消息池中所有的延遲消息,
//并將消息池中所有的非延遲消息派發(fā)出去讓Handler去處理,
//quitSafely相比于quit方法安全之處在于清空消息之前會派發(fā)所有的非延遲消息。
removeAllFutureMessagesLocked();
} else {
//該方法的作用是把MessageQueue消息池中所有的消息全部清空,
//無論是延遲消息(延遲消息是指通過sendMessageDelayed或通過postDelayed等方法發(fā)送的需要延遲執(zhí)行的消息)還是非延遲消息。
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
無論是調(diào)用了quit方法還是quitSafely方法,MessageQueue將不再接收新的Message,此時消息循環(huán)就結(jié)束,MessageQueued的next方法將返回null,結(jié)束loop()的死循環(huán).這時候再通過Handler調(diào)用sendMessage或post等方法發(fā)送消息時均返回false,表示消息沒有成功放入消息隊列MessageQueue中,因為消息隊列已經(jīng)退出了。
Message
Message.obtain()和new Message()如何選擇
Message提供了obtain等多個重載的方法來創(chuàng)建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(); //只有當(dāng)從對象池里取不出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是創(chuàng)建了一個新的對象。我們應(yīng)該使用obtain來創(chuàng)建Message對象,每次使用完后都會自動進(jìn)行回收,節(jié)省內(nèi)存。
......