BlockCanary是什么
非侵入式的性能监控组件
BlockCanary的作用
发现UI卡顿问题(平时ANR时候我们才能获取到当堆栈的信息)
UI卡顿原理
1.60fps---->16m/s帧
2.尽量保证每次在16m/s内处理完所有的CPU与GPU计算绘制,渲染等操作,否则就会造成丢帧卡顿问题
主线程的作用
1.主线程的作用(应用启动后,创建一个主线程,就是ActivityThread)
2.把事件(点击,触摸等)发给合适的View或者Widget
3.同时也是应用与应用UI交互的主线程
耗时操作常见的在子线程处理的方法,(耗时操作不能在主线程处理)
Handler
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable,long)
UI卡顿的常见原因
1.耗时操作
2.布局Layout过于复杂,无法在16m/s内完成渲染
3.View的过度绘制
4.View频繁的触发measure,layout
5.内存频繁触发GC过多(虚拟机在执行垃圾回收的时候,所有的线程,包括UI线程都会暂停,只有当GC垃圾回收完后,所有的线程才能继续执行工作)
先看下[ActivityThread](Android应用程序只有一个主线程,就是ActivityThread)
看Android源码网站地址
//创建一个Looper,Looper里又会关联消息队列MessageQueue
final Looper mLooper = Looper.myLooper();
public static void main(String[] args) {
.
.
.
//一个主线程创建MainLooper后,就会在我们的应用生命周期内,不断轮询,通过Looper.loop()方法,获取到消息队列中的Message,然后通知我们的主线程,去更新UI
Looper.prepareMainLooper();
}
看下prepareMainLooper()方法
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//创建一个主线程的Looper(不管整个应用的主线程,它有多少个子线程,主线程中只有这一个Looper,不管有多少Hanlder,在其它地方可以创建Handler,最终会会到这个sMainLooper 上)
sMainLooper = myLooper();
}
}
//在msg.target.dispatchMessage(msg)方法上下方都有log输出,在dispatchMessage上下分别打印方法执行的时间,根据时间差,来判定dispatchMessage中有没有耗时操作,也就是dispatchMessage中,有没有UI卡顿,如果超过时间差的阈值,就要打印出信息,BlockCanary就是利了这个特点,这也是它的核心实现原理
public static void loop() {
final Printer logging = me.mLogging;
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
//分发message,target就是Handler
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);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); .
}
看下msg.target.dispatchMessage(msg)方法
//这都是执行在主线程中,如果产生了UI卡顿,必定是在这个方法当中
public void dispatchMessage(Message msg) {
//callback就是runnable
if (msg.callback != null) {
//handleCallback,就是把runnable放到方法中,调用它的run方法执行我们的子线程
handleCallback(msg);
} else {
//相当于调用hanlder.sendMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//处理消息
handleMessage(msg);
}
}
看下BlockCanary.install方法
public static BlockCanary install(Context context, BlockCanaryContext blockCanaryContext) {
//init方法就做了BlockCanaryContext的成员变量的赋值
BlockCanaryContext.init(context, blockCanaryContext);
//设置通知栏消息是否开启或关闭,BlockCanaryContext.get().displayNotification(),debug模式默开启(true),release默认关闭(false)
setEnabled(context, DisplayActivity.class, BlockCanaryContext.get().displayNotification());
return get();
}
看下get()方法
public static BlockCanary get() {
//单例,生成BlockCanary的实例
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());
}
看下BlockCanaryInternals的构造方法
public BlockCanaryInternals() {
//dump出线程的stack信息,传入的参数是主线程,第二个参数是Dump的间隔
stackSampler = new StackSampler(
Looper.getMainLooper().getThread(),
sContext.provideDumpInterval());
//dump出cpu的有关情况
cpuSampler = new CpuSampler(sContext.provideDumpInterval());
//LooperMonitor是在dispatchMessage上下分别打印方法执行的时间
setMonitor(new LooperMonitor(new LooperMonitor.BlockListener() {
//主线程的调用栈,CPU的是使用情况,内存使用情况都会这个方法监听到,会回调
@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()) {
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()));
//就是用来删除日志用的,因为在BlockCanary当中,默认情况下日志会保存两天,需要定期的删除
LogWriter.cleanObsolete();
}
看下start() 方法
public void start() {
if (!mMonitorStarted) {
mMonitorStarted = true;
// Looper.getMainLooper()得到主线程Looper,调用setMessageLogging打印时间 Looper.getMainLooper().setMessageLogging(mBlockCanaryCore.monitor);
}
}
看下LooperMonitor的println(String x)方法
//打印时间
public void println(String x) {
if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
return;
}
//表示是否在dispatchMessage方法前
if (!mPrintingStarted) {
//系统时间
mStartTimestamp = System.currentTimeMillis();
//当前线程运行状态的总时间(sleep和wait不会记录到时间里去)
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
startDump();
} else { //表示是否在dispatchMessage方法后
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
//表示有卡顿,UI有阻塞
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
stopDump();
}
}
看下startDump()方法
private void startDump() {
if (null != BlockCanaryInternals.getInstance().stackSampler) {
BlockCanaryInternals.getInstance().stackSampler.start();
}
if (null != BlockCanaryInternals.getInstance().cpuSampler) {
BlockCanaryInternals.getInstance().cpuSampler.start();
}
}
看下BlockCanaryInternals.getInstance().stackSampler.start()方法
public void start() {
if (mShouldSample.get()) {
return;
}
mShouldSample.set(true);
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
看下mRunnable,在aabstract class AbstractSampler 里,AbstractSampler 是stackSampler和cpuSampler的抽象类
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
doSample();
if (mShouldSample.get()) {
HandlerThreadFactory.getTimerThreadHandler()
.postDelayed(mRunnable, mSampleInterval);
}
}
};
看下 doSample()抽象方法StackSampler的实现
p
//打印栈的信息
rotected void doSample() {
StringBuilder stringBuilder = new StringBuilder();
//mCurrentThread.getStackTrace()获取到当前线程的调用栈,mCurrentThread是主线程,因为前面调用的是getMainLooper
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());
}
//以当前时间为Key,放入到HashMap中,sStackMap是LinkedHashMap,这里为什么要定义为LinkedHashMap,LinkedHashMap会记录插入的键的顺序,所以遍历的时候是按插入顺序的,普通的HashMap无法做到,所以遍历顺序未知
sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
}
}
看下 doSample()抽象方法CpuSampler的实现
@Override
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 = "";
}
.
.
.
} } catch (Throwable throwable) {}
}
看下isBlock(endTime)方法
private boolean isBlock(long endTime) {
//endTime是dispatchMessage后的时间,mStartTimestamp 是dispatchMessage前的时间,mBlockThresholdMillis时间为3000,如果大于就表示产生卡顿了,UI阻塞了
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
看下 notifyBlockEvent(endTime)方法
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);
}
});
}