對于一位Android開發(fā)者來說约谈,對
Handler
、Looper
子房、Message
三個乖寶貝應(yīng)該再熟悉不過了狭莱,這里我們先簡單介紹下這三者的關(guān)系,之后再用Looper.loop
方法做點(diǎn)有意思的事情皆愉,加深對運(yùn)行循環(huán)的理解嗜价。
一、源碼理解Handler
幕庐、Looper
久锥、Message
通常我們在使用Handler
時會在主線程中new出一個Handler
來接收消息,我們來看下Handler
源碼:
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
public Handler() {
this(null, false);
}
在源碼注釋中說到默認(rèn)的構(gòu)造方法創(chuàng)建Handler
异剥,會從當(dāng)前線程中取出Looper
瑟由,如果當(dāng)前線程沒有Looper
,這個Handler
不能夠接收到消息并會拋出異常届吁。
我們繼續(xù)點(diǎn)進(jìn)去:
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
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;
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return sThreadLocal.get();//從ThreadLocal中獲取
}
既然Looper
是從ThreadLocal
中獲取的错妖,那必然有時機(jī)要存進(jìn)去,我們看下Looper
是什么時候存進(jìn)去的:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
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));
}
也就是說我們在調(diào)用Looper. prepare
方法時會創(chuàng)建Looper
并存入ThreadLocal
中疚沐,注意默認(rèn)quitAllowed
參數(shù)都為true暂氯,也就是默認(rèn)創(chuàng)建的Looper
都是可以退出的,我們可以點(diǎn)進(jìn)去看看:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
//進(jìn)去MessageQueue.java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
??注意:MessageQueue
的成員變量mQuitAllowed
亮蛔,在調(diào)用Looper.quit
方法時會進(jìn)入MessageQueue
對mQuitAllowed
進(jìn)行判斷痴施,可以簡單看下源碼,后面會再說到:
//MessageQueue.java
void quit(boolean safe) {
//如果mQuitAllowed為false究流,也就是不允許退出時會報出異常
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
看到這里我們應(yīng)該是有疑問的辣吃,
- 第一個疑問:默認(rèn)我們調(diào)用
Looper.prepare
方法時mQuitAllowed
變量都為true的,那它什么時候為false?又是被如何設(shè)為false的芬探?
- 第二個疑問:我們在創(chuàng)建
Handler
時神得,并沒有往ThreadLocal
中存Looper
,而卻直接就取出了ThreadLocal
中的Looper
偷仿,那么這個Looper
是什么時候創(chuàng)建并存入的哩簿?
這里就要說到ActivityThread
中main
方法了宵蕉。Zygote進(jìn)程孵化出新的應(yīng)用進(jìn)程后,會執(zhí)行ActivityThread
類的main
方法节榜。在該方法里會先準(zhǔn)備好Looper
和消息隊列羡玛,并將Looper
存入ThreadLocal
中,然后調(diào)用attach
方法將應(yīng)用進(jìn)程綁定到ActivityManagerService
宗苍,然后進(jìn)入loop循環(huán)稼稿,不斷地讀取消息隊列里的消息,并分發(fā)消息讳窟。
//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());
Process.setArgV0("<pre-initialized>");
//創(chuàng)建主線程的阻塞隊列
Looper.prepareMainLooper();
// 創(chuàng)建ActivityThread實例
ActivityThread thread = new ActivityThread();
//執(zhí)行初始化
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");
}
我們看下開啟的loop循環(huán)吧:
/**
* 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();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
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 (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
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為發(fā)送msg的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);
}
msg.recycleUnchecked();
}
}
Looper.loop
方法內(nèi)部是個死循環(huán)(for(;;))让歼。queue.next();
是從阻塞隊列里取走頭部的Message,當(dāng)沒有Message時主線程就會阻塞丽啡。view繪制是越,事件分發(fā),activity啟動碌上,activity的生命周期回調(diào)等等都是一個個的Message倚评,系統(tǒng)會把這些Message插入到主線程中唯一的queue中,所有的消息都排隊等待主線程的執(zhí)行馏予。
回過來我們捋一下思路天梧,首先霞丧,我們在主線程中創(chuàng)建了Handler
,在Handler
的構(gòu)造方法中會判斷是否創(chuàng)建了Looper
蛹尝,由于在ActivityThread.main
方法中我們初始化了Looper
并將其存入ThreadLocal
中,所以可以正常創(chuàng)建Handler
突那。(而如果不是在主線程中創(chuàng)建Handler
挫酿,則需要在創(chuàng)建之前手動調(diào)用Looper.prepare
方法。)在Looper
的構(gòu)造方法中創(chuàng)建了MessageQueue
消息隊列用于存取Message早龟。然后猫缭,Handler.sendMessage
發(fā)送消息葱弟,在queue.enqueueMessage(msg, uptimeMillis)
方法中將Message存入MessageQueue
中,并最終在Loop.loop
循環(huán)中取出消息調(diào)用msg.target.dispatchMessage(msg);
也就是發(fā)送消息的Handler
的dispatchMessage
方法處理消息猜丹,在dispatchMessage
最終調(diào)用了handleMessage(msg);
方法芝加。這樣我們就可以正常處理發(fā)送到主線程的消息了藏杖。
二、用Looper搞事情
- 異步任務(wù)時阻塞線程制市,讓程序按需要順序執(zhí)行
- 判斷主線程是否阻塞
- 防止程序異常崩潰
1. 異步任務(wù)時阻塞線程弊予,讓程序按需要順序執(zhí)行
在處理異步任務(wù)的時候,通常我們會傳入回調(diào)來處理請求成功或者失敗的邏輯误褪,而我們通過Looper
處理消息機(jī)制也可以讓其順序執(zhí)行碾褂,不使用回調(diào)。我們來看下吧:
String a = "1";
public void click(View v){
new Thread(new Runnable() {
@Override
public void run() {
//模擬耗時操作
SystemClock.sleep(2000);
a = "22";
mHandler.post(new Runnable() {
@Override
public void run() {
mHandler.getLooper().quit();
}
});
}
}).start();
try{
Looper.loop();
}catch (Exception e){
}
Toast.makeText(getApplicationContext(),a,Toast.LENGTH_LONG).show();
}
當(dāng)點(diǎn)擊按鈕的時候我們開啟線程處理耗時操作嘀略,之后調(diào)用Looper.loop();
方法處理消息循環(huán)乓诽,也就是說主線程又開始不斷的讀取queue中的Message并執(zhí)行。這樣當(dāng)執(zhí)行mHandler.getLooper().quit();
時會調(diào)用MessageQueue
的quit
方法:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
...
}
這個就到了之前我們分析的變量mQuitAllowed
,主線程不允許退出讼育,這里會拋出異常稠集,而最終這段代碼是在Looper.loop
方法中獲取消息調(diào)用msg.target.dispatchMessage
執(zhí)行的剥纷,我們將Looper.loop
的異常給捕獲住了痹籍,從而之后代碼繼續(xù)執(zhí)行晦鞋,彈出Toast。
2. 判斷主線程是否阻塞
一般來說吼砂,Loop.loop
方法中會不斷取出Message鼎文,調(diào)用其綁定的Handler在UI線程進(jìn)行執(zhí)行主線程刷新操作拇惋。
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//注意這里 msg.target為發(fā)送msg的Handler
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
也就是這里抹剩,基本上可以說msg.target.dispatchMessage(msg);
我們可以根據(jù)這行代碼的執(zhí)行時間來判斷UI線程是否有耗時操作蓉坎。
在msg.target.dispatchMessage(msg);
前后,分別有logging
判斷并打印>>>>> Dispatching to
和<<<<< Finished to
的log钳踊,我們可以設(shè)置logging
并打印相應(yīng)時間勿侯,基本就可以判斷消耗時間。
Looper.getMainLooper().setMessageLogging(new Printer() {
private static final String START = ">>>>> Dispatching";
private static final String END = "<<<<< Finished";
@Override
public void println(String x) {
if (x.startsWith(START)) {
//開始
}
if (x.startsWith(END)) {
//結(jié)束
}
}
});
3. 防止程序異常崩潰
既然主線程異常事件最終都是在Looper.loop
調(diào)用中發(fā)生的祭埂,那我們在Looper.loop
方法中將異常捕獲住蛆橡,那主線程的異常也就不會導(dǎo)致程序異常了:
private Handler mHandler = new Handler();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_test);
mHandler.post(new Runnable() {
@Override
public void run() {
while (true){
try{
Looper.loop();
}catch (Exception e){
}
}
}
});
}
public void click2(View v){
int a = 1/0;//除數(shù)為0 運(yùn)行時報錯
}
主線程的所有異常都會從我們手動調(diào)用的Looper.loop
處拋出掘譬,一旦拋出就會被try{}catch
捕獲,這樣主線程就不會崩潰了粥血。此原理的開源項目:Cockroach酿箭,有興趣可以看下具體實現(xiàn)。