一:背景
眾所周知,Android不允許在UI線程中做耗時的操作,否則有可能發(fā)生ANR的可能,默認(rèn)情況下,在Android中Activity的最長執(zhí)行時間是5秒,BroadcastReceiver的最長執(zhí)行時間則是10秒。如果操作這個時長,則會產(chǎn)生ANR。而本文所要介紹的主要內(nèi)容是檢測UI線程中的耗時操作,從而能夠定位一些老代碼中的各種耗時的操作,作為性能優(yōu)化的依據(jù)。
二:常見方案
1 通過UI 線程looper
2 通過Choreographer
2.1 通過UI 線程looper的打印日志
在github上也有許多開源庫是基于該原理,比較有代表行的有
AndroidPerformanceMonitor
ANR-WatchDog
下面以AndroidPerformanceMonitor為例子來進(jìn)行原理的介紹
AndroidPerformanceMonitor
?通過如下代碼可以看到,只存在一個主線程的Looper,所有通過主線程Handler發(fā)送的消息,都會發(fā)送到這里。
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
繼續(xù)仔細(xì)觀察Looper的源碼可以發(fā)現(xiàn),msg.target.dispatchMessage(msg); 這句用來進(jìn)行消息的分發(fā)處理,在處理前后分別會打印日志,如果我們在消息處理之前計一個時,在消息處理之后計算一個值,如果這兩者的閾值,大于了我們設(shè)定的門限,那么就可以認(rèn)為卡頓。
public static void loop() {
final Looper me = myLooper();
...
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);
}
...
}
}
但是MainLooper里面的默認(rèn)打印的消息,并沒有記錄時間,這時,我們需要通過Looper.setMessageLogging來設(shè)置自定義的 Printer 。
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
在BlockCanary中 定義了自定義的Printer
class LooperMonitor implements Printer
其println()方法邏輯就是消息處理之后各記錄時間值,計算這兩者的閾值,判斷是否block。
@Override
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
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();
}
}
流程圖

2.2 通過UI 線程looper循環(huán)發(fā)送消息
比較有代表性的作品
ANR-WatchDog
實現(xiàn)十分的簡單,主要的類就是一個Thread, 通過使用主線程的Handler,不斷的發(fā)送任務(wù), 使得變量_tick的值不斷的增加,然后再去做判斷,_tick的值是否能得到,如果_tick的值沒有變,則認(rèn)為UI線程已經(jīng)卡頓.
private final Runnable _ticker = new Runnable() {
@Override public void run() {
_tick = (_tick + 1) % Integer.MAX_VALUE;
}
};
@Override
public void run() {
setName("|ANR-WatchDog|");
int lastTick;
int lastIgnored = -1;
while (!isInterrupted()) {
lastTick = _tick;
_uiHandler.post(_ticker);
try {
//睡眠一段時間,確保_tick的值更新后再做判斷。
Thread.sleep(_timeoutInterval);
}
catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
// If the main thread has not handled _ticker, it is blocked. ANR.
if (_tick == lastTick) {
if (!_ignoreDebugger && Debug.isDebuggerConnected()) {
if (_tick != lastIgnored)
Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
lastIgnored = _tick;
continue ;
}
ANRError error;
if (_namePrefix != null)
error = ANRError.New(_namePrefix, _logThreadsWithoutStackTrace);
else
error = ANRError.NewMainOnly();
_anrListener.onAppNotResponding(error);
return;
}
}
}
通過Choreographer
Takt
Android系統(tǒng)從4.1(API 16)開始加入Choreographer這個類來控制同步處理輸入(Input)、動畫(Animation)、繪制(Draw)三個UI操作。其實UI顯示的時候每一幀要完成的事情只有這三種。

Choreographer接收顯示系統(tǒng)的時間脈沖(垂直同步信號-VSync信號),在下一個frame渲染時控制執(zhí)行這些操作。
Choreographer.getInstance().postFrameCallback(new FPSFrameCallback());
把你的回調(diào)添加到Choreographer之中,那么在下一個frame被渲染的時候就會回調(diào)你的callback.
通過判斷兩次回調(diào)doFrame執(zhí)行的時間差,來判斷是否發(fā)生ANR
以Takt為例。一下為其核心代碼,主要代碼都添加了注釋。
@Override
public void doFrame(long frameTimeNanos) {
long currentTimeMillis = TimeUnit.NANOSECONDS.toMillis(frameTimeNanos);
if (frameStartTime > 0) {
// take the span in milliseconds
final long timeSpan = currentTimeMillis - frameStartTime;
//渲染次數(shù)+1
framesRendered++;
if (timeSpan > interval) {
//超過閾值,計算刷新頻率
final double fps = framesRendered * 1000 / (double) timeSpan;
frameStartTime = currentTimeMillis;
framesRendered = 0;
for (Audience audience : listeners) {
//回調(diào)處理
audience.heartbeat(fps);
}
}
} else {
//第一次 frameStartTime=0;
frameStartTime = currentTimeMillis;
}
choreographer.postFrameCallback(this);
}
三:參考資料
BlockCanary — 輕松找出Android App界面卡頓元兇
Cockroach
Choreographer源碼分析
Android應(yīng)用ANR分析