View性能优化(一)

1 View 机制简单介绍

UI 渲染还依赖两个核心的硬件:CPU 与 GPU。UI 组件在绘制到屏幕之前,都需要经过 Rasterization(栅格化)操作,而栅格化操作又是一个非常耗时的操作。GPU(Graphic Processing Unit )也就是图形处理器,它主要用于处理图形运算,可以帮助我们加快栅格化操作。

1

Android 图形系统的整体架构

2

图形渲染过程当作一次绘画过程:

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

在 Android 3.0 之前,或者没有启用硬件加速时,系统都会使用软件方式来渲染 UI。从 Androd 3.0 开始,Android 开始支持硬件加速,到 Android 4.0 时,默认开启硬件加速,也就是 CPU 会将数据传输给 GPU,利用 GPU 进行渲染,但硬件的加速会占有一定的RAM。平时我们使用 View 更多的是使用系统提供的控件或者控件的组合、以及自定义 View,自定义 View 一般也是通过 Canvas 和 Paint 来绘制。但需要注意的是,Canvas 有些 API 是不支持硬件加速的,所以设置硬件加速的开关。

3

在API >= 14上,默认是开启的,如果你的应用只是标准的View和Drawable,全局都打开硬件加速,是不会有任何问题的。然而,硬件加速并不支持所有的2D画图的操作,这时开着它,可能会影响到你的自定义控件或者绘画,出现异常等行为,所以 android 对于硬件加速提供了可选性。如果你的应用执行了自定义的绘画,可以通过在真机上测试开启硬件加速查找问题。

(1)Application 级别

<application 
    android:hardwareAccelerated="false" 
</application>

(2)Activity 级别

<application 
    android:hardwareAccelerated="true">
    <activity ... />
    <activity android:hardwareAccelerated="false" />
</application>

(3)Window 级别

getWindow().setFlags(
   WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
   WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);

(4)View 级别

  myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

你可以关闭View级别的硬件加速,但是不能在View级别开启硬件加速,因为它还依赖其他的设置

两种获取是否支持硬件加速的方式

View.isHardwareAccelerated()   //returns true if the View is attached to a hardware accelerated window.
Canvas.isHardwareAccelerated() //returns true if the Canvas is hardware accelerated

如果必须进行这样的验证,建议在 draw 代码块中使用:Canvas.isHardwareAccelerated(),因为如果一个View被 attach 到一个硬件加速的 Window 上,即使没有硬件加速的 Canvas,它也是可以被绘制的。比如:将一个 View以bitmap 的形式进行缓存。

2 View 性能检测手段

2.1 检查 GPU 渲染速度和过度绘制

1、渲染速度检测

(1)启用分析器 开始前,请确保是运行 Android 4.1(API 级别 16)或更高版本的设备,并启用开发者选项。按以下步骤操作:

  • 在您的设备上,转到设置并点按开发者选项。
  • 在监控部分中,选择 GPU 渲染模式分析。
  • 在“GPU 渲染模式分析”对话框中,选择在屏幕上显示为竖条,以在设备的屏幕上叠加图形。

然后打开要分析的应用

4

检测时我们主要关注:红色、深绿色和浅绿色

  • 红色:表示 Android 的 2D 渲染程序向 OpenGL 发出绘制和重新绘制显示列表的命令所花的时间。此竖条的高度与执行每个显示列表所花的时间的总和成正比。显示列表越多,红色竖条就越高。

  • 深绿色:表示应用执行两个连续帧之间的操作所花的时间。它可能表示界面线程中进行的处理太多,而这些处理任务本可以分流到其他线程。

  • 浅绿色:表示在视图层次结构中 onLayout 和 onMeasure 回调上所花的时间。大区段表示处理视图层次结构需要很长时间。

(1)红色,意味着我们在界面上显示的 View 的数量越多,其中也包括层级,以及重绘的频繁程度,也就是 DisPlayList 的构建和重绘过程的时间会越长,所以红色的值越高,我们能够初步得出当前界面的渲染的流畅性,即红色线条越高,界面越卡。

(2)深绿色,两帧之间的时间间隔,由于渲染显示时根据 Vsync 信号,那么在两个 Vsync 信号之间 CPU 处理时间不能过长,过长可能导致丢帧。深绿色线条越高意味着在主线程的逻辑过多,容易导致丢帧卡顿现象。

