UI流畅度的优化主要是解决UI卡顿的现象,而UI卡顿的源头就是渲染性能的问题。布局太复杂或者是一个元素重复绘制多次等原因,Android系统无法及时完成那些复杂的界面渲染操作,这样就发生了丢帧,用户就会感觉到不流畅,卡顿。
Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,如果每次渲染都成功,这样就能够达到流畅的画面所需要的60fps,为了能够实现60fps,这意味着程序的大多数操作都必须在16ms内完成。
为什么是60fps?
我们通常都会提到60fps与16ms,可是知道为何会是以程序是否达到60fps来作为App性能的衡量标准吗?这是因为人眼与大脑之间的协作无法感知超过60fps的画面更新。
12fps大概类似手动快速翻动书籍的帧率,这明显是可以感知到不够顺滑的。24fps使得人眼感知的是连续线性的运动,这其实是归功于运动模糊的效果。24fps是电影胶圈通常使用的帧率,因为这个帧率已经足够支撑大部分电影画面需要表达的内容,同时能够最大的减少费用支出。但是低于30fps是无法顺畅表现绚丽的画面内容的,此时就需要用到60fps来达到想要的效果,当然超过60fps是没有必要的。
开发app的性能目标就是保持60fps,这意味着每一帧你只有16ms=1000/60的时间来处理所有的任务。
如果你的某个操作花费时间是24ms,系统在得到VSYNC信号的时候就无法进行正常渲染,这样就发生了丢帧现象。那么用户在32ms内看到的会是同一帧画面。
设置-开发者选项-Show GPU Overdraw,打开可以观察UI上的Overdraw情况。
蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域
设置-开发者选项-Profile GPU Rendering-选中On screen as bars
设置之后可以在手机画面上看到丰富的GPU绘制图形信息,分别关于StatusBar,NavBar,激活的程序Activity区域的GPU Rending信息。
随着界面的刷新,界面上会滚动显示垂直的柱状图来表示每帧画面所需要渲染的时间,柱状图越高表示花费的渲染时间越长。
中间有一根绿色的横线,代表16ms,我们需要确保每一帧花费的总时间都低于这条横线,这样才能够避免出现卡顿的问题。
每一条柱状线都包含三部分,蓝色代表测量绘制Display List的时间,红色代表OpenGL渲染Display List所需要的时间,黄色代表CPU等待GPU处理的时间。
从Android M版本开始,GPU Profiling工具把渲染操作拆解成如下8个详细的步骤进行显示。
注:GPU配置渲染工具虽然可以定位出问题发生在某个步骤,但是并不能定位到具体的某一行;当我们定位到某个步骤之后可以使用工具TraceView进行更加详细的定位。
Hierarchy Viewer是Android Studio自带的用于查看布局层级关系的工具。如下图可以打开Hierarchy Viewer。
Hierarchy Viewer使用如下图,可以看到点击LinearLayout,然后点击Profile Node(红色圆圈圈住的),你会发现所有的子View上面都有了3个圈圈,
(取色范围为红、黄、绿色),这三个圈圈分别代表measure 、layout、draw的速度,并且你也可以看到实际的运行的速度,如果你发现某个View上的圈是红色,那么说明这个View相对其他的View,该操作运行最慢,注意只是相对别的View,并不是说就一定很慢。
真机无法调试时可以使用ViewServer
链接:https://github.com/friendlyrobotnyc/TinyDancer
这个工具可以实时的显示app的帧率(GPU在一秒内绘制操作的帧数,标准是60fps)
同样是实时显示帧率
链接:https://github.com/wasabeef/Takt
用于检测主线程上的各种卡慢问题,并通过组件提供的各种信息分析出原因并进行修复。
链接:https://github.com/markzhai/AndroidPerformanceMonitor
clipRect用来控制canvas的绘制区域,可以避免canvas绘制那些不会显示出来的区域。示例:
明显卡片重叠的部分重复绘制了,所以是红色。这时候的代码如下:
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.save();
canvas.translate(20, 120);
for (Bitmap bitmap : mCards)
{
canvas.translate(120, 0);
canvas.drawBitmap(bitmap, 0, 0, null);
}
canvas.restore();
}
使用clipRect控制绘制区域,不绘制重叠的区域,代码如下:
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.save();
canvas.translate(20, 120);
for (int i = 0; i < mCards.length; i++)
{
canvas.translate(120, 0);
canvas.save();
if (i < mCards.length - 1)
{
canvas.clipRect(0, 0, 120, mCards[i].getHeight());
}
canvas.drawBitmap(mCards[i], 0, 0, null);
canvas.restore();
}
canvas.restore();
}
这时候效果如下:
可以看到卡片都只绘制了一次,变成了蓝色。因为Activity加了背景色,而DecorView也有背景色(主题设定),而Activity又会放入DecorView中,所以Activity的背景绘制了两次,我们可以不设置DecorView的背景色。在Activity的onCreate中调用:
getWindow().setBackgroundDrawable(null);
可使用Hierarchy Viewer工具查看布局的树状View图,减少布局之间的嵌套,嵌套层级越深,耗费时间越久。尽量使布局扁平化。
merge
标签一般和include
配合使用,可以降低减少布局的层级。merge只能用于根布局。
ViewStub提供了按需加载的功能,当需要时才会将ViewStub中的布局加载到内存.ViewStub是轻量级且不可见的视图,它没有大小,没有绘制功能,也不参与measure和layout,资源消耗非常低。
panel是layout/layout_err布局的根元素的id,可以在需要时将此布局加载出来。加载方式:
((ViewStub)findViewById(R.id.stub)).setVisibility(View.VISIBLE);
或者
View panel = ((ViewStub)findViewById(R.id.stub)).inflate();
App里常见的视图如蒙层、小红点,以及网络错误、没有数据等公共视图,使用频率并不高,如果每一次都参与绘制其实是浪费资源的,都可以借助ViewStub标签进行延迟初始化,仅当使用时才去初始化。
直接setAlpha在把视图绘制到帧缓存之前,先在离屏缓存上绘制这个视图,实际上是增加了另一个未被发现的过度绘制分层。
详细参考:给 App 提速:Android 性能优化总结
参考