帧率卡顿ANR

文章结构
一、性能指标
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);//直接开始绘制一帧数据

  1. 计算收到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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值