UI优化

作者: 修塔寻千里 | 来源:发表于2019-09-30 14:24 被阅读0次

UI渲染基础

1、屏幕与适配


通过dp和自适应布局可以基本解决屏幕碎片化的问题,这也是Android推荐使用的屏幕兼容性适配方案,但它存在两个比较大的问题:

  • 不一致行,由于dpi与实际的ppi的差异性,导致在相同分辨率的手机上,控件的实际大小会有所不同
  • 效率,设计师设计稿都是以px为单位的,开发人员为了UI适配,需要手动通过百分百估算出dp值
    除了直接dp适配之外,目前比较常用的适配方案有两种:
  • 限制符适配方案,主要有宽高限定符与smallestWidth限定符适配方案
  • 今日头条适配方案,通过反射修正系统的density值

2、CPU与GPU

UI组件在绘制到屏幕之前,都需要经过Rasterization(栅格化)操作,而栅格化操作有是一个非常耗时的操作,GPU也就是图形处理器,它主要用于处理图形运算,可以帮助我们加快栅格化操作。


3、OpenGL和Vulkan

硬件绘制,我们通过调用OpenGL ES接口利用GPU完成绘制,OpenGL是一个跨平台的图形API,它为2D/3D图形处理硬件制定了标准软件接口,而OpenGL ES是OpenGL的子集,专为嵌入式设备设计。



Android 7.0把OpenGL ES升级到最新的3.2版本同时,还添加了对Vulkan的支持。Vulkan是用于高性能的3D图形的低开销、跨平台API。在改善功耗、多核优化提升绘图调用上有着非常明显的优势。

Android渲染


Android各组件的作用:

  • 画笔:Skia或者OpenGL,我们用Skia画笔绘制2D图形,也可以用OpenGL来绘制2D/3D图形
  • 画纸:Surface,在Android 中Window是View的容器,每个窗口都会关联一个Surface,而WindowManager则负责管理这些窗口,并把它们的数据传递给SurfaceFlinger
  • 画板:Graphic Buffer,用于应用程序图形的绘制,在Android 4.1之前是双缓冲机制,在Android 4.1之后采用三缓冲机制
  • 显示:SurfaceFlinger,它将WindowManager提供的所有Surface,通过硬件合成器Hardware Composer合成并输出到显示屏。

1、Android 4.0开启硬件加速


在Android3.0之前,或者没有启用硬件加速时,系统会使软件方式渲染UI,整个流程如上图所示:

  • Surface,每个View都由某一个窗口管理,而每个窗口都关联有一个Surface
  • Canvas,通过Surface的lock函数获得一个Canvas,Canvas可以简单理解为Skia底层接口的封装
  • Graphic Buffer, SurfaceFlinger会帮我们托管一个BufferQueue,我们冲BufferQueue中拿到Graphic Buffer,然后通过Canvas以及Skia将绘制内容栅格化到上面
  • SurfaceFlinger, 通过Swap Buffer 把Front Graphic Buffer 的内容交给SurfaceFlinger,最后硬件合成器Hardware Composer合成并输出到显示屏。
    Android 3.0开始支持硬件加速,到Android 4.0 时默认开启硬件加速



    硬件加速绘制与软件绘制整个流程差异非常大,最核心就是我们通过GPU完成Graphic Buffer的内容绘制。此外硬件绘制还引入了一个DisplayList的概念,每个View内部都有一个DisplayList,当某个View需要重绘时,将它标记会Dirty。
    当需要重绘时,仅仅只需要重绘一个View的DisplayList,而不是像软件绘制那样需要向上递归。这样可以大大减少绘图的操作数量,提高渲染效率。


2、Android 4.1:Project Butter

Project Butter主要包含两个组成部分:一个是VSYNC,一个是Triple Buffering。

VSYNC信号

VSYNC类似于时钟中断,每收到VSYNC中断,CPU会立即准备Buffer数据,由于大部分显示设备刷新频率都是60HZ(一秒刷新60次),也是一帧数据的准备工作要在16ms内完成。



