// 这样Handler发送的消息最终又交回到它的dispatchMessage方法来处理。不同的是,Handler的dispatchMessage
// 方法是在创建Handler时所使用的Looper中执行的,这样就成功将代码逻辑切换到指定线程中去执行了。
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
//消息分发完成后,调用用户自己设置的Printer.println()方法,此时获取消息分发之后时间T2;
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
/**
-
Control logging of messages as they are processed by this Looper. If
-
enabled, a log message will be written to printer
-
at the beginning and ending of each message dispatch, identifying the
-
target Handler and message contents.
-
@param printer A Printer object that will receive log messages, or
-
null to disable message logging.
-
用户可以设置自己的Printer,这样在知道消息分发前后的时间,
-
通过前后的时差与阈值进行对比,从而确定是否发生了卡顿
*/
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
通过设置Printer我们可以检测msg.target.dispatchMessage(msg)执行时间,这样就可以知道部分UI线程是否有耗时操作了。
BlockCanary的LooperMonitor的println方法如下:
LooperMonitor
@Override
public void println(String x) {
if (!mPrintingStarted) {
//dispatchMesage前执行的println
//记录开始时间
mStartTimestamp = System.currentTimeMillis();
mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
mPrintingStarted = true;
//开始采集栈及cpu信息,最终会调用Stacksampler.start()方法;
startDump();
} else {
//dispatchMesage后执行的println
//获取结束时间
final long endTime = System.currentTimeMillis();
mPrintingStarted = false;
//判断耗时是否超过阈值
if (isBlock(endTime)) {
notifyBlockEvent(endTime);
}
//最终会调用Stacksampler.stop()方法;
stopDump();
}
}
//判断是否超过阈值
private boolean isBlock(long endTime) {
return endTime - mStartTimestamp > mBlockThresholdMillis;
}
//回调监听
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);
}
});
}
其中在startDump方法最终会调用Stacksampler.start()方法;stopDump最终会调用Stacksampler.stop()方法;相关方法如下:
Stacksampler
public void start() {
//在mRunable进行信息采集;
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
//通过一个HandlerThread延时执行了mRunnable
HandlerThreadFactory.getTimerThreadHandler().postDelayed(mRunnable,
BlockCanaryInternals.getInstance().getSampleDelay());
}
public void stop() {
//取消handler消息,如果未超时就不会采集相关信息
HandlerThreadFactory.getTimerThreadHandler().removeCallbacks(mRunnable);
}
在开始进行msg.target.dispatchMessage(msg)消息分发前通过HandlerThread发送一个延时runable,在msg.target.dispatchMessage(msg)消息分发后会remove该runable,如果指定的时间消息分发没有完成,说明应用发生了卡顿,这之后开始执行mRunable,在mRunable进行相关信息采集及提示APP发生卡顿;以上就是BlockCanary监测卡顿的核心原理;
利用Choreographer监测APP卡顿
Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染。开发者可以使用Choreographer#postFrameCallback设置自己的callback与Choreographer交互,你设置的FrameCallCack(doFrame方法)会在下一个frame被渲染时触发。理论上来说两次回调的时间周期应该在16ms,如果超过了16ms我们则认为发生了卡顿,我们主要就是利用两次回调间的时间周期来判断,
Choreographer.getInstance()
.postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long l) {
//移除消息
Handler.removeMessage();
//发送延时消息
Hnadler.sendMessageAtTime(…)
Choreographer.getInstance().postFrameCallback(this);
}
});
发送的延时消息在执行的时间没有被remove掉,说明发生了卡顿,这时候可以进行卡顿相关信息的采集,如果在渲染下一帧的时候该消息还没有被处理,这时候将该消息remove掉,此场景说明未发生卡顿;该检测卡顿的思想和BlockCanary类似;
最后,我们可以结合上述原理以及自己需求开发出一个适合自己的卡顿监测方案,也可以参考已有开源方案。
其它
为什么主线程Looper.loop进行消息分发耗时就代表APP卡顿?
答:为了保证应用的平滑性,每一帧渲染时间不能超过16ms,达到60帧每秒;如果UI渲染慢的话,就会发生丢帧,这样用户就会感觉到不连贯性,我们称之为Jank(APP卡顿);VSync信号由SurfaceFlinger实现并定时发送(每16ms发送),Choreographer.FrameDisplayEventReceiver收到信号后,调用onVsync方法组织消息发送到主线程处理。Choreographer主要功能是当收到VSync信号时,去调用使用通过postCallBack设置的回调函数,在postCallBack调用doFrame,在doFrame中渲染下一帧;FrameDisplayEventReceiver相关代码如下:
Choreographer.java
/**
-
FrameDisplayEventReceiver继承自DisplayEventReceiver接收底层的VSync信号开始处理UI过程。
-
VSync信号由SurfaceFlinger实现并定时发送。FrameDisplayEventReceiver收到信号后,
-
调用onVsync方法组织消息发送到主线程处理。这个消息主要内容就是run方法里面的doFrame了,
-
这里mTimestampNanos是信号到来的时间参数。
*/
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
最后
其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
上面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
【Android学习PDF+学习视频+面试文档+知识点笔记】
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
【Android高级架构视频学习资源】
*,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。
【Android学习PDF+学习视频+面试文档+知识点笔记】
【Android思维脑图(技能树)】
知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。
[外链图片转存中…(img-HmUY3quE-1711168830996)]
【Android高级架构视频学习资源】
**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!