相信很多人都會有一個疑問,我們爲何要去閱讀源碼,工作上又用不上,這個問題很棒,我們就先從使用出發,然後分析這些用法的實現原理,這樣才能體現出閱讀源碼的意義。
- 基於 Handler 和 Looper 攔截全局崩潰(主線程),避免 APP 退出。
- 基於 Handler 和 Looper 實現 ANR 監控。
- 基於 Handler 實現單線程的線程池。
實現代碼
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
var startWorkTimeMillis = 0L
Looper.getMainLooper().setMessageLogging {
if (it.startsWith(">>>>> Dispatching to Handler")) {
startWorkTimeMillis = System.currentTimeMillis()
} else if (it.startsWith("<<<<< Finished to Handler")) {
val duration = System.currentTimeMillis() - startWorkTimeMillis
if (duration > 100) {
Log.e("主線程執行耗時過長","$duration 毫秒,$it")
}
}
}
val handler = Handler(Looper.getMainLooper())
handler.post {
while (true) {
try {
Looper.loop()
} catch (e: Throwable) {
// TODO 主線程崩潰,自行上報崩潰信息
if (e.message != null && e.message!!.startsWith("Unable to start activity")) {
android.os.Process.killProcess(android.os.Process.myPid())
break
}
e.printStackTrace()
}
}
}
Thread.setDefaultUncaughtExceptionHandler { thread, e ->
e.printStackTrace()
// TODO 異步線程崩潰,自行上報崩潰信息
}
}
}
通過上面的代碼就可以就可以實現攔截UI線程的崩潰,耗時性能監控。但是也並不能夠攔截所有的異常,如果在Activity的onCreate出現崩潰,導致Activity創建失敗,那麼就會顯示黑屏。
ANR獲取堆棧信息《Android:基於 Handler、Looper 實現 ANR 監控,獲取堆棧》
源碼剖析
通過上面簡單的代碼,我們就實現崩潰和ANR的攔截和監控,但是我們可能並不知道是爲何實現的,包括我們知道出現了ANR,但是我們還需要進一步分析爲何處出現ANR,如何解決。今天分析的問題有:
- 如何攔截全局崩潰,避免APP退出。
- 如何實現 ANR 監控。
- 利用 Handler 實現單線程池功能。
- Activity 的生命週期爲什麼用 Handler 發送執行。
- Handler 的延遲操作如何實現。
涉及的源碼
/java/android/os/Handler.java
/java/android/os/MessageQueue.java
/java/android/os/Looper.java
/java/android.app/ActivityThread.java
我們先從APP啓動開始分析,APP的啓動方法是在ActivityThread中,在main方法中創建了主線程的Looper,也就是當前進程創建。並且在main方法的最後調用了 Looper.loop()
,在這個方法中處理主線程的任務調度,一旦執行完這個方法就意味着APP被退出了,如果我們要避免APP被退出,就必須讓APP持續執行Looper.loop()。
package android.app;
public final class ActivityThread extends ClientTransactionHandler {
...
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
Looper.loop()
那我們進一步分析Looper.loop()
方法,在這個方法中寫了一個循環,只有當 queue.next() == null
的時候才退出,看到這裏我們心裏可能會有一個疑問,如果沒有主線程任務,是不是Looper.loop()
方法就退出了呢?實際上queue.next()
其實就是一個阻塞的方法,如果沒有任務或沒有主動退出,會一直在阻塞,一直等待主線程任務添加進來。
當隊列有任務,就會打印信息 Dispatching to ...
,然後就調用 msg.target.dispatchMessage(msg);
執行任務,執行完畢就會打印信息 Finished to ...
,我們就可以通過打印的信息來分析 ANR,一旦執行任務超過5秒就會觸發系統提示ANR,但是我們對自己的APP肯定要更加嚴格,我們可以給我們設定一個目標,超過指定的時長就上報統計,幫助我們進行優化。
public final class Looper {
final MessageQueue mQueue;
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;
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
}
try {
msg.target.dispatchMessage(msg);
} finally {}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
msg.recycleUnchecked();
}
}
public void quit() {
mQueue.quit(false);
}
}
如果主線程發生了異常,就會退出循環,意味着APP崩潰,所以我們我們需要進行try-catch,避免APP退出,我們可以在主線程再啓動一個 Looper.loop()
去執行主線程任務,然後try-catch這個Looper.loop()方法,就不會退出。
基於 Handler 實現單線程的線程池
從上面的 Looper.loop()
,我們可以利用 Handler 實現單線程池功能,而且這個線程池和主線程一樣擁有立刻執行post()
、延遲執行postDelayed()
、定時執行postAtTime()
等強大功能。
// 錯誤用法
var handler: Handler? = null
Thread({
handler = Handler()
}).start()
當我們在異步線程執行上面的代碼,就會報錯 Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
。 這個是因爲 Handler 的工作是依靠 Looper ,必須爲線程創建 Looper 才能正常功能,正確的用法如下:
// 正確用法
var handler: Handler? = null
Thread({
Looper.prepare()
handler = Handler()
Looper.loop()
}).start()
測試:
button.setOnClickListener {
handler?.post {
println(Thread.currentThread())
}
handler?.post {
println(Thread.currentThread())
}
}
輸出結果:
System.out: Thread[Thread-2,5,main]
System.out: Thread[Thread-2,5,main]
HandlerThread
HandlerThread 是 Android 對Thread的封裝,增加了Handler的支持,實現就是實現了前面例子的功能
val handlerThread = HandlerThread("test")
handlerThread.start()
handler = Handler(handlerThread.looper)
MessageQueue 源碼剖析
我們都知道Handler的功能非常豐富,擁有立刻執行post()
、延遲執行postDelayed()
、定時執行postAtTime()
等執行方式。下面就從源碼分析是如何實現的。
public final class MessageQueue {
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
}
MessageQueue.next() 是一個帶有阻塞的方法,只有退出或者有任務纔會return,起阻塞的實現是使用Native層的 nativePollOnce()
函數,如果消息隊列中沒有消息存在nativePollOnce就不會返回,一直處於Native層等待狀態。直到調用 quit()
退出或者調用 enqueueMessage(Message msg, long when)
有新的任務進來調用了Native層的nativeWake()
函數,纔會重新喚醒。 android_os_MessageQueue.cpp
nativePollOnce(long ptr, int timeoutMillis)
nativePollOnce
是一個帶有兩個參數的Native函數,第一個參數是作爲當前任務隊列ID;第二個參數是等待時長,如果是-1,就代表無消息,會進入等待狀態,如果是 0,再次查找未等待的消息。如果大於0,就等到指定時長然後返回。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
在這行代碼進行延時的賦值,從而實現postDelayed、postAtTime的功能
enqueueMessage()
看到這裏我們可能會有一個疑問,既然是隊列,先進先出的原則,那麼以下代碼輸出的結果是如何?
handler?.postDelayed({ println("任務1") },5000)
handler?.post { println("任務2") }
handler?.postDelayed({ println("任務3") },3000)
// 輸出結果
任務2
任務3
任務1
之所以是如此,是因爲在 enqueueMessage(Message msg, long when)
添加任務的時候已經就已經按照執行的時間要求做好了排序。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
總結
經過上述的分析,我覺得弄懂Handler和Looper MessageQueue還是很有意義的,可以幫助我們更好處理崩潰、ANR、Handler的使用等。
好了,文章到這裏就結束瞭如果你覺得文章還算有用的話,不妨把它們推薦給你的朋友。