性能优化之UI卡顿排查与解决(帧率查看、X2C)

名词解释:

显卡
如: 桌面端使用的NVIDA AMD
独立显卡: 桌面端Windows macOS才有 好处是独立出主板,更好散热,更好替换,最主要的是有自己的显存,而不是使用系统内存
而Android这种就没有独立显卡,使用的是显卡驱动,占据的也是系统内存

显存(即显卡内存 显卡的一个参数) 也叫帧缓存 用于缓存画面

FPS(frame per second) 帧率 24是能看到动画 30更流畅 60以上是能很流畅的看画面没有任何卡顿感 (即不会掉帧)

丢帧(SF: Skipped Frame) 不是丢包 而是在16ms完成工作却因各种原因没做完,占了下n个16ms的时间,相当于丢了n帧

流畅度(SM: SMoothness) 和丢帧相对,在VSync机制中1s内Loop运行的次数。

从代码到画面渲染到显示器上:
OpenGL-ES
CPU 计算(负责把UI组件计算成Polygons(多边形),Texture(纹理))->
GPU 渲染(交给GPU进行栅格化渲染(即对每个正方形像素点安排渲染 因此像素点越高 细节越凸显),这里涉及到矢量图形,位图的转换等等 )->
显存 帧缓存 ->
视频控制器 数模转换 视频控制器会按照HSync信号逐行读取帧缓冲区的数据->
显示器 进行画面的显示渲染

画面撕裂现象
显示器还没读取完一帧的画面,GPU就已经完成了下一帧的渲染,然后放到了显存中,替换了前一帧,此时视频控制器就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象
解决:
在GPU中引入一个VSync垂直同步,GPU 会等待显示器的 VSync 信号发出后,才进行新的一帧渲染和缓冲区更新
引入的问题:
1 若CPU或者GPU计算渲染较慢,则会占据2个以上的VSync时间片,而导致显示器继续显示之前d帧的画面
2 限制了60帧率的刷新


Android帧率查看

1 可视化UI方式: 开发者选项->GPU呈现模式 绿色线即16.67ms分割线 但是这种对于surfaceView的帧率看不了

2 adb方式:adb shell dumpsys gfxinfo <PACKAGE_NAME> 命令

得到以下结果:

Total frames rendered: 30  // 渲染的帧数
Janky frames: 21 (70.00%) // 掉帧数
50th percentile: 32ms 
90th percentile: 150ms
95th percentile: 150ms
99th percentile: 150ms // 99%的帧渲染时间
Number Missed Vsync: 4
Number High input latency: 0
Number Slow UI thread: 6
Number Slow bitmap uploads: 15
Number Slow issue draw commands: 10
3  adb shell dumpsys SurfaceFlinger --latency + <Component名称>

3 可视化方式: perfdog
UI界面挺好看,也是要装一个app到设备上
原理:

4 SurfaceFlinger
Android系统的一个服务,用来生成Surface,管理帧缓冲区,实际做的事:把不同z坐标的Window按顺序排放,将所有的window合成一张图,也就是一帧

该命令会有128行数据结果的,第一行表示刷新间隔,不同手机可能会有不同的值, 接下来的127行表示了最近的127帧的渲染情况,每行分三列,第二列比较重要,因为它对应的是这一帧渲染时垂直同步脉冲到来的时间

缺点:1 对于非连续绘制场景,获取的FPS不准确,比如有的画面就是停在那,这时并不进行新的渲染,FPS会很低

5 systrace 查看到系统渲染的帧时间
可以在代码中
也可以用命令行 执行一个python脚本

若>16ms则是有问题的 说明渲染耗时太久 造成UI卡顿
通过放大那一帧进一步分析耗时问题

6 choreographer(编舞者)
在每次VSync的时候,会通过Choreographer.postCallback(callback) callback通知view去更新 而若此时view没绘制完,是不会响应这个callback的
https://juejin.im/entry/5ae1a4aef265da0b7e0bf94a
若非连续绘制场景,也是会回调,唯一不回调的就是view没绘制完的情况,即卡顿的情况


优化几大思想

所有的流畅度优化方法,本质都是:提高在下一次VSync到来之前,CPU/GPU完成绘制渲染的概率

1 直接砍功能
2 砍不了就懒加载
3 延迟加载
4 提前预加载 一般搭配5
5 子线程异步处理
6 减负


UI绘制慢的排查和解决

过渡绘制

过渡绘制即界面中存在多个layout重叠,上面的会显示,而底层的并不会显示,但是也执行了相关的计算和绘制渲染

一 过度绘制监测:

1 打开开发者选项的 显示过度绘制

  • 原色:没有过度绘制
  • 蓝色:1 次过度绘制
  • 绿色:2 次过度绘制
  • 粉色:3 次过度绘制
  • 红色:4 次及以上过度绘制

2 Tracer for OpenGL ES
查看每一帧的绘制过程
在android-sdk/tools/monitor中,打开即是Android Device Monitor 里面有Tracer for OpenGL ES

查看app当前界面的布局情况

Hierarchy View(deprecate) 使用Layout Inspector代替

