對於一位Android開發者來說,對
Handler
、Looper
、Message
三個乖寶貝應該再熟悉不過了,這裏我們先簡單介紹下這三者的關係,之後再用Looper.loop
方法做點有意思的事情,加深對運行循環的理解。
一、源碼理解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);
}
在源碼註釋中說到默認的構造方法創建Handler
,會從當前線程中取出Looper
,如果當前線程沒有Looper
,這個Handler
不能夠接收到消息並會拋出異常。
我們繼續點進去:
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
中獲取的,那必然有時機要存進去,我們看下Looper
是什麼時候存進去的:
/** 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));
}
也就是說我們在調用Looper. prepare
方法時會創建Looper
並存入ThreadLocal
中,注意默認quitAllowed
參數都爲true,也就是默認創建的Looper
都是可以退出的,我們可以點進去看看:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
//進去MessageQueue.java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
⚠️注意:MessageQueue
的成員變量mQuitAllowed
,在調用Looper.quit
方法時會進入MessageQueue
對mQuitAllowed
進行判斷,可以簡單看下源碼,後面會再說到:
//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);
}
}
看到這裏我們應該是有疑問的,
- 第一個疑問:默認我們調用
Looper.prepare
方法時mQuitAllowed
變量都爲true的,那它什麼時候爲false?又是被如何設爲false的?
- 第二個疑問:我們在創建
Handler
時,並沒有往ThreadLocal
中存Looper
,而卻直接就取出了ThreadLocal
中的Looper
,那麼這個Looper
是什麼時候創建並存入的?
這裏就要說到ActivityThread
中main
方法了。Zygote進程孵化出新的應用進程後,會執行ActivityThread
類的main
方法。在該方法裏會先準備好Looper
和消息隊列,並將Looper
存入ThreadLocal
中,然後調用attach
方法將應用進程綁定到ActivityManagerService
,然後進入loop循環,不斷地讀取消息隊列裏的消息,並分發消息。
//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>");
//創建主線程的阻塞隊列
Looper.prepareMainLooper();
// 創建ActivityThread實例
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");
}
我們看下開啓的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();
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爲發送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
方法內部是個死循環(for(;;))。queue.next();
是從阻塞隊列裏取走頭部的Message,當沒有Message時主線程就會阻塞。view繪製,事件分發,activity啓動,activity的生命週期回調等等都是一個個的Message,系統會把這些Message插入到主線程中唯一的queue中,所有的消息都排隊等待主線程的執行。
回過來我們捋一下思路,首先,我們在主線程中創建了Handler
,在Handler
的構造方法中會判斷是否創建了Looper
,由於在ActivityThread.main
方法中我們初始化了Looper
並將其存入ThreadLocal
中,所以可以正常創建Handler
。(而如果不是在主線程中創建Handler
,則需要在創建之前手動調用Looper.prepare
方法。)在Looper
的構造方法中創建了MessageQueue
消息隊列用於存取Message。然後,Handler.sendMessage
發送消息,在queue.enqueueMessage(msg, uptimeMillis)
方法中將Message存入MessageQueue
中,並最終在Loop.loop
循環中取出消息調用msg.target.dispatchMessage(msg);
也就是發送消息的Handler
的dispatchMessage
方法處理消息,在dispatchMessage
最終調用了handleMessage(msg);
方法。這樣我們就可以正常處理髮送到主線程的消息了。
二、用Looper搞事情
- 異步任務時阻塞線程,讓程序按需要順序執行
- 判斷主線程是否阻塞
- 防止程序異常崩潰
1. 異步任務時阻塞線程,讓程序按需要順序執行
在處理異步任務的時候,通常我們會傳入回調來處理請求成功或者失敗的邏輯,而我們通過Looper
處理消息機制也可以讓其順序執行,不使用回調。我們來看下吧:
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();
}
當點擊按鈕的時候我們開啓線程處理耗時操作,之後調用Looper.loop();
方法處理消息循環,也就是說主線程又開始不斷的讀取queue中的Message並執行。這樣當執行mHandler.getLooper().quit();
時會調用MessageQueue
的quit
方法:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
...
}
這個就到了之前我們分析的變量mQuitAllowed
,主線程不允許退出,這裏會拋出異常,而最終這段代碼是在Looper.loop
方法中獲取消息調用msg.target.dispatchMessage
執行的,我們將Looper.loop
的異常給捕獲住了,從而之後代碼繼續執行,彈出Toast。
2. 判斷主線程是否阻塞
一般來說,Loop.loop
方法中會不斷取出Message,調用其綁定的Handler在UI線程進行執行主線程刷新操作。
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//注意這裏 msg.target爲發送msg的Handler
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
也就是這裏,基本上可以說msg.target.dispatchMessage(msg);
我們可以根據這行代碼的執行時間來判斷UI線程是否有耗時操作。
在msg.target.dispatchMessage(msg);
前後,分別有logging
判斷並打印>>>>> Dispatching to
和<<<<< Finished to
的log,我們可以設置logging
並打印相應時間,基本就可以判斷消耗時間。
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)) {
//結束
}
}
});
3. 防止程序異常崩潰
既然主線程異常事件最終都是在Looper.loop
調用中發生的,那我們在Looper.loop
方法中將異常捕獲住,那主線程的異常也就不會導致程序異常了:
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;//除數爲0 運行時報錯
}
主線程的所有異常都會從我們手動調用的Looper.loop
處拋出,一旦拋出就會被try{}catch
捕獲,這樣主線程就不會崩潰了。此原理的開源項目:Cockroach,有興趣可以看下具體實現。