blockCanary
對(duì)于android里面的性能優(yōu)化,最主要的問(wèn)題就是UI線程的阻塞導(dǎo)致的,對(duì)于如何準(zhǔn)確的計(jì)算UI的繪制所耗費(fèi)的時(shí)間,是非常有必要的,blockCanary是基于這個(gè)需求出現(xiàn)的,同樣的,也是基于LeakCanary,和LeakCanary有著顯示頁(yè)面和堆棧信息。
使用
首先在gradle引入
implementation 'com.github.markzhai:blockcanary-android:1.5.0'
然后Application里面進(jìn)行初始化和start
BlockCanary.install(this, new BlockCanaryContext()).start();
原理:
其中BlockCanaryContext表示的就是我們監(jiān)測(cè)的某些參數(shù),包括卡頓的閾值、輸出文件的路徑等等
//默認(rèn)卡頓閾值為1000ms
public int provideBlockThreshold() {
return 1000;
}
//輸出的log
public String providePath() {
return "/blockcanary/";
}
//支持文件上傳
public void upload(File zippedFile) {
throw new UnsupportedOperationException();
}
//可以在卡頓提供自定義操作
@Override
public void onBlock(Context context, BlockInfo blockInfo) {
}
其中,init只是創(chuàng)建出BlockCanary實(shí)例。主要是start方法的操作。
/**
* Start monitoring.
*/
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
其實(shí)就是給主線程的Looper設(shè)置一個(gè)monitor。
我們可以先看看主線程的looper實(shí)現(xiàn)。
public static void loop() {
...
for (;;) {
...
// 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);
}
...
}
}
在上面的loop循環(huán)的代碼中,msg.target.dispatchMessage就是我們UI線程收到每一個(gè)消息需要執(zhí)行的操作,都在其內(nèi)部執(zhí)行。
系統(tǒng)也在其執(zhí)行的前后都會(huì)執(zhí)行l(wèi)ogging類的print的方法,這個(gè)方法是我們可以自定義的。所以只要我們?cè)谶\(yùn)行的前后都添加一個(gè)時(shí)間戳,用運(yùn)行后的時(shí)間減去運(yùn)行前的時(shí)間,一旦這個(gè)時(shí)間超過(guò)了我們?cè)O(shè)定的閾值,那么就可以說(shuō)這個(gè)操作卡頓,阻塞了UI線程,最后通過(guò)dump出此時(shí)的各種信息,來(lái)分析各種性能瓶頸。
那么接下來(lái)可以看看這個(gè)monitor的println方法。
@Override
public void println(String x) {
//如果當(dāng)前是在調(diào)試中,那么直接返回,不做處理
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
//執(zhí)行操作前
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else {
//執(zhí)行操作后
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
//是否卡頓
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
在ui操作執(zhí)行前,將會(huì)記錄當(dāng)前的時(shí)間戳,同時(shí)會(huì)startDump。
在ui操作執(zhí)行后,將會(huì)計(jì)算當(dāng)前是否卡頓了,如果卡頓了,將會(huì)回調(diào)到onBlock的onBlock方法。同時(shí)將會(huì)停止dump。
為什么操作之前就開(kāi)啟了startDump,而操作執(zhí)行之后就stopDump呢?
private void startDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
BlockCanaryInternals.getInstance().stackSampler.start();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
BlockCanaryInternals.getInstance().cpuSampler.start();
}
}
public void start() {
if (mShouldSample.get()) {
return;
}
mShouldSample.set(true);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
其實(shí)startDump的時(shí)候并沒(méi)有馬上start,而是會(huì)postDelay一個(gè)runnable,這個(gè)runnable就是執(zhí)行dump的真正的操作,delay的時(shí)間就是我們?cè)O(shè)置的閾值的0.8
也就是,一旦我們的stop在設(shè)置的延遲時(shí)間之前執(zhí)行,就不會(huì)真正的執(zhí)行dump操作。
public void stop() {
if (!mShouldSample.get()) {
return;
}
mShouldSample.set(false);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
只有當(dāng)stop操作在設(shè)置的延遲時(shí)間之后執(zhí)行,才會(huì)執(zhí)行dump操作。
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
doSample();
if (mShouldSample.get()) {
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
這個(gè)doSameple分別會(huì)dump出stack信息和cpu信息。
cpu:
try {
cpuReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/stat")), BUFFER_SIZE);
String cpuRate = cpuReader.readLine();
if (cpuRate == null) {
cpuRate = "";
}
if (mPid == 0) {
mPid = android.os.Process.myPid();
}
pidReader = new BufferedReader(new InputStreamReader(
new FileInputStream("/proc/" + mPid + "/stat")), BUFFER_SIZE);
String pidCpuRate = pidReader.readLine();
if (pidCpuRate == null) {
pidCpuRate = "";
}
parse(cpuRate, pidCpuRate);
}
stack:
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());
}
}
這樣,整個(gè)blockCanary的執(zhí)行過(guò)程就完畢了。
ANR
當(dāng)卡頓時(shí)間大于一定值之后,將會(huì)造成ANR,那么Android系統(tǒng)的ANR是如何檢測(cè)出來(lái)的呢?其實(shí)就是通過(guò)Watchdog來(lái)實(shí)現(xiàn)的,這個(gè)Watchdog是一個(gè)線程。
public class Watchdog extends Thread {
}
我們主要看一下其中的run方法的實(shí)現(xiàn)。
@Override
public void run() {
boolean waitedHalf = false;
while (true) {
final ArrayList<HandlerChecker> blockedCheckers;
final String subject;
final boolean allowRestart;
int debuggerWasConnected = 0;
synchronized (this) {
long timeout = CHECK_INTERVAL;
// Make sure we (re)spin the checkers that have become idle within
// this wait-and-check interval
for (int i=0; i<mHandlerCheckers.size(); i++) {
HandlerChecker hc = mHandlerCheckers.get(i);
hc.scheduleCheckLocked();
}
if (debuggerWasConnected > 0) {
debuggerWasConnected--;
}
// NOTE: We use uptimeMillis() here because we do not want to increment the time we
// wait while asleep. If the device is asleep then the thing that we are waiting
// to timeout on is asleep as well and won't have a chance to run, causing a false
// positive on when to kill things.
long start = SystemClock.uptimeMillis();
while (timeout > 0) {
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
try {
wait(timeout);
} catch (InterruptedException e) {
Log.wtf(TAG, e);
}
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
}
在這個(gè)run方法中,會(huì)開(kāi)啟一個(gè)死循環(huán),主要用于持續(xù)檢測(cè)ANR
while (true) {
}
通過(guò)wait,設(shè)置每一次休眠時(shí)間,
long start = SystemClock.uptimeMillis();
while (timeout > 0) {
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
try {
wait(timeout);
} catch (InterruptedException e) {
Log.wtf(TAG, e);
}
if (Debug.isDebuggerConnected()) {
debuggerWasConnected = 2;
}
timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
}
當(dāng)timeout計(jì)算完畢之后,會(huì)嘗試獲取當(dāng)前各個(gè)的線程的狀態(tài)
final int waitState = evaluateCheckerCompletionLocked();
private int evaluateCheckerCompletionLocked() {
int state = COMPLETED;
for (int i=0; i<mHandlerCheckers.size(); i++) {
HandlerChecker hc = mHandlerCheckers.get(i);
state = Math.max(state, hc.getCompletionStateLocked());
}
return state;
}
public int getCompletionStateLocked() {
if (mCompleted) {
return COMPLETED;
} else {
long latency = SystemClock.uptimeMillis() - mStartTime;
if (latency < mWaitMax/2) {
return WAITING;
} else if (latency < mWaitMax) {
return WAITED_HALF;
}
}
return OVERDUE;
}
一旦有線程等待時(shí)間超過(guò)了最大等待時(shí)間,則表示當(dāng)前已經(jīng)有ANR。需要dump此時(shí)的堆棧信息。
if (waitState == COMPLETED) {
// The monitors have returned; reset
waitedHalf = false;
continue;
} else if (waitState == WAITING) {
// still waiting but within their configured intervals; back off and recheck
continue;
} else if (waitState == WAITED_HALF) {
if (!waitedHalf) {
// We've waited half the deadlock-detection interval. Pull a stack
// trace and wait another half.
ArrayList<Integer> pids = new ArrayList<Integer>();
pids.add(Process.myPid());
ActivityManagerService.dumpStackTraces(true, pids, null, null,
getInterestingNativePids());
waitedHalf = true;
}
continue;
}
此外,還有一個(gè)第三方庫(kù),ANRWatchDog,也是用來(lái)檢測(cè)Anr的,其實(shí)原理更加簡(jiǎn)單,
public void run() {
setName("|ANR-WatchDog|");
int lastTick;
int lastIgnored = -1;
while (!isInterrupted()) {
lastTick = _tick;
_uiHandler.post(_ticker);
try {
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;
}
}
}
它會(huì)在線程中,利用uiHanlder拋出一個(gè)計(jì)數(shù)器,然后wait指定時(shí)間,一旦等待時(shí)間到達(dá),那么它會(huì)檢查計(jì)數(shù)的值是否發(fā)生改變,如果沒(méi)有發(fā)生改變,表示uiHandler的計(jì)算方法并沒(méi)有執(zhí)行到。也就是出現(xiàn)了Anr,此時(shí)需要dump堆棧信息。