作者:闫回
链接:https://www.jianshu.com/p/13a13d5aac49
大多数用户感知到的卡顿等性能问题的最主要根源都是因为渲染性能,Android系统每隔大概16.6ms发出的VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。
我们通常都会提到60fps与16ms,可是知道为何会是以程序是否达到60fps来作为APP性能的衡量标准吗?这是因为人眼与大脑之间的写作无法感知超过60fps的画面更新。
12fps大概类似手动快速翻动的帧率,这明显是可以感知到不够顺滑的。24fps使得人眼感知的是连续线性的运动,这其实是归功于运动模糊的效果,24fps是电影胶圈通常使用的帧率,因为这个帧率已经足够支撑大部分电影画面需要表达的内容,同时能够最大的减少费用支出。但是低于30fps是无法顺畅表现绚丽的画面内容的,此时就需要用到60fps来达到想要的效果,当然超过60fps是没有必要的。
开发app的性能目标就是保持60fps,这意味着每一帧你只有16ms=1000/60的时间来处理所有的任务。
如果某个操作花费时间是24ms,系统在得到VSYNV信号的时候就无法进行正常渲染,这样就发生了丢帧现象,那么用户在32ms内看到的会是同一帧画面。
有很多原因可以导致丢帧,一般主线程过多的UI绘制、大量的IO操作或是大量操作占用CPU,都会导致App界面卡顿。
更详细的描述请参考Android图形显示系统https://blog.csdn.net/a740169405/article/details/70548443
卡顿分析工具CPU Profile介绍
最基本的介绍参考官网https://developer.android.google.cn/studio/profile/cpu-profiler
实际的操作中如何使用呢,如果你发现你的某个操作有点卡顿,这时候你就可以使用这个工具进行查找原因,先点击cpu profile按钮,这时候有两种配置方式
选择记录配置.png
Sample Java Methods
对Java方法采样:在应用的Java代码执行期间,频繁捕获应用的调用堆栈,分析器会比较捕获的数据集,以推导与应用的Java代码执行有关的时间和资源使用信息。如果应用在捕获调用堆栈后进入一个方法并在下次捕获前退出该方法,分析器将不会记录该方法调用。如果您想要跟踪生命周期如此短的方法,应使用检测跟踪。
Trace Java Methods
跟踪 Java 方法:在运行时检测应用,以在每个方法调用开始和结束时记录一个时间戳。系统会收集并比较这
些时间戳,以生成方法跟踪数据,包括时间信息和 CPU 使用率。
可以根据自己的需要进行选择,然后我们点击Record进行记录,这时候我们进行手机上面的操作,当操作完成后点击 Stop recording,这时候就会生成对应的跟踪信息
跟踪信息
然后我们可以进行分析,逐步排查卡顿的原因。
还有一种方式可以生成对应的跟踪信息,在开始记录的位置使用startMethodTracingSampling(String tracePath, int bufferSize,int intervalUs)进行采样
,三个参数代表生成trace文件的位置,文件大小还有采样的时间间隔
或者startMethodTracing(String tracePath, int bufferSize, int flags)进行跟踪,
三个参数代表生成trace文件的位置,文件大小还有一个标志设置,bufferSize参数指定了trace文件的最大值。Trace信息可以使用很多空间,你的存储器可能是有限的,所以尝试去使用一个可接受的值(默认8M)。安卓现在只定义一个标志,Debug.TRACE_COUNT_ALLOCS.
设置了之后我们就可以在对应的位置找到trace文件,使用AS工具打开进行问题查找了。
布局优化
层级优化
使用Layout Inspector
Layout Inspector工具.png
然后选择需要查看的进程与Activity:
布局检查器.png
Layout Inspector主要用来分析布局的层级结构,减少不必要的层级,更加详细的介绍参考官网https://developer.android.google.cn/studio/debug/layout-inspector?hl=zh_cn
使用merge标签
merge标签是用来帮助在视图树中减少重复布局的,当某个layout布局被多次引用时,可以使用merge,减少嵌套
可参考文章https://blog.csdn.net/a740169405/article/details/50473909
使用ViewStub标签
当我们布局中存在一个View/ViewGroup,在某个特定时刻才需要它的展示时,可能会使用gone或者invisible,在需要显示时再设置visible可见。
viewStub是一个轻量级的view,它不可见,不用占用资源,只有设置viewstub为visible或者调用其inflater()方法时,其对应的布局文件才会被初始化。
可参考详细的文章https://blog.csdn.net/a740169405/article/details/50351013
过度渲染
过度渲染是指系统在渲染单个帧的过程中多次在屏幕上绘制某一个像素,例如,如果我们有若干界面卡片堆叠在一起,每张卡片都会遮盖其下面一张卡片的部分内容,但是,系统仍然需要绘制堆叠中的卡片被遮盖的部分。
GPU过度绘制检查
手机开发者选项中能够显示过度渲染检查功能,通过对界面进行彩色编码来帮我们识别过度绘制,开启步骤如下:
1.进入开发者选项
2.找到调试GPU过度绘制
3.在弹出的对话框中,选择显示过度绘制区域
Android 将按如下方式为界面元素着色,以确定过度绘制的次数:
参考过度绘制.png
更加详细的介绍参考官网https://developer.android.google.cn/topic/performance/rendering/inspect-gpu-rendering?hl=zh_cn
解决过度绘制问题
可以通过以下方法来减少过度绘制
1.移除布局中不需要的背景
默认情况下,布局没有背景,这表示布局本身不会直接渲染任何内容,但是当布局具有背景时,其有可能会导致过度绘制
移除不必要的背景可以快速提高渲染性能,不必要的背景可能永远不可见,因为它会被在该视图上绘制的任何其他内容完全覆盖,例如,当系统在父视图上绘制子视图时,可能会完全覆盖父视图的背景。
2.使视图层次结构扁平化
可以通过优化视图层次结构来减少重叠界面对象的数量,从而提高性能。
3.降低透明度
对于不透明的view,只需要渲染一次就可以显示出来,但是如果这个view设置了alpha值,则至少需要渲染两次,这是因为使用了alpha的view需要先知道混合view的下一层元素是什么,然后再结合上层的view进行混色处理。透明动画、淡入淡出和阴影等都涉及到某种透明度,这就会造成了过度绘制。可以通过减少要渲染的透明对象的数量,来改善这些情况下的过度绘制,例如,如需获得灰色文本,可以在TextView中绘制黑色文本,再为其设置半透明的透明度值,但是,简单地通过用灰色绘制文本也能获得同样的效果,而且能够大幅提升性能。
布局加载优化
LayoutInflater加载xml布局的过程会在主线程使用IO读取XML布局文件进行XML解析,再根据解析结果利用反射 创建布局中的View/ViewGroup对象。这个过程随着布局的复杂度上升,耗时自然也会随之增大。Android为我们 提供了 Asynclayoutinflater 把耗时的加载操作在异步线程中完成,最后把加载结果再回调给主线程。
dependencies {
implementation "androidx.asynclayoutinflater:asynclayoutinflater:1.0.0"
}
new AsyncLayoutInflater(this)
.inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() { @Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
setContentView(view);
//......
} });
1、使用异步 inflate,那么需要这个 layout 的 parent 的 generateLayoutParams 函数是线程安全的; 2、所有构建的 View 中必须不能创建 Handler 或者是调用 Looper.myLooper;(因为是在异步线程中加载的,异
步线程默认没有调用 Looper.prepare );
3、AsyncLayoutInflater 不支持设置 LayoutInflater.Factory 或者 LayoutInflater.Factory2; 4、不支持加载包含 Fragment 的 layout
5、如果 AsyncLayoutInflater 失败,那么会自动回退到UI线程来加载布局