View 性能优化(一)

1 View 机制简单介绍

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

View 性能优化(一)_第1张图片

Android 图形系统的整体架构

View 性能优化(一)_第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 是不支持硬件加速的,所以设置硬件加速的开关。

View 性能优化(一)_第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 渲染模式分析”对话框中,选择在屏幕上显示为竖条,以在设备的屏幕上叠加图形。

然后打开要分析的应用

View 性能优化(一)_第4张图片

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

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

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

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

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

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

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

View 性能优化(一)_第5张图片

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

2.2 过度绘制检测

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

View 性能优化(一)_第6张图片

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

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

2、解决过度绘制

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

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

View 性能优化(一)_第7张图片

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

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

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

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

2.3 布局检测

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

View 性能优化(一)_第8张图片

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

2、Layout Inspector

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

View 性能优化(一)_第9张图片

2.4 内存信息检测

1、 gfxinfo

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

adb shell dumpsys gfxinfo 包名

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

View 性能优化(一)_第10张图片

adb shell dumpsys gfxinfo 包名 framestats

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

2、SurfaceFlinger

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

adb shell dumpsys SurfaceFlinger

View 性能优化(一)_第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 没有设置背景色。

View 性能优化(一)_第12张图片

3、ClipRect & QuickReject

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

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硬件加速原理与实现简介

你可能感兴趣的:(性能优化)