设备adb连接androidStudio,在Tool->layout inspector中可以查看该设备的app的某个界面的layout情况
如看laout的view树,view的大小 left和top坐标等


优化策略:

1 若你的界面有自己的背景覆盖了全屏,则可以去除Activity自带的背景色

<style name="AppTheme" parent="android:Theme.Light.NoTitleBar">
    <item name="android:windowBackground">@null</item>
</style>

或者

getWindow().setBackgroundDrawable(null);

2 ImageView的background和imageDrawable重叠
ImageView的background设置了默认背景图
而 加载图片时 仍然会绘制这个背景图

解决: 把背景图和真正加载的图片都通过imageDrawable方法进行设置

3 限制 view的绘制区域
通过clipRect和clipPath方法 限制view的绘制区域
避免不必要的区域的绘制

4 使用 merge、include、ViewStub 标签进行布局优化

include设置layout=“xxx”进行布局设置
可以设置 长宽

<include layout="@layout/xxx_layout"></include>

使用 merge标签作根布局去除界面的多余层级
merge并不是一个ViewGroup 也不是View 而是直接将里面的控件加入到父布局中 减少布局嵌套

<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <TextView
   />
    <Button
     />
</merge>

ViewStub是惰性加载 用到的时候才加载布局

注意:
若要使用viewStub中的控件
不是用 viewStub.findxxx
而是直接 findxxx 并且要在 ViewStub加载后才可以
因为 ViewStub 加载了 ViewStub会移除自身 用相应的布局代替
所有的控件已经放到了 父布局里面了

实践: setVisibility会不会绘制
无论是 View.INVISIBLE 还是 View.GONE 都是不绘制的 也是不能被点击
但是 会调用 invalidate 因此
动态地inflation view性能要比SetVisiblity性能要好,当然ViewStub是最好的选择


前置同步屏障可以优化UI

通过Choreographer的postFrameCallback监听doFrame,记录doFrame时间mLastDoFrameTime
发起一个丢帧检测线程,每隔1帧时间验证
满足这两个条件,表明:因为同步屏障滞后导致了丢帧,需要前置同步屏障

  1. mLastDoFrameTime距离当前时间 是否超过1帧时间,也就是发生了丢帧
  2. 判断消息队列中是否有doFrame消息

确认发生丢帧后,post同步屏障到消息队列的队首,提前执行doFrame消息
实现则是修改MessageQueue,将同步屏障插入到队首


首页优化

1 View预加载
子线程中 异步预加载 inflate View
可能会与主线程加载View冲突,解决:

2 闪屏页和主页在同一个Activity 闪屏只作为一个View

3 数据到首帧渲染之间的优化:将一些任务放在doFrame之后,反射的方式将Sync Barrier提前


布局加载优化 X2C

问题背景:
通过XML编写布局,具体会有以下的性能瓶颈耗时:
1、通过I/O操作将XML加载到内存中,即读取xml很耗时
2、递归解析xml较耗时
3、通过反射生成对象耗时,是new的3倍以上

定义: 为了即保留xml的优点,又解决它带来的性能问题,在编译生成APK期间,将XML layout翻译生成对应的java文件,这样对于开发人员来说写布局还是写原来的xml,但对于程序来说,运行时加载的是对应的java文件。

原理
采用APT (AnnotationProcessor Tool) + JavaPoet技术来完成编译期间 [注解] ->[解注解] ->[翻译xml] ->[生成java] 整个流程的操作

优势:
省去了使用IO的方式去加载XML布局和解析XML的耗时。
采用Java代码直接new的方式去创建控件对象,所以它也没有反射带来的性能损耗

缺点:
兼容性和稳定性稍差
包体积增加
编译时间变长

存在问题:
不支持flavor,不支持< merge/ >标签,部分android属性不支持等
会存在很多跨模块的资源引用问题(A模块中的a.xml include B模块中的b.xml)。而apt/kapt插件是module级别的插件,跨模块的问题处理起来很困难。当时的方案是将< include/ >标签转换成< ViewStub/ > 处理,将编译期间的问题延迟到runtime处理。


代码层面检测

Lint工具和Inspect Code 静态代码扫描 用于发现XML布局等的问题
在项目的根目录中(有gradlew的)
执行

./gradlew lint

即可编译程序的同时开启lint检测 若lint不成功 则会中断编译

Android Studio中 Analyse->Inspect Code
这个是包含了lint检测 还有其他的资源检测等的

Android Profiler
可以提供更详细的方法调用关系链和对应耗时信息,通过层层分析,找到真正耗时的执行方法

** 此外本身堆栈采样会造成性能消耗,导致每个操作的耗时被放大了,不能代表真实耗时 **

TimiLogger 查看具体的执行时间

1 new一个TimiLogger对象

TimingLogger timings = new TimingLogger(TAG, "methodA");

TAG 标签 methodA 方法标签

2 在需要打印时间的地方调用addSplit(String splitLabel)方法

 timings.addSplit("worked");
 // ... do some work A ...
 timings.addSplit("work A");   

3 调用dumpToLog()打印日志

timings.dumpToLog();
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值