Android(安卓)App性能优化之UI流畅度优化
文章目录
- 一、卡顿的问题本质
- 二、检测工具
- 1.手机自带的Show GPU Overdraw
- 2.手机自带的Profile GPU Rendering
- 3.Hierarchy Viewer
- 4.Tiny Dancer
- 5.Takt
- 6.BlockCanary/Android Performance Monitor
- 三、优化方案
- 1.避免过度绘制
- 1.1 移除不必要的background
- 1.2 clipRect
- 2.减少布局的层级
- 3.使用标签
- 3.1 merge
- 3.2 ViewStub
- 4.Alpha
- 5.硬件加速
一、卡顿的问题本质
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内看到的会是同一帧画面。
二、检测工具
1.手机自带的Show GPU Overdraw
设置-开发者选项-Show GPU Overdraw,打开可以观察UI上的Overdraw情况。
蓝色,淡绿,淡红,深红代表了4种不同程度的Overdraw情况,我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域
2.手机自带的Profile GPU Rendering
设置-开发者选项-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个详细的步骤进行显示。
- Swap Buffers:表示处理任务的时间,也可以说是CPU等待GPU完成任务的时间,线条越高,表示GPU做的事情越多;
- Command Issue:表示执行任务的时间,这部分主要是Android进行2D渲染显示列表的时间,为了将内容绘制到屏幕上,Android需要使用Open GL ES的API接口来绘制显示列表,红色线条越高表示需要绘制的视图更多;
- Sync & Upload:表示的是准备当前界面上有待绘制的图片所耗费的时间,为了减少该段区域的执行时间,我们可以减少屏幕上的图片数量或者是缩小图片的大小;
- Draw:表示测量和绘制视图列表所需要的时间,蓝色线条越高表示每一帧需要更新很多视图,或者View的onDraw方法中做了耗时操作;
- Measure/Layout:表示布局的onMeasure与onLayout所花费的时间,一旦时间过长,就需要仔细检查自己的布局是不是存在严重的性能问题;
- Animation:表示计算执行动画所需要花费的时间,包含的动画有ObjectAnimator,ViewPropertyAnimator,Transition等等。一旦这里的执行时间过长,就需要检查是不是使用了非官方的动画工具或者是检查动画执行的过程中是不是触发了读写操作等等;
- Input Handling:表示系统处理输入事件所耗费的时间,粗略等于对事件处理方法所执行的时间。一旦执行时间过长,意味着在处理用户的输入事件的地方执行了复杂的操作;
- Misc Time/Vsync Delay:表示在主线程执行了太多的任务,导致UI渲染跟不上vSync的信号而出现掉帧的情况;出现该线条的时候,可以在Log中看到这样的日志:
注:GPU配置渲染工具虽然可以定位出问题发生在某个步骤,但是并不能定位到具体的某一行;当我们定位到某个步骤之后可以使用工具TraceView进行更加详细的定位。
3.Hierarchy Viewer
Hierarchy Viewer是Android Studio自带的用于查看布局层级关系的工具。如下图可以打开Hierarchy Viewer。
Hierarchy Viewer使用如下图,可以看到点击LinearLayout,然后点击Profile Node(红色圆圈圈住的),你会发现所有的子View上面都有了3个圈圈,
(取色范围为红、黄、绿色),这三个圈圈分别代表measure 、layout、draw的速度,并且你也可以看到实际的运行的速度,如果你发现某个View上的圈是红色,那么说明这个View相对其他的View,该操作运行最慢,注意只是相对别的View,并不是说就一定很慢。
真机无法调试时可以使用ViewServer
4.Tiny Dancer
链接:https://github.com/friendlyrobotnyc/TinyDancer
这个工具可以实时的显示app的帧率(GPU在一秒内绘制操作的帧数,标准是60fps)
5.Takt
同样是实时显示帧率
链接:https://github.com/wasabeef/Takt
6.BlockCanary/Android Performance Monitor
用于检测主线程上的各种卡慢问题,并通过组件提供的各种信息分析出原因并进行修复。
链接:https://github.com/markzhai/AndroidPerformanceMonitor
三、优化方案
1.避免过度绘制
1.1 移除不必要的background
1.2 clipRect
clipRect用来控制canvas的绘制区域,可以避免canvas绘制那些不会显示出来的区域。示例:
明显卡片重叠的部分重复绘制了,所以是红色。这时候的代码如下:
@Overrideprotected 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控制绘制区域,不绘制重叠的区域,代码如下:
@Overrideprotected 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);
2.减少布局的层级
可使用Hierarchy Viewer工具查看布局的树状View图,减少布局之间的嵌套,嵌套层级越深,耗费时间越久。尽量使布局扁平化。
3.使用标签
3.1 merge
merge
标签一般和include
配合使用,可以降低减少布局的层级。merge只能用于根布局。
3.2 ViewStub
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标签进行延迟初始化,仅当使用时才去初始化。
4.Alpha
直接setAlpha在把视图绘制到帧缓存之前,先在离屏缓存上绘制这个视图,实际上是增加了另一个未被发现的过度绘制分层。
- 文本视图(TextView)—— 用 setTextColor() 代替 setAlpha() 方法。文本颜色如果使用 alpha 通道,会导致直接用 alpha 绘制文本。
- 图像视图(ImageView)—— 用 setImageAlpha() 代替 setAlpha() 方法。原因同文本视图。
- 自定义视图 —— 如果你自定义的视图不支持视图重叠,这个复杂的行为就和我们无关。那就没有办法,如上面例子所示,子视图会混在一起。通过重载 hasOverlappingRendering() 方法并返回错误,我们可以通知系统对视图采用直接和简单的通道。通过重载 onSetAlpha() 方法并返回正确,我们就有一个选择来手动处理设定一个 alpha 值会发生什么。
详细参考:给 App 提速:Android 性能优化总结
5.硬件加速
参考
- Android性能优化(二)之布局优化面面观
- Android UI性能优化实战 识别绘制中的性能问题
- Android性能优化典范 - 第1季
- Android性能优化之渲染篇
更多相关文章
- Android——谷歌cameraview详解
- Android(安卓)的几种布局方式及实践【转】
- Android基本组件之图像视图等余下组件(自用)
- Android视图加载到窗口的过程
- Android(安卓)子布局不超过父布局 圆角
- android简单的答题游戏
- android 数据备份
- 详解Android冷启动实现APP秒开的方法
- 【eoe教程】Android中自定义视图的绘制方法