Android UI卡顿优化

一、Android渲染机制

(一)两个重要概念

        屏幕刷新率:1秒内屏幕刷新次数,取决于硬件性能参数。

        帧率:CPU、GPU在1秒内绘制的帧数。

(二)为什么是60fps?

        目前移动设备上一般使用60HZ的屏幕刷新率,系统为了配合60HZ的刷新频率,把帧率也定为60fps,所以Android的渲染机制是16.67ms绘制一次。

        两者最好保持一致,如果屏幕刷新率是 75 hz,帧率是 60 fps,每秒软件渲染60次,你刷新 75 次,是没有啥效果的,除了重复帧率费电;同样,如果你屏幕刷新率是 30 hz,软件是 60 fps,那么软件每秒绘制的60次有一半是没有显示就被抛弃了的。

(三)黄油计划

        谷歌为解决滑动不流畅问题引入黄油计划。黄油计划包含三个核心元素:VSYNCTriple BufferChoreographer

        1. VSYNC的作用:VSync是Vertical Synchronization垂直同步)的缩写,是一种在PC上很早就广泛使用的技术,可以简单的把它认为是一种定时中断。在接收到VSYNC信号后CPU立刻开始绘制下一帧内容

        2. Choreographer的作用:接收系统的VSYNC信号,统一管理应用的输入、动画和绘制等任务的执行时机。

        3. Triple Buffer的作用:系统使用3个缓冲区用于显示工作。

        正常情况下双缓冲区系统运行情况:

        帧率低于屏幕刷新率的情况:

        引入Triple Buffer后的情况:

(四)CPU、GPU、SurfaceFlinger 

        GPU由来:CPU的任务繁多,做逻辑计算,还要做内存管理、显示操作,因此在实际运算(浮点运算)的时候性能会大打折扣,在没有GPU的时代,不能显示复杂的图形,其运算速度远跟不上今天复杂三维游戏的要求。即使CPU的工作频率超过2GHZ或更高,对它绘制图形提高也不大。这时GPU的设计就出来了。

  • CPU:中央处理器,它集成了运算、缓冲、控制等单元,包括绘图功能。CPU将对象处理为多维图形,纹理(Bitmaps、Drawables等都是一起打包到统一的纹理)。
  • GPU:一个类似于CPU的专门用来处理Graphics的处理器,作用用来帮助加快栅格化操作(将向量图形格式表示的图像转换成位图以用于显示器,即把button、textview等组件拆分到不同的像素上进行显示。这是一个很费时的操作,GPU的引入就是为了加快栅格化的操作)。当然也有相应的缓存数据(例如缓存已经光栅化过的bitmap等)机制。
  • OpenGL ES:2D和3D图形应用程序接口API。
  • Display List:Android需要把XML布局文件转换成GPU能够识别并绘制的对象,DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。
  • SurfaceFlinger:接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备

        Android系统的绘制原理
        Android中每个界面都是大大小小的View组成,对于应用里的每个view都会经过:measure,layout,draw。然后就由主线程传给CPU进行计算纹理,再通过OpenGL ES接口调用GPU进行栅格化处理,再通过跨进程通信机制把需要显示的数据传到SurfaceFlinger,通过它将栅格化的信息交给硬件合成器合成后输出到显示屏。

二、UI检测工具

        Layout Inspector视图层次结构分析

        Show GPU Overrdraw:用于检测过渡绘制

        Profile GPU Rendering:用于检查界面的渲染性能

 

三、优化方案

        主线程避免做耗时操作,否则CPU的绘制工作可能会被delay。

1、合理选择父容器

        LinearLayout易用,效率高,表达能力有限。RelativeLayout复杂,表达能力强,效率稍逊。对于同一界面而言,是使用尽量少的、表达能力强的RelativeLayout作为容器,还是选择多个、表达能力稍弱的LinearLayout来展示。从减少overdraw的角度来看,LinearLayout会增加控件数的层级,自然是RelativeLayout更优,但是当某一界面在使用LinearLayout并不会比RelativeLayout带来更多的控件数和控件层级时,LinearLayout则是首选。应根据实际情况来选择合适容器控件,在保证性能的同时,尽量避免overdraw。

2、去掉不必要的背景

       如window的默认背景、外层Layout设置一个背景,子View再设置一个背景,RecyclerView设置一个背景,子Item再设置一个背景等情况。

3、ClipRect & QuickReject

        为了解决Overdraw的问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少消耗。但对于自定义View(通常重写了onDraw方法),Android系统无法检测在onDraw里面具体会执行什么操作,系统无法监控并自动优化,也就无法避免Overdraw。可以通过canvas.clipRect()来帮助系统识别那些可见的区域。

4、ViewStub

        对于运行时动态根据条件来决定显示哪个View或布局这种需求,常用做法是把View都写在上面,先把它们的可见性都设为View.GONE,然后在代码中动态的更改它的可见性。但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性。此时可以选择ViewStub,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局才会被Inflate和实例化。

5、Merge

        作用:减少View层级

        示例:

不使用Merge的布局层级:
<FrameLayout>
   <include layout="@layout/layout2"/>
</FrameLayout>

<FrameLayout>
   <TextView />
</FrameLayout>

<FrameLayout>
   <FrameLayout>
      <TextView />
   </FrameLayout>
</FrameLayout>

使用Merge的布局层级:
<FrameLayout>
   <include layout="@layout/layout2"/>
</FrameLayout>

<merge>
   <TextView />
</merge>

<FrameLayout>
   <TextView />

        注意点:

  • merge必须放在布局文件的根节点上。
  • 因为merge标签并不是View,所以在通过LayoutInflate.inflate方法渲染的时候, 第二个参数必须指定一个父容器,且第三个参数必须为true,也就是必须为merge下的视图指定一个父亲节点。

6、draw9Path

        经常会遇到给ImageView设置一张背景图的需求,这时两层drawable的重叠区域是绘制了两次的。这时可以将背景制作成draw9path,并将和前景重叠的部分设置为透明,由于Android的2D渲染器会优化draw9patch中的透明区域,从而优化了这次overdraw。

猜你喜欢

转载自blog.csdn.net/qq_34519487/article/details/113048447