文章结构
一、性能指标
1、帧率
2、ANR
3、屏幕卡顿
二、深入原理—监控实现手段、问题发生原因、解决优化方案
三、优化方案
UI卡顿优化方案:
- 复杂数据处理放子线程,UI刷新放主线程
- 降低UI层级,减少不必要的层级,去除不必要的背景色,使用include,merge,ViewStub标签,多使用relativelayout、ConstraintLayout
- 绘制优化,不要在onDraw执行大量操作,或者创建对象
- 内存抖动的问题,fragment回收,大图等,
- 减少requestlayout次数
ANR
ANR发生
5秒内无法响应屏幕触摸事件或键盘输出
广播onReceive()函数时10秒没有处理完成,后台为20秒
前台服务20秒内,后台服务在200秒内没有执行完成
contentProvider的publish在10秒内没进行完
造成ANR的主要原因
主线程被IO操作阻塞
主线程中存在耗时的计算
避免ANR
尽量避免在主线程中做耗时操作
子线程处理耗时操作,hander切换到主线程刷新UI
如何分析ANR
https://blog.csdn.net/feelinghappy/article/details/99847856
在data/anr目录生成一个文件traces.txt
布局优化,减少不必要的嵌套
多使用relativelayout/ConstrainLayout
善用textview的Drawable减少布局层级
Background少用
TraceView
绘制优化:onDraw方法避免执行大量操作
ANR系统原理
AMS.appNotResponding流程
ANR深入分析
http://mp.weixin.qq.com/s?__biz=MzIwMTAzMTMxMg==&mid=2649493643&idx=1&sn=34b51d1f61bd2ecaa8fd0a2d39c4d1d1&chksm=8eec9b74b99b126246acc4547597dfe55c836b8f689b2d1a65bdf1ee2054ced2fc070bfa2678&mpshare=1&scene=24&srcid=0116vzNfMMv2dLizhAT8mEYq#rd
BlockCanary
ConstraintLayout
https://www.jianshu.com/p/17ec9bd6ca8a
渲染优化
降低UI层级
数据异步处理,和UI刷新分离
LinearLayout RelativeLayout相同层级效率比较
https://www.cnblogs.com/zhaojianhua/p/8574136.html
屏幕卡顿
ANR解决
1、日志获取
2、查看时间点,查看是IO高还是CPU占用高
3、分析日志堆栈
https://www.jianshu.com/p/3959a601cea6
ANR的系统实现原理,在system server—AMS—AppErrors-handleShowAnrUi
https://www.cnblogs.com/android-blogs/p/5718302.html
堆栈信息不是出问题的点,怎么办
Hashset 间隔一定时间打印堆栈信息,比方说6次
原子性
Anr 系统是怎么实现的?
帧率计算原理
分析doraemonkit帧率计算原理
最近看到好车主有用到性能监控工具doraemonkit来监控app的性能,其中就有监控app屏幕帧率的功能,不知道具体怎么实现的?就去学习了一下帧率计算的原理。
doraemonkit是滴滴开源的库,从github下载源码。
首先打开监控帧率的开关
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)//android4.1以上
public void startMonitorFrameInfo() {
//开启定时任务
mMainHandler.postDelayed(mRateRunnable, DateUtils.SECOND_IN_MILLIS);
Choreographer.getInstance().postFrameCallback(mRateRunnable);
}
1、开启了一个定时任务,间隔1s去获取这秒内,屏幕绘制的总帧数
2、将FrameRateRunnable 添加到mCallbackQueues中Choreographer.CALLBACK_ANIMATION的队列中//FrameRateRunnable implements Runnable, Choreographer.FrameCallback
Choreographer
postFrameCallback(FrameCallback callback)
postFrameCallbackDelayed(FrameCallback callback, long delayMillis)
postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis)
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
//将FrameRateRunnable添加到queue中
//delayMillis最后都走到这个方法
scheduleFrameLocked(now){
//在doFrame方法里mFrameScheduled = false;
if (!mFrameScheduled) {//是否正在绘制一帧,标志位,避免不必要的多次调用,
if (USE_VSYNC) {//收到垂直同步信号调度下一帧
//见下面具体逻辑线1
} else {
//见逻辑线2
}
}
}
逻辑线1
scheduleVsyncLocked()
FrameDisplayEventReceiver.scheduleVsync()
//应用必须向底层请求vsync信号,然后下一次vsync信号来的时候会通过JNI通知到应用
nativeScheduleVsync(long receiverPtr)//请求vsync信号
//vsync来的时候底层会通过JNI回调这个方法
private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
onVsync(timestampNanos, builtInDisplayId, frame);
}
//timestampNanos表示应该开始准备绘制一帧数据。如果手机出现卡顿,onVsync函数会在timestampNanos+delayTime才会接受到VSync信号
onVsync(long timestampNanos, int builtInDisplayId, int frame)
通过Handler,往消息队列插入一个异步消息,指定执行的时间,然后看注释1,callback传this,所以最终会回调run方法
onVsync.run方法里面调用doFrame就和逻辑线2一样了
逻辑线2
doFrame(System.nanoTime(), 0);//直接开始绘制一帧数据
- 计算收到vsync信号到doFrame被调用的时间差,vsync信号间隔是16毫秒一次,大于16毫秒就是掉帧了,如果超过30帧(默认30),
就打印log提示开发者检查主线程是否有耗时操作
2.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(now / TimeUtils.NANOS_PER_MS);
遍历callbacks,CallbackRecord c.run(frameTimeNanos);
((FrameCallback)action).doFrame(frameTimeNanos);//绘制一帧,FrameRateRunnable.doFrame回调一次,totalFramesPerSecond++
这样就统计到了屏幕1s画了多少帧。
FrameRateRunnable implements Runnable, Choreographer.FrameCallback
run方法:
mLastFrameRate = totalFramesPerSecond;//1s内统计的总帧数
totalFramesPerSecond = 0;
mMainHandler.postDelayed(this, DateUtils.SECOND_IN_MILLIS);//开启下一秒的定时任务
doFrame(long frameTimeNanos)方法 //每回调一次,就是绘制了一帧
totalFramesPerSecond++;
Choreographer.getInstance().postFrameCallback(this);//将FrameRateRunnable继续添加到mCallbackQueues中Choreographer.CALLBACK_CALLBACK_ANIMATION的队列中
备注:
Frame time appears to be going backwards. May be due to a previously skipped frame. Waiting for next vsync
帧时间似乎在倒退。可能是由于之前跳过的帧。等待下一个vsync
If running on the Looper thread, then schedule the vsync immediately,otherwise post a message to schedule the vsync from the UI thread as soon as possible.
如果在Looper线程上运行,则立即调度vsync,否则将发送消息以尽快从UI线程调度vsync。
Schedules a single vertical sync pulse to be delivered when the next display frame begins.
安排下一个显示帧开始时要传送的单个垂直同步脉冲
Attempted to schedule a vertical sync pulse but the display event receiver has already been disposed.
试图调度垂直同步脉冲,但显示事件接收器已被释放。
System.nanoTime()的返回值只和进程已运行的时间有关, 不受调系统时间影响
java中System.nanoTime()返回的是纳秒,nanoTime而返回的可能是任意时间,甚至可能是负数
java中System.currentTimeMillis()返回的毫秒,这个毫秒其实就是自1970年1月1日0时起的毫秒数
BlockCanary原理分析
参考博客
https://blog.csdn.net/u013493809/article/details/62215250
https://juejin.im/post/5ae98d33518825670b33e5e6
https://juejin.im/post/5d837cd1e51d4561cb5ddf66
//反射修改掉帧默认个数30,输出log
https://www.dazhuanlan.com/2019/10/28/5db5c5a826049/
VSync信号的分发过程
https://blog.csdn.net/kc58236582/article/details/52763534
我们回顾下整个流程,VSync信号从底层产生后,经过几个函数,保存到了SurfaceFlinger的mPrimaryDispSync变量(DisySync类)的数组中,这样设计的目的让底层的调用尽快结束,否则会耽搁下次VSync信号的发送。然后在mPrimaryDispSync变量关联的线程开始分发数组中的VSync信号,分发的过程也调用了几个回调函数,最终结果是放在EventThread对象的数组中。EventThread是转发VSync信号的中心。不但会把VSync信号发给SurfaceFlinger,还会把信号发送到用户进程中去。EventThread的工作比较重,因此SurfaceFlinger中使用了两个EventThread对象来转发VSync信号。确保能及时转发。SurfaceFlinger中的MessageQueue收到Event后,会将Event转化成消息发送,这样最终就能在主线程调用SurfaceFlinger的函数处理VSync信号了。
DisplayEventReceiver分析
https://blog.csdn.net/kc58236582/article/details/52892384
https://blog.csdn.net/houliang120/article/details/50958212
可见设计的时候就专门用这个SurfaceFlinger.mEventThread线程来接收来自应用进程的同步信号请求,每来一个应用进程同步信号请求,就通过SurfaceFlinger.mEventThread创建一个EventThread::Connection对象,并通过EventThread.registerDisplayEventConnection函数将创建的EventThread::Connection对象保存到EventThread.mDisplayEventConnections里面
同步信号是周期性的,那么应用请求同步信号是只请求一次呢?还是多次?
EventThread::Connection::Connection(const sp& eventThread)
: count(-1), mEventThread(eventThread), mChannel(new BitTube()){
}
// count >= 1 : continuous event. count is the vsync rate
// count == 0 : one-shot event that has not fired
// count ==-1 : one-shot event that fired this round / disabled
很清楚的说明了,count = 0说明当前的垂直同步信号请求是一个一次性的请求,并且还没有被处理
一个应用进程需要多次请求Vsync同步信号会不会使用同样的一串对象?
https://www.jianshu.com/p/1ebd152273e4
MainThread的Lopper发起,MainThread的Handler处理Callback,然后调用到Choreographer,执行一系列的doFrame -> doCallbacks -> doTraversal -> performTraversals -> measureHierarchy
https://www.jianshu.com/p/205edf062ae9
Vsync信号的产生:硬件源HWComposer,它属于HAL,硬件抽象层的类,主要就是把硬件的Vsync信号传递到上层来
blockcanary
https://juejin.im/post/5de336d96fb9a0716d5c0814
BlockMonitorFragment
start(Context context)
mMonitorCore = new MonitorCore();
// 卡顿检测和跳转耗时统计都使用了Printer的方式,无法同时工作
public void setMessageLogging(@Nullable Printer printer) //原理
public void println(String x) //原理
dumpInfo();//把堆栈信息写入文件中
//draw绘制过程
https://blog.csdn.net/kc58236582/article/details/52879698
//测量(Measure)、布局(Layout)和绘制(Draw)
https://blog.csdn.net/kc58236582/article/details/52437855
//Vsync信号如何到用户进程
https://blog.csdn.net/kc58236582/article/details/52892384
include/merge/ViewStub
http://www.trinea.cn/android/layout-performance/
Anr分析
https://blog.csdn.net/qq_31939617/article/details/79756718