硬件加速与软件加速
很多人会把 Android 中的硬件加速和 Hardware Layer 搞混,会以为启用了硬件加速,就是启用了 Hardware Layer. 所以在说 Hardware Layer 之前,我们先说一下硬件加速
关于硬件加速的比较详细的文章,推荐大家看这三篇
硬件加速,实际上应该叫 GPU 加速,软硬件加速的区别主要是图形的绘制究竟是 GPU 来处理还是 CPU,如果是 GPU,就认为是硬件加速绘制,反之,则是软件绘制
目前的 Android 版本, 默认情况下都是开了硬件加速的,如果你的 App 没有特殊声明,那么硬件加速就是默认开启的
上面三篇文章都有介绍,代码级别和原理级别都讲的比较深,这里我从 Systrace 的角度来给大家展示一下硬件加速下 App 的绘制与软件加速的区别
硬件加速 App 的表现
由于默认情况下就是硬件加速,所以我们以最常见的滑动桌面为例,看一下硬件加速情况下 App 在 Systrace 上的表现
硬件加速情况下,App 存在主线程和渲染线程,一帧的绘制是主线程和渲染线程一起配合执行的
我们把 Systrace 放大,来看每一帧主线程和渲染线程是怎么工作的,GPU 是什么时候介入工作,实现”加速”的
GPU 的真正介入是在 RenderThread 中的部分操作中
软件加速 App 的表现
对应的,软件加速我们也找一个 App 来进行演示:云闪付
首先放一张全景图,可以看到软件渲染下,只有主线程,没有渲染线程,所有的渲染工作,都在主线程完成,同时可以看到,软件渲染下,每一帧的执行时间都非常长,超过1个 Vsync 周期,所以滑动的时候会一卡一卡的,非常难受 Systrace 下载
我们把 Systrace 放大,来看每一帧主线程是怎么工作的
总结
通过上面的对比以及推荐的三篇文章的阅读,你应该对硬件渲染和软件渲染的区别了然于胸,这里总结一下
- 硬件渲染情况下,app 存在主线程和渲染线程;软件渲染情况下, app 只有主线程没有渲染线程
- 硬件渲染情况下,app 最终绘制是借助 GPU 来实现 ;软件渲染情况下, app 最终绘制是使用 CPU 来实现(调用 skia 库)
- 硬件渲染情况下,App 的性能是要优于软件渲染的
- 由于部分 api 硬件渲染不支持,所以只能是要软件渲染,做 App 开发的时候,应该尽量避免使用此类 Api(支持情况可以直接在 Android 官方文档里面查看 :https://developer.android.google.cn/guide/topics/graphics/hardware-accel)
Software Layer VS Hardware Layer
说完了硬件渲染,我们来说一下 Software Layer 和 Hardware Layer , 这两个概念主要是针对 View 的说的, 与此时 App 是硬件渲染还是软件渲染没有直接关系(但是有依赖关系,稍后会讲).
一个 View 的 layerType 共有三种状态( 后面的英文是官方文档,先读英文我再讲解):
- LAYER_TYPE_NONE : Indicates that the view does not have a layer.
- LAYER_TYPE_SOFTWARE :Indicates that the view has a software layer. A software layer is backed by a and causes the view to be rendered using Android’s software rendering pipeline, even if hardware acceleration is enabled
- LAYER_TYPE_HARDWARE :Indicates that the view has a hardware layer. A hardware layer is backed by a hardware specific texture (generally Frame Buffer Objects or FBO on OpenGL hardware) and causes the view to be rendered using Android’s hardware rendering pipeline, but only if hardware acceleration is turned on for the view hierarchy. When hardware acceleration is turned off, hardware layers behave exactly as LAYER_TYPE_SOFTWARE
LAYER_TYPE_NONE
默认情况下,所有的 View 都是这个 layerType,这种情况下,这个 View 不会做任何的特殊处理,该怎么走怎么走
LAYER_TYPE_SOFTWARE
Software layerType , 标识这个 View 有一个软件实现的 Layer ,怎么个软件实现法呢,实际上就是把这个 View,根据一定的条件,变成一个 Bitmap 对象
1 | android/view/View.java |
Software layer 的作用如下
- When the application is not using hardware acceleration, a software layer is useful to apply a specific color filter and/or blending mode and/or to a view and all its children.(当应用程序不使用硬件加速时,Software layer 可用于将特定的颜色过滤器、混合模式或半透明应用于 View 及其所有子 View)
- When the application is using hardware acceleration, a software layer is useful to render drawing primitives not supported by the hardware accelerated pipeline. It can also be used to cache a complex view tree into a texture and reduce the complexity of drawing operations. For instance, when a complex view tree with a translation, a software layer can be used to render the view tree only once.(当应用程序使用硬件加速时,软件层可用于呈现硬件加速管道不支持的绘图基元。 它还可用于将复杂视图树缓存到纹理中,并降低绘制操作的复杂性。 例如,在使用转换动画复杂视图树时,可以使用软件层仅渲染视图树一次)
- Software layers should be avoided when the affected view tree updates often. Every update will require to re-render the software layer, which can potentially be slow (particularly when hardware acceleration is turned on since the layer will have to be uploaded into a hardware texture after every update(当受影响的视图树经常更新时,应避免使用软件层。 每次更新都需要重新渲染软件层,这可能会很慢(特别是在打开硬件加速时,因为每次更新后都必须将图层上传到硬件纹理中)
LAYER_TYPE_HARDWARE
Hardware layerType ,标识这个 View 有一个硬件实现的 Layer ,通过第一小节我知道,这里的硬件指的是 GPU ,那么硬件实现的 Layer 顾名思义就是通过 GPU 来实现的,通常是OpenGL硬件上的帧缓冲对象或FBO(离屏渲染 Buffer)
注意:这里 Hardware layerType 是依赖硬件加速的,如果硬件加速开启,那么才会有 FBO 或者帧缓冲 ; 如果硬件加速关闭,那么就算你设置一个 View 的 LayerType 是 Hardware Layer ,也会按照 Software Layer 去做处理
Hardware layer 的作用:
- A hardware layer is useful to apply a specific color filter and/or blending mode and/or to a view and all its children.(硬件层可用于将特定颜色过滤器和/或混合模式和/或半透明应用于视图及其所有子视图
- A hardware layer can be used to cache a complex view tree into a texture and reduce the complexity of drawing operations. For instance, when a complex view tree with a translation, a hardware layer can be used to render the view tree only once.(hardware layer 可用于将复杂视图树缓存到纹理中,并降低绘制操作的复杂性。 例如,在使用转换动画复杂视图树时,可以使用硬件层仅渲染视图树一次,这个是最主要的一个点)
- A hardware layer can also be used to increase the rendering quality when rotation transformations are applied on a view. It can also be used to prevent potential issues when applying 3D transforms on a view (在视图上应用旋转变换时,还可以使用硬件层来提高渲染质量。 它还可用于在视图上应用3D变换时防止潜在的剪切问题)
而设置 Hardware Layer 对 alpha\translation \ scale \ rotation \ 这几个属性动画性能有帮助(同样的, 设置 Software Layer 也有相同的功效,下面的小例子环节会有详细的讲解),具体的使用如下
动画开始前,设置 LayerType 为 LAYER_TYPE_HARDWARE(代码为官方示例)
1 | view.setLayerType(View.LAYER_TYPE_HARDWARE, null); |
动画结束的时候,重新设置为LAYER_TYPE_NONE(代码为官方示例)
1 | view.setLayerType(View.LAYER_TYPE_HARDWARE, null); |
由于 Hardware Layer 的特性,属性动画( alpha \ translation \ scale \ rotation \ )过程中只更新 View 的 property,不会每一帧都去销毁和重建 FBO,其动画性能会有很大的提升。当然这里要注意属性动画的过程中( 比如 AnimationUpdate 回调中),不要做除了上述属性更新之外的其他事情,比如添加删除子 View、修改 View 的显示内容等,这会使得 FBO 失效,性能反而变差
总结
- 从上面对 Hardware Layer 和 Software Layer 的描述可以看到,
Software Layer 是对 Hardware Layer 的一个补充,如果 App 处于某种情况不能使用 Hardware Layer ,那么 Software Layer 就会派上用场 。 Hardware Layer 不支持的 API 的实现也得用 Software Layer 来实现 - Software Layer 和 Hardware Layer 都可以对 View 进行操作,比如颜色过滤器、混合模式等
- Software Layer 和 Hardware Layer 对 alpha \ translation \ scale \ rotation \ pivot 这几个属性动画性能有帮助,这也是 Software Layer 和 Hardware Layer 使用最频繁的优化 (也就是我们常说的 : 在做上述动画的时候,在动画开始前,将这个 View 的 LayerType 设置为 LAYER_TYPE_HARDWARE ,在动画结束后,将 layerType 重新设置为 LAYER_TYPE_NONE , 设置回来的原因是 Hardware Layer 使用的是 Video Memory,设置为 NONE 之后这部分使用的内存将会回收 )
不正确使用 LayerType 导致的性能问题案例
不正确使用 Software layer 引起的性能问题
看 Trace 经常会有这样的情况出现 , 我们知道 Software layer 的生成过程本质上是生成一个 Bitmap Cache ,这个 Cache 的生成是很耗时的, 从下面的 Trace 也可以看出来,每一帧都比一个 Vsync 周期要长。
之所以下面的 Trace 每一帧都去调用了 buildDrawingCache/SW ,是因为每一帧的过程中,这个 View 的内容进行了更新,导致 Cache 失效,所以每一帧都去触发销毁 Cache 和重建 Cache,导致界面滑动卡顿
下面这个 Trace 是微信朋友圈的大图滑动情况 Trace 在 Github 上可以下载
放大来看,每一帧都在做 buildDrawingCache 操作,说明每一帧的缓存都失效了,在进行销毁和重建,性能极差,滑动的时候顿挫感非常严重
代码流程
简单看一下 LAYER_TYPE_HARDWARE 的代码流程,详细的流程可以看上面推荐的文章
1 | buildLayer -> buildDrawingCache -> buildDrawingCacheImpl |
其中 buildDrawingCache 的实现, 可以看到对应的 Trace 就是在这里打印的:
1 | public void buildDrawingCache(boolean autoScale) { |
不正确使用 Hardware layer 引起的性能问题
不正确使用 Hardware Layer 和不正确使用 Software Layer 会引起相同的性能问题,比如下面这个场景 (桌面打开文件夹),由于开发的实现问题,多个文件夹小图标都被设置了 Hardware LayerType , 导致 RenderThread 非常耗时,又因为每一帧其中的内容都在变,导致每一帧的 Hardware Layer 都失效,被销毁后重建,所以就有了下面的 Systrace 所展示的情况
我们放大 RenderThread 的一帧来看
Debug 工具
我们可以在 设置 - 辅助功能 - 开发者选项 - 显示硬件层更新(Show hardware layers updates) 这个工具来追踪硬件层更新导致的性能问题 。
当 View 渲染 Hardware Layer 的时候整个界面会闪烁绿色,正常情况下,它应该在动画开始的时候闪烁一次(也就是 Layer 渲染初始化的时候),后续的动画不应该再有绿色出现;如果你的 View 在整个动画期间保持绿色不变,这就是持续的缓存失效问题了
查看 Systrace 也可以发现相同的问题, 两个工具可以一起使用,早些发现动画的性能问题。
总结
- 记住 LayerType 使用的场景:View 做 alpha \ translation \ scale \ rotation \ 这几个属性动画
- 做动画的时候,如果可以,尽量多使用 Hardware Layer ,使用完成后记得设置为 None,除非有硬件层不支持的 api,才去考虑使用 Software Layer
- 如果是使用 setAlpha(), AlphaAnimation, or ObjectAnimator 来设置 View 的透明度的话,默认就会走 off-screen buffer , 所以如果你操作的 View 比较大的话,也可以把这个 View 的 Type 设置为 LAYER_TYPE_HARDWARE(官方建议)
- 在某些情况下,实际上 Hardware Layer 可能要做非常多的工作,而不仅仅是渲染视图。缓存一个层需要花费时间,因为这一步要划分为两个过程:首先,视图渲染入 GPU 上的一个层中,然后,GPU 再渲染那个层到窗口,如果 View 的渲染十分简单(比如一个纯色),那么在初始化的时候设置 Hardware Layer 可能增加不必要的开销
- 对所有缓存来讲,存在一个缓存失效的可能性。动画运行时,如果某个地方调用了View.invalidate( ),那么 Layer 就不得不重新渲染一遍。倘若不断地失效,你的Hardware Layer 实际上要比不添加任何 Layer 性能更差(下面的例子可以佐证),因为Hardware Layer 在设置缓存的时候增加了开销。如果你不断的重缓存 Layer,会对性能造成极大地负担(做动画的 View 越复杂,带来的负担就越重)
LayerType 对动画性能的影响示例
示例
代码
为了说明上面所说的情况,我们用一个小例子来做示例,演示在各种情况下,其性能表现,代码非常简单(代码项目地址 :https://github.com/Gracker/Android_HardwareLayer_Example), 项目 Systrace 文件夹中包含此文章中涉及的所有例子(这都是好东西,值得收藏)
- 两个 TextView ,一个负责开始动画,一个负责做动画
- 动画类型有 TRANSLATION_X 、ALPHA、TRANSLATION_Y、SCALE_X、SCALE_Y
- 我们会控制 AnimatorListener 和 AnimatorUpdateListener ,使得动画的实现不一样
- onAnimationStart 和 onAnimationEnd 中主要是设置是否启用 LAYER_TYPE_HARDWARE 或者 LAYER_TYPE_SOFTWARE
- onAnimationUpdate 主要演示如果在动画过程中改变了 View 的内容,会造成什么影响
1 | //设置动画 |
统计工具:GFXInfo
为了得到准确的数据,我们使用 gfxinfo 得到的数据来进行对比( adb shell dumpsys gfxinfo)
gfxInfo 记录的是每一帧的耗时,我们重点看下面几个指标
- Janky Frames :超过 16 ms 的帧数 (超过 16 ms 不一定会卡顿,但是会增加卡顿情况出现的风险)
- 耗时帧统计:可以看到大部分帧的区间,以及最大耗时
案例一:Normal Layer + 不动态更新 View 内容
代码
1 | AnimatorListener 和 AnimatorUpdateListener 都不重写, 如下,函数内的都注释掉 |
Systrace 现象
可以看到有部分黄帧, 渲染线程中 flush commands 方法执行比较久Systrace 下载
gfxInfo 数据
可以看到 Janky Frames 比例为 46%,99th percentile: 32ms ,说明性能比较差,同时 Number High input latency = 30 说明主线程的负载是比较高的
1 | Total frames rendered: 30 |
案例二:Software Layer + 不动态更新 View 内容
代码
1 | objectAnimator1.addListener(new Animator.AnimatorListener() { |
Systrace 现象
第一帧执行 buildDrawingCache/SW Layer for AppCompatTextView ,后续的属性动画中,都没有在执行这个方法,可以看到动画过程中所有的帧都是绿色,说明性能很好Systrace 下载
gfxInfo 数据
可以看到 Janky Frames 比例为 3%,99th percentile: 16ms ,说明性能非常好,同时 Number High input latency = 0 说明主线程的负载是比较低的
1 | Total frames rendered: 31 |
案例三:Hardware Layer + 不动态更新 View 内容
代码
1 | objectAnimator1.addListener(new Animator.AnimatorListener() { |
Systrace 现象
可以看到,动画过程全是绿帧,性能非常好Systrace 下载
gfxInfo 数据
可以看到 Janky Frames 比例为 0%,99th percentile: 14ms ,说明性能非常好,同时 Number High input latency = 0 说明主线程的负载是非常低的
1 | Total frames rendered: 31 |
案例四:Normal Layer + 动态更新 View 内容
代码
1 | objectAnimator1.addListener(new Animator.AnimatorListener() { |
Systrace 现象
可以看到动画过程中有部分黄帧,部分帧的 Animation、measure、layout、draw 比较耗时Systrace 下载
gfxInfo 数据
可以看到 Janky Frames 比例为 38%,99th percentile: 29ms ,说明性能比较差,同时 Number High input latency = 31 说明主线程的负载是比较高的
1 | Total frames rendered: 31 |
案例五:Software Layer + 动态更新 View 内容
代码
1 | objectAnimator1.addListener(new Animator.AnimatorListener() { |
Systrace 现象
由于每一帧都在更新内容,所以每次 buildDrawingCache 生成的 Bitmap 都会被销毁和重建,此时的瓶颈都在主线程中,由于 buildDrawingCache 每一帧都执行,导致 Animation 和 Draw 的执行时间都很长Systrace 下载
gfxInfo 数据
可以看到 Janky Frames 比例为 41%,99th percentile: 32ms ,说明性能比较差,同时 Number High input latency = 18 说明主线程的负载是比较高的
1 | Total frames rendered: 29 |
案例六:Hardware Layer + 动态更新 View 内容
代码
1 | objectAnimator1.addListener(new Animator.AnimatorListener() { |
Systrace 现象
与 Software Layer 情况类似,由于每一帧都在更新内容,所以每次 drawLayer 生成的 Buffer 都会被销毁和重建,此时的瓶颈都在主线程 + 渲染线程中,由于每一帧内容更新和 Buffer 销毁重建,导致主线程和渲染线程执行时间都很长,性能比较差Systrace 下载
gfxInfo 数据
可以看到 Janky Frames 比例为 46%,99th percentile: 32ms ,说明性能比较差,同时 Number High input latency = 30 说明主线程的负载是比较高的
1 | Total frames rendered: 30 |
总结
从上面的六个案例可以看到,相同的动画,在不同的 LayerType 之下,其性能表现差别很大,这还只是简单的属性动画,如果碰到更加复杂的动画,性能差别会更大。
我们对上面几个案例和表现出来的性能数据做一下简单的总结:
- 如果只是单纯的做动画,不动态修改 View 的内容,那么性能表现为 :Hardware Layer >= Software Layer > Normal Layer
- 如果做动画同时动态修改 View 的内容,那么性能表现为 :Normal Layer > Software Layer = Hardware Layer
- Hardware Layer 对动画性能确实有很大的提升,但是如果你用不好,那么还不如不用
- 如果通过 Systrace 发现你做动画的时候每一帧都在 buildDrawingCache/SW(主线程) 或者 buildLayer(渲染线程),那么请查看你的代码的逻辑
- 有些情况下是由于系统的原因,比如图片比 Cache 大,invalidate 逻辑问题,可以联系手机厂商进行一起修改
既然读完了,如果有什么想法可以留言沟通,也可以扫文章下面的微信二维码加好友一起讨论;如有疏漏或者错误的地方,辛苦大家告知一下,我尽早更新以免误导他人;如果觉得有用,也请把这篇文章分享给其他人.
本文知乎地址
由于博客留言交流不方便,点赞或者交流,可以移步本文的知乎界面
知乎 - Android 中的 Hardware Layer 详解
关于我 && 博客
下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!
- 博主个人介绍 :里面有个人的微信和微信群链接。
- 本博客内容导航 :个人博客内容的一个导航。
- 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
- Android性能优化知识星球 : 欢迎加入,多谢支持~
一个人可以走的更快 , 一群人可以走的更远