android blockcanary 原理,blockCanary原理
blockCanary
對于android里面的性能優化,最主要的問題就是UI線程的阻塞導致的,對于如何準確的計算UI的繪制所耗費的時間,是非常有必要的,blockCanary是基于這個需求出現的,同樣的,也是基于LeakCanary,和LeakCanary有著顯示頁面和堆棧信息。
使用
首先在gradle引入
implementation 'com.github.markzhai:blockcanary-android:1.5.0'
然后Application里面進行初始化和start
BlockCanary.install(this, new BlockCanaryContext()).start();
原理:
其中BlockCanaryContext表示的就是我們監測的某些參數,包括卡頓的閾值、輸出文件的路徑等等
//默認卡頓閾值為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只是創建出BlockCanary實例。主要是start方法的操作。
/**
* Start monitoring.
*/
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
其實就是給主線程的Looper設置一個monitor。
我們可以先看看主線程的looper實現。
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循環的代碼中,msg.target.dispatchMessage就是我們UI線程收到每一個消息需要執行的操作,都在其內部執行。
系統也在其執行的前后都會執行logging類的print的方法,這個方法是我們可以自定義的。所以只要我們在運行的前后都添加一個時間戳,用運行后的時間減去運行前的時間,一旦這個時間超過了我們設定的閾值,那么就可以說這個操作卡頓,阻塞了UI線程,最后通過dump出此時的各種信息,來分析各種性能瓶頸。
那么接下來可以看看這個monitor的println方法。
@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();
}
}
在ui操作執行前,將會記錄當前的時間戳,同時會startDump。
在ui操作執行后,將會計算當前是否卡頓了,如果卡頓了,將會回調到onBlock的onBlock方法。同時將會停止dump。
為什么操作之前就開啟了startDump,而操作執行之后就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());
}
其實startDump的時候并沒有馬上start,而是會postDelay一個runnable,這個runnable就是執行dump的真正的操作,delay的時間就是我們設置的閾值的0.8
也就是,一旦我們的stop在設置的延遲時間之前執行,就不會真正的執行dump操作。
public void stop() {
if (!mShouldSample.get()) {
return;
}
mShouldSample.set(false);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
只有當stop操作在設置的延遲時間之后執行,才會執行dump操作。
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
doSample();
if (mShouldSample.get()) {
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
這個doSameple分別會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());
}
}
這樣,整個blockCanary的執行過程就完畢了。
ANR
當卡頓時間大于一定值之后,將會造成ANR,那么Android系統的ANR是如何檢測出來的呢?其實就是通過Watchdog來實現的,這個Watchdog是一個線程。
public class Watchdog extends Thread {
}
我們主要看一下其中的run方法的實現。
@Override
public void run() {
boolean waitedHalf = false;
while (true) {
final ArrayListblockedCheckers;
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; i0) {
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);
}
在這個run方法中,會開啟一個死循環,主要用于持續檢測ANR
while (true) {
}
通過wait,設置每一次休眠時間,
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);
}
當timeout計算完畢之后,會嘗試獲取當前各個的線程的狀態
final int waitState = evaluateCheckerCompletionLocked();
private int evaluateCheckerCompletionLocked() {
int state = COMPLETED;
for (int i=0; i
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;
}
一旦有線程等待時間超過了最大等待時間,則表示當前已經有ANR。需要dump此時的堆棧信息。
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.
ArrayListpids = new ArrayList();
pids.add(Process.myPid());
ActivityManagerService.dumpStackTraces(true, pids, null, null,
getInterestingNativePids());
waitedHalf = true;
}
continue;
}
此外,還有一個第三方庫,ANRWatchDog,也是用來檢測Anr的,其實原理更加簡單,
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;
}
}
}
它會在線程中,利用uiHanlder拋出一個計數器,然后wait指定時間,一旦等待時間到達,那么它會檢查計數的值是否發生改變,如果沒有發生改變,表示uiHandler的計算方法并沒有執行到。也就是出現了Anr,此時需要dump堆棧信息。
總結
以上是生活随笔為你收集整理的android blockcanary 原理,blockCanary原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 雷克萨斯rx+270胎压传感器怎么匹配?
- 下一篇: 50行车记录仪降压线怎么诱骗acc?