相信很多人都會(huì)有一個(gè)疑問(wèn)瘾腰,我們?yōu)楹我ラ喿x源碼,工作上又用不上覆履,這個(gè)問(wèn)題很棒蹋盆,我們就先從使用出發(fā),然后分析這些用法的實(shí)現(xiàn)原理硝全,這樣才能體現(xiàn)出閱讀源碼的意義栖雾。
- 基于 Handler 和 Looper 攔截全局崩潰(主線程),避免 APP 退出伟众。
- 基于 Handler 和 Looper 實(shí)現(xiàn) ANR 監(jiān)控析藕。
- 基于 Handler 實(shí)現(xiàn)單線程的線程池。
實(shí)現(xiàn)代碼
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("主線程執(zhí)行耗時(shí)過(guò)長(zhǎng)","$duration 毫秒凳厢,$it")
}
}
}
val handler = Handler(Looper.getMainLooper())
handler.post {
while (true) {
try {
Looper.loop()
} catch (e: Throwable) {
// TODO 主線程崩潰账胧,自行上報(bào)崩潰信息
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 異步線程崩潰竞慢,自行上報(bào)崩潰信息
}
}
}
通過(guò)上面的代碼就可以就可以實(shí)現(xiàn)攔截UI線程的崩潰,耗時(shí)性能監(jiān)控治泥。但是也并不能夠攔截所有的異常筹煮,如果在Activity的onCreate出現(xiàn)崩潰,導(dǎo)致Activity創(chuàng)建失敗居夹,那么就會(huì)顯示黑屏败潦。
ANR獲取堆棧信息《Android:基于 Handler、Looper 實(shí)現(xiàn) ANR 監(jiān)控准脂,獲取堆棧》
源碼剖析
通過(guò)上面簡(jiǎn)單的代碼劫扒,我們就實(shí)現(xiàn)崩潰和ANR的攔截和監(jiān)控,但是我們可能并不知道是為何實(shí)現(xiàn)的狸膏,包括我們知道出現(xiàn)了ANR沟饥,但是我們還需要進(jìn)一步分析為何處出現(xiàn)ANR,如何解決环戈。今天分析的問(wèn)題有:
- 如何攔截全局崩潰,避免APP退出澎灸。
- 如何實(shí)現(xiàn) ANR 監(jiān)控院塞。
- 利用 Handler 實(shí)現(xiàn)單線程池功能。
- Activity 的生命周期為什么用 Handler 發(fā)送執(zhí)行性昭。
- Handler 的延遲操作如何實(shí)現(xiàn)拦止。
涉及的源碼
/java/android/os/Handler.java
/java/android/os/MessageQueue.java
/java/android/os/Looper.java
/java/android.app/ActivityThread.java
我們先從APP啟動(dòng)開(kāi)始分析,APP的啟動(dòng)方法是在ActivityThread中糜颠,在main方法中創(chuàng)建了主線程的Looper汹族,也就是當(dāng)前進(jìn)程創(chuàng)建。并且在main方法的最后調(diào)用了 Looper.loop()
其兴,在這個(gè)方法中處理主線程的任務(wù)調(diào)度顶瞒,一旦執(zhí)行完這個(gè)方法就意味著APP被退出了,如果我們要避免APP被退出元旬,就必須讓APP持續(xù)執(zhí)行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()
那我們進(jìn)一步分析Looper.loop()
方法,在這個(gè)方法中寫了一個(gè)循環(huán)匀归,只有當(dāng) queue.next() == null
的時(shí)候才退出坑资,看到這里我們心里可能會(huì)有一個(gè)疑問(wèn),如果沒(méi)有主線程任務(wù)穆端,是不是Looper.loop()
方法就退出了呢袱贮?實(shí)際上queue.next()
其實(shí)就是一個(gè)阻塞的方法,如果沒(méi)有任務(wù)或沒(méi)有主動(dòng)退出体啰,會(huì)一直在阻塞攒巍,一直等待主線程任務(wù)添加進(jìn)來(lái)嗽仪。
當(dāng)隊(duì)列有任務(wù),就會(huì)打印信息 Dispatching to ...
窑业,然后就調(diào)用 msg.target.dispatchMessage(msg);
執(zhí)行任務(wù)钦幔,執(zhí)行完畢就會(huì)打印信息 Finished to ...
,我們就可以通過(guò)打印的信息來(lái)分析 ANR常柄,一旦執(zhí)行任務(wù)超過(guò)5秒就會(huì)觸發(fā)系統(tǒng)提示ANR鲤氢,但是我們對(duì)自己的APP肯定要更加嚴(yán)格,我們可以給我們?cè)O(shè)定一個(gè)目標(biāo)西潘,超過(guò)指定的時(shí)長(zhǎng)就上報(bào)統(tǒng)計(jì)卷玉,幫助我們進(jìn)行優(yōu)化。
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);
}
}
如果主線程發(fā)生了異常喷市,就會(huì)退出循環(huán)相种,意味著APP崩潰,所以我們我們需要進(jìn)行try-catch品姓,避免APP退出寝并,我們可以在主線程再啟動(dòng)一個(gè) Looper.loop()
去執(zhí)行主線程任務(wù),然后try-catch這個(gè)Looper.loop()方法腹备,就不會(huì)退出衬潦。
基于 Handler 實(shí)現(xiàn)單線程的線程池
從上面的 Looper.loop()
,我們可以利用 Handler 實(shí)現(xiàn)單線程池功能植酥,而且這個(gè)線程池和主線程一樣擁有立刻執(zhí)行post()
镀岛、延遲執(zhí)行postDelayed()
、定時(shí)執(zhí)行postAtTime()
等強(qiáng)大功能友驮。
// 錯(cuò)誤用法
var handler: Handler? = null
Thread({
handler = Handler()
}).start()
當(dāng)我們?cè)诋惒骄€程執(zhí)行上面的代碼漂羊,就會(huì)報(bào)錯(cuò) Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
。
這個(gè)是因?yàn)?Handler 的工作是依靠 Looper 卸留,必須為線程創(chuàng)建 Looper 才能正常功能走越,正確的用法如下:
// 正確用法
var handler: Handler? = null
Thread({
Looper.prepare()
handler = Handler()
Looper.loop()
}).start()
測(cè)試:
button.setOnClickListener {
handler?.post {
println(Thread.currentThread())
}
handler?.post {
println(Thread.currentThread())
}
}
輸出結(jié)果:
System.out: Thread[Thread-2,5,main]
System.out: Thread[Thread-2,5,main]
HandlerThread
HandlerThread 是 Android 對(duì)Thread的封裝,增加了Handler的支持耻瑟,實(shí)現(xiàn)就是實(shí)現(xiàn)了前面例子的功能
val handlerThread = HandlerThread("test")
handlerThread.start()
handler = Handler(handlerThread.looper)
MessageQueue 源碼剖析
我們都知道Handler的功能非常豐富买喧,擁有立刻執(zhí)行post()
、延遲執(zhí)行postDelayed()
匆赃、定時(shí)執(zhí)行postAtTime()
等執(zhí)行方式淤毛。下面就從源碼分析是如何實(shí)現(xiàn)的。
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() 是一個(gè)帶有阻塞的方法算柳,只有退出或者有任務(wù)才會(huì)return低淡,起阻塞的實(shí)現(xiàn)是使用Native層的 nativePollOnce()
函數(shù),如果消息隊(duì)列中沒(méi)有消息存在nativePollOnce就不會(huì)返回,一直處于Native層等待狀態(tài)蔗蹋。直到調(diào)用 quit()
退出或者調(diào)用 enqueueMessage(Message msg, long when)
有新的任務(wù)進(jìn)來(lái)調(diào)用了Native層的nativeWake()
函數(shù)何荚,才會(huì)重新喚醒。
android_os_MessageQueue.cpp
nativePollOnce(long ptr, int timeoutMillis)
nativePollOnce
是一個(gè)帶有兩個(gè)參數(shù)的Native函數(shù)猪杭,第一個(gè)參數(shù)是作為當(dāng)前任務(wù)隊(duì)列ID餐塘;第二個(gè)參數(shù)是等待時(shí)長(zhǎng),如果是-1皂吮,就代表無(wú)消息戒傻,會(huì)進(jìn)入等待狀態(tài),如果是 0蜂筹,再次查找未等待的消息需纳。如果大于0,就等到指定時(shí)長(zhǎng)然后返回艺挪。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
在這行代碼進(jìn)行延時(shí)的賦值不翩,從而實(shí)現(xiàn)postDelayed、postAtTime的功能
enqueueMessage()
看到這里我們可能會(huì)有一個(gè)疑問(wèn)麻裳,既然是隊(duì)列口蝠,先進(jìn)先出的原則,那么以下代碼輸出的結(jié)果是如何津坑?
handler?.postDelayed({ println("任務(wù)1") },5000)
handler?.post { println("任務(wù)2") }
handler?.postDelayed({ println("任務(wù)3") },3000)
// 輸出結(jié)果
任務(wù)2
任務(wù)3
任務(wù)1
之所以是如此妙蔗,是因?yàn)樵?enqueueMessage(Message msg, long when)
添加任務(wù)的時(shí)候已經(jīng)就已經(jīng)按照?qǐng)?zhí)行的時(shí)間要求做好了排序。
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;
}
攔截主進(jìn)程崩潰
攔截主進(jìn)程崩潰其實(shí)也有一定的弊端国瓮,因?yàn)榻o用戶的感覺(jué)是點(diǎn)擊沒(méi)有反應(yīng)灭必,因?yàn)楸罎⒁呀?jīng)被攔截了狞谱。如果是Activity.create崩潰乃摹,會(huì)出現(xiàn)黑屏問(wèn)題,所以如果Activity.create崩潰跟衅,必須殺死進(jìn)程孵睬,讓APP重啟,避免出現(xiàn)改問(wèn)題伶跷。
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
new Handler(getMainLooper()).post(() -> {
while (true) {
try {
Looper.loop();
} catch (Throwable e) {
e.printStackTrace();
// TODO 需要手動(dòng)上報(bào)錯(cuò)誤到異常管理平臺(tái)掰读,比如bugly,及時(shí)追蹤問(wèn)題所在叭莫。
if (e.getMessage() != null && e.getMessage().startsWith("Unable to start activity")) {
// 如果打開(kāi)Activity崩潰蹈集,就殺死進(jìn)程,讓APP重啟雇初。
Process.killProcess(Process.myPid());
break;
}
}
}
});
}
}
總結(jié)
經(jīng)過(guò)上述的分析拢肆,我覺(jué)得弄懂Handler和Looper MessageQueue還是很有意義的,可以幫助我們更好處理崩潰、ANR郭怪、Handler的使用等支示。