(3)浅绿色线条,布局测量和布局过程所需要的时间,通过该线条可以看出层次结构可能存在不合理的地方,如层级嵌套过多等。

5

该界面上深绿色线条和浅绿色线条很高,意味着可能是 View 的数量过多或者 View 的布局嵌套可能不合理导致,初步分析布局嵌套有点多,可以进行层级上的优化,另外 RecyclerView 每次加载的数量也可以进行合理设置,同时其他一些优化手段也可以采取,如滚动预加载,多个页面复用 RecyclerView 的缓存池都可以一定程度上优化该页面的流畅性。

2.2 过度绘制检测

1、检查过度绘制 在开发者选项中设置过度绘制选项,向下滚动到硬件加速渲染部分,并选择调试 GPU 过度绘制。在调试 GPU 过度绘制对话框中,选择显示过度绘制区域。Android 将按如下方式为界面元素着色,以确定过度绘制的次数:

6

真彩色:没有过度绘制 蓝色:过度绘制 1 次 绿色:过度绘制 2 次 粉色:过度绘制 3 次 红色:过度绘制 4 次或更多次

在一些界面复杂的情况下,在界面可能看到粉色或者红色,此时已经处于过度绘制,所以需要进行适当的优化,加快渲染速度,减少过度绘制。

2、解决过度绘制

  • 移除布局中不需要的背景
  • 使视图层次结构扁平化
  • 降低透明度

(1)移除布局中不需要的背景 默认情况下,布局没有背景,这表示布局本身不会直接渲染任何内容。但是,当布局具有背景时,其有可能会导致过度绘制。移除不必要的背景可以快速提高渲染性能。不必要的背景可能永远不可见,因为它会被应用在该视图上绘制的任何其他内容完全覆盖。例如,当系统在父视图上绘制子视图时,可能会完全覆盖父视图的背景。

7

要查找过度绘制的原因,请在布局检查器工具中浏览层次结构。在浏览过程中,可以移除不需要的背景,因为它们对用户不可见。在许多容器采用同一种背景颜色的情况下,需要移除不需要的背景:或者可以将窗口背景设置为应用的主背景颜色,并且不为其上面的任何容器定义背景值。

使视图层次结构扁平化 借助先进的布局设计方法,您可以轻松对视图进行堆叠和分层,从而打造出精美的设计。但是,这样做会导致过度绘制,从而降低性能,特别是在每个堆叠视图对象都是不透明的情况下,这需要将可见和不可见的像素都绘制到屏幕上。

如果遇到这类问题,您可以通过优化视图层次结构来减少重叠界面对象的数量,从而提高性能。要详细了解如何实现此操作,请参阅优化视图层次结构。

降低透明度 在屏幕上渲染透明像素,即所谓的透明度渲染,是导致过度绘制的重要因素。在普通的过度绘制中,系统会在已绘制的现有像素上绘制不透明的像素,从而将其完全遮盖,与此不同的是,透明对象需要先绘制现有的像素,以便达到正确的混合效果。诸如透明动画、淡出和阴影之类的视觉效果都会涉及某种透明度,因此有可能导致严重的过度绘制。您可以通过减少要渲染的透明对象的数量,来改善这些情况下的过度绘制。例如,要获得灰色文本,您可以在 TextView 中绘制黑色文本,再为其设置半透明的透明度值。但是,您可以简单地通过用灰色绘制文本来获得同样的效果,而且能够大幅提升性能。

2.3 布局检测

1、lint :工具 Android Studio 的 Lint 工具可以帮助了解视图层次结构中的低效问题。要使用此工具,请依次选择 Analyze > Inspect Code,如图 所示。

8

Android > Lint > Performance 下面显示了有关各种布局项目的信息。要查看更多详情,可以点击各个项目将其展开,然后在屏幕右侧的窗格中会显示详细信息。

2、Layout Inspector

可以通过布局检查器,可以用在捕捉 App 在运行时的布局情况,并且在开发阶段用来做 UI 还原。

9

2.4 内存信息检测

1、 gfxinfo

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

adb shell dumpsys gfxinfo 包名

除了渲染的性能之外,gfxinfo 还可以拿到渲染相关的内存和 View hierarchy 信息。在 Android 6.0 之后,gxfinfo 命令新增了 framestats 参数,可以拿到最近 120 帧每个绘制阶段的耗时信息。

