1.使用
debug模式依赖blockcanary
debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0'
安装并启动BlockCanary
BlockCanary.install(this, AppBlockCanaryContext()).start()
AppBlockCanaryContext
public class AppBlockCanaryContext extends BlockCanaryContext {
/..省略大量代码../
//检测超过500毫秒的方法
public int provideBlockThreshold() {
return 500;
}
//卡顿发生自定义操作, blockInfo为卡顿信息
public void onBlock(Context context, BlockInfo blockInfo) {
}
}
配置读写权限 ,不然卡顿检测文件无法读写
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2.源码分析
2.1 install 方法分析
BlockCanary#install 方法 , 先初始化BlockCanaryContext,然后设置是否显示通知
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
BlockCanaryContext.init(context, blockCanaryContext);
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
return get();
}
get方法
双重校验锁单例模式
public static BlockCanary get() {
if (sInstance == null) {
synchronized (BlockCanary.class) {
if (sInstance == null) {
sInstance = new BlockCanary();
}
}
}
return sInstance;
}
BlockCanary私有构造方法中 ,添加卡顿发生之后的相关操作处理
private BlockCanary() {
BlockCanaryInternals.setContext(BlockCanaryContext.get());
mBlockCanaryCore = BlockCanaryInternals.getInstance();
//添加用于发生卡顿之后的拦截器
mBlockCanaryCore.addBlockInterceptor(BlockCanaryContext.get());
if (!BlockCanaryContext.get().displayNotification()) {
return;
}
//用于发生卡顿之后卡顿信息的显示
mBlockCanaryCore.addBlockInterceptor(new DisplayService());
}
public BlockCanaryInternals() {
//栈采样
stackSampler = new StackSampler(
Looper.getMainLooper().getThread(),
sContext.provideDumpInterval());
//cpu采样
cpuSampler = new CpuSampler(sContext.provideDumpInterval());
//LooperMonitor继承自Printer接口
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
//卡顿发生后要做的操作
@Override
public void onBlockEvent(long realTimeStart, long realTimeEnd,
long threadTimeStart, long threadTimeEnd) {
// Get recent thread-stack entries and cpu usage
ArrayList<String> threadStackEntries = stackSampler
.getThreadStackEntries(realTimeStart, realTimeEnd);
if (!threadStackEntries.isEmpty()) {
if (mInterceptorChain.size() != 0) {
for (BlockInterceptor interceptor : mInterceptorChain) {
interceptor.onBlock(getContext().provideContext(), blockInfo);
}
}
}
}
}, getContext().provideBlockThreshold(), getContext().stopWhenDebugging()));
}
方法调用栈采样
方法调用栈采样通过 HandlerThread 在后台线程进行采样 ,获取发生卡顿时间的栈调用信息
//com.github.moduth.blockcanary.StackSampler
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
//进行采样
doSample();
if (mShouldSample.get()) {
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
//发送handler消息 ,用于获取当前线程堆栈信息
public void start() {
if (mShouldSample.get()) {
return;
}
mShouldSample.set(true);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
//移除消息
public void stop() {
if (!mShouldSample.get()) {
return;
}
mShouldSample.set(false);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
StackSampler#doSample 方法中 , 获取方法调用栈信息,并按照当前时间保存在map中
for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
stringBuilder
.append(stackTraceElement.toString())
.append(BlockInfo.SEPARATOR);
}
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
LooperMonitor 继承自Printer , println 方法中统计Handler#handleMessage方法执行的时间差,如果超过配置的阀值则发生卡顿 , 显示 调用栈cpu等信息
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
if (!mPrintingStarted) {
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
//开始调用栈 cpu 采样
startDump();
} else {
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
//停止调用栈 cpu 采样
stopDump();
}
}
卡顿信息展示
<activity
android:name="com.github.moduth.blockcanary.ui.DisplayActivity"
android:enabled="false"
android:icon="@drawable/block_canary_icon"
android:label="@string/block_canary_display_activity_label"
//配置单独任务栈
android:taskAffinity="com.github.moduth.blockcanary"
android:theme="@style/block_canary_BlockCanary.Base" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
DisplayService#onBlock 中显示通知,然后点击通知启动DisplayActivity
public void onBlock(Context context, BlockInfo blockInfo) {
//启动DisplayActivity
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);
}
DisplayActivity#updateUi 拿到卡顿信息 BlockInfo 然后渲染界面
private void updateUi() {
final BlockInfoEx blockInfo = getBlock(mBlockStartTime);
if (blockInfo == null) {
mBlockStartTime = null;
}
// Reset to defaults
mListView.setVisibility(VISIBLE);
mFailureView.setVisibility(GONE);
if (blockInfo != null) {
//渲染界面
renderBlockDetail(blockInfo);
} else {
renderBlockList();
}
}
2.2 start 方法分析
给Looper设置Printer
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
Looper#setMessageLogging 方法中给mLogging赋值
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
Looper#loop方法在handler handleMessage 方法执行前后打印
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
//此处logging为start方法中设置的
final Printer logging = me.mLogging;
//handler的handleMessage方法执行前打印
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
//执行handler的handleMessage方法
msg.target.dispatchMessage(msg);
}
//handler的handleMessage方法执行后打印 , 统计handleMessage执行时间
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
3.总结
大致原理就是调用Looper#setMessageLogging 统计每个message执行的时间 ,判断方法执行时间是否超过设置的阀值,使用Thread.currentThread().getStackTrace() 获取当前线程的发生卡顿时的堆栈信息 ,然后进行界面的渲染