blockcanary是什么撩荣?
blockcanary是國(guó)內(nèi)開(kāi)發(fā)者MarkZhai開(kāi)發(fā)的一套性能監(jiān)控組件,它對(duì)主線程操作進(jìn)行了完全透明的監(jiān)控灸叼,并能輸出有效的信息汉额,幫助開(kāi)發(fā)分析、定位到問(wèn)題所在被冒,迅速優(yōu)化應(yīng)用
下圖為官方原理介紹示例圖:
簡(jiǎn)介
Github地址:blockcanary
特點(diǎn)
- 非侵入式
- 使用簡(jiǎn)單
- 實(shí)時(shí)監(jiān)控
- 提供完善的堆棧及內(nèi)存信息
Android渲染機(jī)制
Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào)军掂,觸發(fā)對(duì)UI進(jìn)行渲染, 如果每次渲染都成功昨悼,這樣就能夠達(dá)到流暢的畫(huà)面所需要的60fps蝗锥,為了能夠?qū)崿F(xiàn)60fps,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成率触。如果超過(guò)了16ms那么可能就出現(xiàn)丟幀的情況终议。
本文主要對(duì)blockcanary的原理進(jìn)行分析,關(guān)于渲染的詳細(xì)機(jī)制及優(yōu)化,推薦參考如下文章:
blockcanary怎么用穴张?
1细燎、gradle引入庫(kù)
debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0'
releaseImplementation 'com.github.markzhai:blockcanary-no-op:1.5.0'
2、自定義Application并且在onCreate中進(jìn)行初始化
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
BlockCanary.install(this, new BlockCanaryContext()).start();
}
}
blockcanary核心執(zhí)行流程是怎樣皂甘?
blockcanary的核心原理是通過(guò)自定義一個(gè)Printer玻驻,設(shè)置到主線程ActivityThread的MainLooper中。MainLooper在dispatch消息前后都會(huì)調(diào)用Printer進(jìn)行打印偿枕。從而獲取前后執(zhí)行的時(shí)間差值击狮,判斷是否超過(guò)設(shè)置的閾值。如果超過(guò)益老,則會(huì)將記錄的棧信息及cpu信息發(fā)通知到前臺(tái)彪蓬。
關(guān)鍵類功能說(shuō)明
類 | 說(shuō)明 |
---|---|
BlockCanary | 外觀類,提供初始化及開(kāi)始捺萌、停止監(jiān)聽(tīng) |
BlockCanaryContext | 配置上下文档冬,可配置id、當(dāng)前網(wǎng)絡(luò)信息桃纯、卡頓閾值酷誓、log保存路徑等 |
BlockCanaryInternals | blockcanary核心的調(diào)度類,內(nèi)部包含了monitor(設(shè)置到MainLooper的printer)态坦、stackSampler(棧信息處理器)盐数、cpuSampler(cpu信息處理器)、mInterceptorChain(注冊(cè)的攔截器)伞梯、以及onBlockEvent的回調(diào)及攔截器的分發(fā) |
LooperMonitor | 繼承了Printer接口玫氢,用于設(shè)置到MainLooper中。通過(guò)復(fù)寫(xiě)println的方法來(lái)獲取MainLooper的dispatch前后的執(zhí)行時(shí)間差谜诫,并控制stackSampler和cpuSampler的信息采集漾峡。 |
StackSampler | 用于獲取線程的棧信息,將采集的棧信息存儲(chǔ)到一個(gè)以key為時(shí)間戳的LinkHashMap中喻旷。通過(guò)mCurrentThread.getStackTrace()獲取當(dāng)前線程的StackTraceElement |
CpuSampler | 用于獲取cpu信息生逸,將采集的cpu信息存儲(chǔ)到一個(gè)以key為時(shí)間戳的LinkHashMap中。通過(guò)讀取/proc/stat文件獲取cpu的信息 |
DisplayService | 繼承了BlockInterceptor攔截器且预,onBlock回調(diào)會(huì)觸發(fā)發(fā)送前臺(tái)通知 |
DisplayActivity | 用于顯示記錄的異常信息Activity |
代碼執(zhí)行流程
leakcanary的核心流程主要包含3個(gè)步驟槽袄。
1、init-初始化
2锋谐、monitor-監(jiān)聽(tīng)MainLooper的dispatch時(shí)間差遍尺,推送前臺(tái)通知
3、dump-采集線程棧信息及cpu信息
這里先上一下整體的流程圖怀估,建議結(jié)合源碼進(jìn)行查看狮鸭。
下面我們通過(guò)上述3個(gè)步驟相關(guān)的源碼來(lái)進(jìn)行分析。
1多搀、init
根據(jù)Application中的使用歧蕉,我們首先看install方法
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
//BlockCanaryContext.init會(huì)將保存應(yīng)用的applicationContext和用戶設(shè)置的配置參數(shù)
BlockCanaryContext.init(context, blockCanaryContext);
//etEnabled將根據(jù)用戶的通知欄消息配置開(kāi)啟
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
return get();
}
接著看get方法的實(shí)現(xiàn)如下:
//使用單例創(chuàng)建了一個(gè)BlockCanary對(duì)象
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}
接著我們看BlockCanary的對(duì)象的構(gòu)造方法實(shí)現(xiàn)如下:
private BlockCanary() {
//初始化lockCanaryInternals調(diào)度類
BlockCanaryInternals.setContext(BlockCanaryContext.get());
mBlockCanaryCore = BlockCanaryInternals.getInstance();
//為BlockCanaryInternals添加攔截器(責(zé)任鏈)BlockCanaryContext對(duì)BlockInterceptor是空實(shí)現(xiàn)
mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
if (!BlockCanaryContext.get().displayNotification()) {
return;
}
//DisplayService只在開(kāi)啟通知欄消息的時(shí)候添加,當(dāng)卡頓發(fā)生時(shí)將通過(guò)DisplayService發(fā)起通知欄消息
mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
接著我們看BlockCanaryInternals的構(gòu)造方法康铭,實(shí)現(xiàn)如下:
public BlockCanaryInternals() {
//初始化棧采集器
stackSampler = new StackSampler(
Looper.getMainLooper().getThread(),
sContext.provideDumpInterval());
//初始化cpu采集器
cpuSampler = new CpuSampler(sContext.provideDumpInterval());
//初始化LooperMonitor惯退,并實(shí)現(xiàn)了onBlockEvent的回調(diào),該回調(diào)會(huì)在觸發(fā)閾值后被調(diào)用
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
LogWriter.save(blockInfo.toString());
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
LogWriter.cleanObsolete();
}
2从藤、monitor
首先我們先看下系統(tǒng)的Looper的loop()方法中對(duì)于printer的使用催跪,如下:
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// 執(zhí)行dispatchMessage前,執(zhí)行Printer的println方法
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
// 執(zhí)行dispatchMessage后夷野,執(zhí)行Printer的println方法
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
當(dāng)install進(jìn)行初始化完成后懊蒸,接著會(huì)調(diào)用start()方法,實(shí)現(xiàn)如下:
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
//把mBlockCanaryCore中的monitor設(shè)置MainLooper中進(jìn)行監(jiān)聽(tīng)
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
當(dāng)MainLooper執(zhí)行dispatch的前后會(huì)調(diào)用printer的println方法悯搔,所以這里我們看LooperMonitor對(duì)println方法的實(shí)現(xiàn)如下:
@Override
public void println(String x) {
//如果再debug模式骑丸,不執(zhí)行監(jiān)聽(tīng)
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {//dispatchMesage前執(zhí)行的println
//記錄開(kāi)始時(shí)間
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
//開(kāi)始采集棧及cpu信息
startDump();
} else {//dispatchMesage后執(zhí)行的println
//獲取結(jié)束時(shí)間
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
//判斷耗時(shí)是否超過(guò)閾值
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
//判斷是否超過(guò)閾值
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
//回調(diào)監(jiān)聽(tīng)
private void notifyBlockEvent(final long endTime) {
final long startTime = mStartTimestamp;
final long startThreadTime = mStartThreadTimestamp;
final long endThreadTime = SystemClock.currentThreadTimeMillis();
HandlerThreadFactory.getWriteLogThreadHandler().post(new Runnable() {
@Override
public void run() {
mBlockListener.onBlockEvent(startTime, endTime, startThreadTime, endThreadTime);
}
});
}
當(dāng)發(fā)現(xiàn)時(shí)間差超過(guò)閾值后,會(huì)回調(diào)onBlockEvent妒貌。具體的實(shí)現(xiàn)在BlockCanaryInternals的構(gòu)造方法中通危,如下:
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
//根據(jù)開(kāi)始及結(jié)束時(shí)間,從棧的map當(dāng)中獲取記錄信息
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
//構(gòu)建 BlockInfo對(duì)象灌曙,設(shè)置相關(guān)的信息
BlockInfo blockInfo = BlockInfo.newInstance()
.setMainThreadTimeCost(realTimeStart, realTimeEnd, threadTimeStart, threadTimeEnd)
.setCpuBusyFlag(cpuSampler.isCpuBusy(realTimeStart, realTimeEnd))
.setRecentCpuRate(cpuSampler.getCpuRateInfo())
.setThreadStackEntries(threadStackEntries)
.flushString();
//記錄信息
LogWriter.save(blockInfo.toString());
//遍歷攔截器菊碟,通知
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
最后我們看攔截器的實(shí)現(xiàn)DisplayService,會(huì)發(fā)送前臺(tái)的通知在刺,代碼如下:
@Override
public void onBlock(Context context, BlockInfo blockInfo) {
Intent intent = new Intent(context, DisplayActivity.class);
intent.putExtra("show_latest", blockInfo.timeStart);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 1, intent, FLAG_UPDATE_CURRENT);
String contentTitle = context.getString(R.string.block_canary_class_has_blocked, blockInfo.timeStart);
String contentText = context.getString(R.string.block_canary_notification_message);
show(context, contentTitle, contentText, pendingIntent);
}
3逆害、dump
從上面的流程我們可以知道,當(dāng)dispatchMessage前的println觸發(fā)時(shí)蚣驼,會(huì)執(zhí)行dump的start方法忍燥,當(dāng)dispatchMessage后的println觸發(fā)時(shí),會(huì)執(zhí)行dump的stop方法隙姿。
private void startDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
BlockCanaryInternals.getInstance().stackSampler.start();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
BlockCanaryInternals.getInstance().cpuSampler.start();
}
}
private void stopDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
BlockCanaryInternals.getInstance().stackSampler.stop();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
BlockCanaryInternals.getInstance().cpuSampler.stop();
}
}
下面我們分Stacksampler和CpuSampler進(jìn)行介紹梅垄。
1、Stacksampler
start()的執(zhí)行流程如下:
public void start() {
if (mShouldSample.get()) {
return;
}
mShouldSample.set(true);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
//通過(guò)一個(gè)HandlerThread延時(shí)執(zhí)行了mRunnable
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
//mRunnable在基類AbstractSampler中定義
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
//抽象方法
doSample();
//繼續(xù)執(zhí)行采集
if (mShouldSample.get()) {
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
//Stacksampler的doSample()實(shí)現(xiàn)
@Override
protected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
//通過(guò)mCurrentThread.getStackTrace()獲取StackTraceElement输玷,加入到StringBuilder
for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append(BlockInfo.SEPARATOR);
}
synchronized (sStackMap) {
//Lru算法队丝,控制LinkHashMap的長(zhǎng)度
if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
sStackMap.remove(sStackMap.keySet().iterator().next());
}
//加入到map中
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
stop()的執(zhí)行流程如下:
public void stop() {
if (!mShouldSample.get()) {
return;
}
//設(shè)置控制變量
mShouldSample.set(false);
//取消handler消息
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
2、CpuSampler
其他執(zhí)行流程均與StackSampler一致欲鹏,這里主要分析doSample的實(shí)現(xiàn)机久,如下:
//主要通過(guò)獲取/proc/stat文件 去獲取cpu的信息
protected void doSample() {
BufferedReader cpuReader = null;
BufferedReader pidReader = null;
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);
} catch (Throwable throwable) {
Log.e(TAG, "doSample: ", throwable);
} finally {
try {
if (cpuReader != null) {
cpuReader.close();
}
if (pidReader != null) {
pidReader.close();
}
} catch (IOException exception) {
Log.e(TAG, "doSample: ", exception);
}
}
}
blockcanary是如何進(jìn)行卡頓的判定?
blockcanary的核心原理是通過(guò)自定義一個(gè)Printer赔嚎,設(shè)置到主線程ActivityThread的MainLooper中膘盖。MainLooper在dispatch消息前后都會(huì)調(diào)用Printer進(jìn)行打印胧弛。從而獲取前后執(zhí)行的時(shí)間差值,判斷是否超過(guò)設(shè)置的閾值侠畔。如果超過(guò)结缚,則判定為卡頓。
leakcanary是如何獲取線程的堆棧信息软棺?
通過(guò)mCurrentThread.getStackTrace()方法红竭,遍歷獲取StackTraceElement,轉(zhuǎn)化為一個(gè)StringBuilder的value喘落,并存儲(chǔ)到一個(gè)key為時(shí)間戳的LinkHashMap中茵宪。
leakcanary是如何獲取cpu的信息?
通過(guò)讀取/proc/stat文件瘦棋,獲取所有CPU活動(dòng)的信息來(lái)計(jì)算CPU使用率稀火。解析出信息后,轉(zhuǎn)化為一個(gè)StringBuilder的value赌朋,并存儲(chǔ)到一個(gè)key為時(shí)間戳的LinkHashMap中憾股。
總結(jié)
思考
blockcanary充分的利用了Loop的機(jī)制,在MainLooper的loop方法中執(zhí)行dispatchMessage前后都會(huì)執(zhí)行printer的println進(jìn)行輸出箕慧,并且提供了方法設(shè)置printer服球。通過(guò)分析前后打印的時(shí)差與閾值進(jìn)行比對(duì),從而判定是否卡頓颠焦。
參考資料
Android UI卡頓監(jiān)測(cè)框架BlockCanary原理分析
關(guān)于
歡迎關(guān)注我的個(gè)人公眾號(hào)
微信搜索:一碼一浮生斩熊,或者搜索公眾號(hào)ID:life2code