这样应用总是在VSYNC边界上开始绘制,而SurfaceFlinger总是VSYNC边界上进行合成,这样可以消除卡顿,并提升图形的视觉表现。

三级缓冲机制Triple Buffering

在Android 4.1之前都使用双缓冲机制,不同的View或者Activity它们都会共用一个Window,也就是共用一个Surface。而每个Surface都会有一个BufferQueue缓存机制,但是这个队列会由SurfaceFlinger管理,通过匿名共享内存机制与App应用成交互。



整个流程如下:

  • 每个Surface对应的BufferQueue内部都有两个Graphic Buffer,一个用于绘制一个用于显示。会把内容先绘制到离屏缓冲区(OffScreen Buffer),在需要显示时,才把离屏缓冲区的内容通过Swap Buffer复制到Front Graphic Buffer中
  • 这样SurfaceFlinger就拿到了某个Surface最终要显示的内容,但是同一时间可能会有多个Surface,这里可能是不同应用的Surface,也可能是同一个应用里面类似SurfaceView和TextureView,他们都会有自己单独的Surface
  • 这个时候SurfaceFlinger把所有的Surface要显示的内容统一交给Hardware Composer,它会根据位置、Z-Order顺序等信息合成为最终显示的内容,而这个内容交给系统的帧缓冲区Frame Buffer来显示(Frame Buffer是非常底层的,可以理解为屏幕显示的抽象)。
    如果只有两个Graphic Buffer缓冲区A和B,如果CPU/GPU绘制时间过程,超过了一个VSYNC信号周期,因为缓冲区B中的数据还没准备好,所以只能继续显示A缓冲区的内容,这样缓冲区A和B都分别被显示设备和GPU占用,CPU无法准备下一帧的数据。



    如果再提供一个缓冲区,CPU、GPU和显示设备都能各自使用各自的缓冲区工作,互不应用。简单来说,三缓冲机制就是在双缓冲机制的基础上增加了一个Graphic Buffer,这样可以最大限度的利用空闲时间,带来的坏处是多使用了一个Graphic Buffer所占用的内存。


数据测量

Android 4.1新增了Systrace性能数据采样和分析工具,Tracer for OpenGL ES也是Android 4.1新增的工具,可逐帧、逐函数的记录App用OpenGL ES的绘制过程。它提供了每个OpenGL函数调用的消耗时间,所以很多时候用来做性能分析。在Android 4.2系统增加了检测过度绘制工具。

3、Android 5.0:RenderThread

经过Project Buffer黄油计划之后,Android的渲染性能有了很大的改善,但是有一个问题,虽然我们利用了GPU的图形高性能运算,但是从计算DisplayList,到通过GPU绘制到Frame Buffer,整个计算和绘制都在UI主线程中完成。



UI主线程任务过于繁重,如果整个渲染过比较耗时,可能会造成用户无法响应的操作,进而出现卡顿。GPU对图形的绘制渲染能力更胜一筹,如果使用GPU并在不同的线程绘制渲染图形,那么整个流程会更加顺畅。
所以Android 5.0引入了两个比较大的改变。一是RenderNode,它对DisplayList及一些View的显示属性做了进一步的封装;另一个是RenderThread,所欲的GL命令执行都放到这个线程上,渲染线程在RenderNode中存有渲染帧的所有信息,可以做一些属性动画,这样即便主线程有耗时的操作也可以保证动画流程。
我们可以开启Profile GPU Rendering检查,在Android 6.0之后,会输出下面的计算和绘制每个阶段的耗时:



如果将上面的步骤转化为线程模型,可以得到下面的流水线模型。CPU将数据同步(sync)给GPU之后,一般不会阻塞等待GPU渲染完毕,而是通知结束后返回,而RenderThread承担了比较多的绘制工作,分担了主线程很多压力,提高了UI线程的响应速度。

