Looper 提供的機制
先看看我們熟悉的 Looper 的源碼,裏面實現的功能就是不斷地從 MessageQueue 裏面取出 Message 對象,並加以執行。
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.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// ignore some code...
msg.recycleUnchecked();
}
注意到,在 dispatchMessage
的前後,分別有兩個 log 的輸出事件,而 dispatchMessage
就是線程上的一次消息處理。如果兩次消息處理事件,都超過了 16.67ms, 那就一定發生了卡頓,這也是 BlockCanary 的基礎原理。
BlockCanary 實現了 Printer,我們看看具體的實現。
class LooperMonitor implements Printer {
@Override
public void println(String x) {
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
// ignore other codes...
}
這裏實現了 println 方法,而 Looper 中也調用了 println 方法,而且在除非 dump 日誌的情況下,也只有在事件消息前後進行 println 操作。換而言之,我們可以初步認爲兩個 println 調用之間的時間超過 16.67ms 就證明了卡頓。上面的代碼也非常地清晰明瞭說明了這點。
輸出堆棧
在發現卡頓時,還需要提供當前線程的堆棧,這樣才能方便開發人員知曉在哪裏發生了卡頓,而 Java 剛好也提供了類似的機制,代碼也非常的簡單。
@Override
protected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append(BlockInfo.SEPARATOR);
}
synchronized (sStackMap) {
if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
sStackMap.remove(sStackMap.keySet().iterator().next());
}
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
BlockCanary 的原理簡介就到這裏,還是比較輕鬆和簡單的。
BlockCanary卡頓檢測流程圖
BlockCanary啓動一個線程負責保存UI線程當前堆棧信息,將堆棧信息以及CPU信息保存分別保存在 mThreadStackEntries和mCpuInfoEntries中,每條信息都以時間撮爲key保存。
BlockCanary註冊了logging來獲取事件開始結束時間。如果檢測到事件處理時間超過閾值(默認值1s),則從mThreadStackEntries中查找T1~T2這段時間內的堆棧信息,並且從mCpuInfoEntries中查找T1~T2這段時間內的CPU及內存信息。並且將信息格式化後保存到本地文件,並且通知用戶。
https://lrh1993.gitbooks.io/android_interview_guide/content/java/concurrence/thread-pool.html