10

adb shell dumpsys gfxinfo 包名 framestats

通过这个命令可以实现自动化统计应用的帧率。

2、SurfaceFlinger

除了耗时,我们还比较关心渲染使用的内存。在 Android 4.1 以后每个 Surface 都会有三个 Graphic Buffer,那如何查看 Graphic Buffer 占用的内存,系统是怎么样管理这部分的内存的呢?可以通过下面的命令拿到系统 SurfaceFlinger 相关的信息:

adb shell dumpsys SurfaceFlinger

11

3 减少过度绘制实践

3.1 常用方式

1、移除背景色

  • 移除Window默认的Background
  • 移除XML布局文件中非必需的Background
  • 按需显示占位背景图片
  • 下面是 activity 的设置,同样 View 也可以设置。
    // 设置背景为 null
    getWindow().setBackGroundDrawable(null);
    

    小技巧:针对ListView或者 Recyclerview 中的 Avatar ImageView 的设置,在 getView 的代码里面,判断是否获取到对应的 Bitmap,在获取到 Avatar 的图像之后,把 ImageView 的 Background 设置为 Transparent,只有当图像没有获取到的时候才设置对应的 Background 占位图片,这样可以避免因为给 Avatar 设置背景图而导致的过度渲染。

    if (chat.getAuthor().getAvatarId() == 0) {
     Picasso.with(getContext()).load(android.R.color.transparent).into(chat_author_avatar);
     chat_author_avatar.setBackgroundColor(chat.getAuthor().getColor());
    } else {
     Picasso.with(getContext()).load(chat.getAuthor().getAvatarId()).into(
         chat_author_avatar);
     chat_author_avatar.setBackgroundColor(Color.TRANSPARENT);
    }
    

2、fragment 使用

在一个 activity 中可能存在多个 fragment,那么fragment 的重叠是否会导致过度绘制问题呢?首先一个 activity 中如果有多个 fragment,对于每个 fragment 的背景除非有特殊需求,否则一般不设置背景色,这样没有设备背景的部分就不会进行渲染。那么fragment 的尺寸大小如何使用呢?设置成刚好包裹子 View 的大小还是可以设置成 match_parent?这块其实可以根据需要来进行设置,因为如果 fragment 没有设置背景色,那么该 fragment 中没有子 View 的空间是不渲染的,所以这部分可以忽略,也就是怎么设置都可以!下面一张图可以看出,渲染部分是包含子 View 的部分,再次强调一下,前提是 fragment 没有设置背景色。

12

3、ClipRect & QuickReject

例如Nav Drawer从前置可见的Activity滑出之后,如果还继续绘制那些在Nav Drawer里面不可见的UI组件,这就导致了Overdraw。为了解决这个问题,Android系统会通过避免绘制那些完全不可见的组件来尽量减少Overdraw。那些Nav Drawer里面不可见的View就不会被执行浪费资源。

13

不可见区域,不需要绘制,和设置背景色其实是一个道理,界面上不可见的区域不需要绘制,因为绘制了也是一种浪费,反而影响性能。这种场景更多出现在我们进行自定义 view 时,使用 canvas.draw() 方法绘制区域需要合理设置,减少重叠部分的重复绘制。一种方式我们自己计算出绘制位置,另一种方式是使用 clipRect 方法,限制 canvas 的绘制区域,话句话说,也就是在 canvas 指定的区域进行绘制,超出区域的部分不会被绘制。注意在绘制之前需要保存 canvas 的状态,使用 canvas.save() 保存,canvas.restore() 进行恢复。

除了clipRect方法之外,我们还可以使用 canvas.quickreject() 来判断是否没和某个矩形相交,从而跳过那些非矩形区域内的绘制操作。

4、减少 UI 布局层次

  • 尽量扁平化,使用 等优化。优化 layout 的开销。尽量不使用 RelativeLayout 或者基于 weighted LinearLayout,它们 layout 的开销非常巨大。这里推荐使用 ConstraintLayout 替代 RelativeLayout 或者 weighted LinearLayout。
  • 使用 merge 和 include 等

4.参考

检查 GPU 渲染速度和过度绘制

Android性能优化之渲染篇

使用布局检查器调试布局

性能与视图层次结构

Android Project Butter分析

Android硬件加速原理与实现简介

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