UI渲染测量

  • 测试工具:Profile GPU Rendering 和Show GPU Overdraw
  • 问题定位工具:Systrace和Tracer for OpenGL ES
    在Android 3.1之后,推荐使用Graphic API Debugger(GAPID)来代替Tracer for OpenGL ES工具,GAPID可以说是升级版,它不仅可以跨平台,而且功能更加强大,支持Vulkan与回放。


1、gfxinfo

gfxinfo可以输出包含各阶段发生的动画及帧相关的性能信息,具体命令如下:

adb shell dumpsys gfxinfo 包名

除了渲染性能之外,gfxinfo还可以拿到渲染相关的内存和View hierachy信息,在Android 6.0之后,gfxinfo命令增加了framestats参数,可以拿到最近120帧每个绘制阶段的耗时信息。通过这个命令可以实现自动化统计应用的帧率,更进一步还可以实现自定义的“Profile GPU Rendering”工具。

adb shell dumpsys gfxinfo 包名 framestats

2、SurfaceFlinger

关于渲染使用的内存情况,可以使用下面的命令拿到SurfaceFlinger相关的信息:

adb shell dumpsys SurfaceFlinger

UI优化的常用手段


要求所有的渲染操作都必须在16ms(=1000ms / 60fps)内完成,UI优化就是拆解熏染的各个阶段的耗时,找到瓶颈点,再加以优化。

1、尽量使用硬件加速

硬件加速绘制的性能是远大于软件绘制,所以要尽可能的采用硬件加速。有些情况不能采用硬件加速,因为硬件加速不支持所有的Canvas API,具体API兼容列表,查看drawing-support,如果使用了不支持的API,系统就需要通过CPU软件模拟绘制,这也是渐变、磨砂、圆角等效果渲染性能比较低的原因。
SVG也是一个非常典型的例子,SVG很多指令硬件加速都不支持,但可以提前将SVG转换成Bitmap保存起来,这样系统就可以更好的使用硬件加速会绘制,同理对于圆角、渐变等场景,也可以改为Bitmap实现。

2、Create View优化

View的创建是在主线程完成的,对于一些负责的界面,耗时会增加,根据View的创建流程有如下优化点:

使用代码创建

XML进行UI编写十分方便且提供可视化预览,但是效率方面大打折扣,建议在对性能要求非常高,且修改不频繁的页面采用代码创建的方式。

异步创建

如果再线程提前创建View,会报如下的错误:

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() at android.os.Handler.(Handler.java:121)

解决方案是先将线程的Looper的MessageQueue替换成UI线程Looper的Queue,最后在创建完View后把线程的Looper恢复成原来的。


View复用

正常来说,View会随着Activity的销毁而同时销毁,ListView、RecycleView通过View的缓存和重用大大提升渲染性能。因此可以实现一套可以在不同Activity或Fragment使用的View缓存机制。但是这里需要保证所有进入缓存的View不会保留之前的状态。


3、measure/layout优化

  • 减少UI布局层次,尽量扁平化,使用<ViewStub><Merg>等优化
  • 优化layout开销,尽量不使用RelativeLayout或者基于weighted LinearLayout,他们layout的开销非常巨大,建议使用ConstrainLayout替代。
  • 背景优化,尽量不要重复设置背景
    PrecomputedText提供了接口,可以异步进行measure和layout,不必再主线程中执行。

UI优化其他手段

1、Litho:异步布局

Litho是Facebook开源的声明式Android UI渲染框架,本身非常强大,内部做了很多优化。

  • 异步布局
    一般来说Android所有的控件绘制都要遵守measure -> layout -> draw的流水线。


Litho把measure和layout都放到了后台线程,只留下了必须要再主线程完成的draw,大大降低了UI线程的负载。


  • 界面扁平化
    Litho使用自有的布局引擎(Yoga),在布局阶段可以检测不必要的层级,减少ViewGroups,来实现UI扁平化。
  • 优化RecycleView
    Litho优化RecycleView中UI组件的缓存和回收方法,原生RecycleView或者ListView是按照viewType来进行缓存和回收,但是如果一个RecycleView/ListView中出现过多的viewType,会使缓存形同虚设,Litho是按照text、image和video独立回收的,可以提高缓存命中率、降低内存使用率、提高滚动帧率。


2、Flutter:自己的布局+渲染引擎

Flutter是谷歌推出的开源移动应用开发框架,可发着可以通过Dart语言开发App,一套代码同时运行在iOS和Android平台。在Android上Flutter完全没有基于系统的渲染引擎,而是把Skia引擎直接集成到App中,并且直接使用了Dart虚拟机。



开发Flutter应用总的来说简化了线程模型,框架给我们抽象出各司其职的Runner,包括UI、GPU、I/O、Platform Runner。Android平台上面没一个引擎实例启动的时候都会为UI Runner、GPU Runner、I/O Runner各自创建一个新的线程,所有Engine实例共享同一个Platform Runner和线程。

  • 首先UI Runner会执行root isolate(可以简单理解为main函数,isolate是Dart虚拟机中一种执行并发代码实现,Dart虚拟机实现了Actor的并发模型,与大名鼎鼎的Erlang使用了类似的并发模型。)
  • Flutter 引擎得到通知后,会告知系统我们要同步VSYNC
  • 得到GPU的VSYNC信号后,对UI Widgets进行Layout并生成一个Layer Tree
  • 然后Layer Tree会交给GPU Runner进行合成和栅格化
  • GPU Runner使用Skia库绘制相关图形



    Flutter也采用了类似Litho、React属性不可变,单向数据流的方案,这样做的好处是将视图与数据分开。

3、RenderThread与RenderScript

在Android 5.0系统增加了RenderThread对于ViewPropertyAnimator和CircularReveal动画,可以使用RenderThread实现动画的异步渲染。图片的变换涉及大量的计算任务,可以通过RenderScript提高性能,它是Android操作系统提供的一套API,基于异构计算思想,专门用于密集计算。RenderScript提供了三个基本工具:一个硬件无关的通用计算API;一个类似于CUDA、OpenGL和GLSL的计算API;一个类C99的脚本语言,语序开发者以较少的代码实现功能复杂且性能优越的应用程序。

相关文章

  • Android UI性能优化

    Ui性能优化 参考博客:Android UI性能优化实战 识别绘制中的性能问题Android UI性能优化详解 1...

  • 日常学习

    1:日常中优化有哪些: 优化主要分为UI优化、内存优化、相应优化 UI优化 1、布局控件的选择:主要布局有Rela...

  • Android App优化

    本篇文章来简单介绍一下App优化的方式。 1、UI优化 UI优化主要是提高UI的绘制效率,包括减少UI层次,提高初...

  • Android性能优化大纲

    1.内存优化 内存泄漏 优化分析 内存优化工具 2.UI优化 UI卡顿分析 渲染优化 计算性能优化 3.电量优化 ...

  • UI优化

    UI优化,也就是体验优化 常见优化场景 1) 过渡绘制 2) 布局复杂度 3) 逻辑优化 4) 内存使用优化 UI...

  • Android UI优化

    Android性能优化 - UI篇Android性能优化 - CPU/GPU篇 一、UI层级优化 借助工具:Hie...

  • Android进阶篇:深度解析UI原理和高级的UI优化方式

    不知道UI原理如何做UI优化? 本文内容分为三个部分,UI原理、LayoutInflater原理、UI优化,篇幅有...

  • 性能优化

    Android UI性能优化实战 识别绘制中的性能问题性能优化(二) UI 绘制优化 通过Hierarchy Vi...

  • 无标题文章

    APP性能优化 UI卡顿优化 View的绘制原理 UI卡顿原理分析 UI卡顿检测分析 BlockCanary原理分...

  • Android 之你真的了解 View.post() 原理吗?

    UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识、如何优化 UI 渲染两部...

网友评论

      本文标题:UI优化

      本文链接:https://www.haomeiwen.com/subject/xmyructx.html