很多人会把 Android 中的硬件加速和 Hardware Layer 搞混,会以为启用了硬件加速,就是启用了 Hardware Layer. 所以在说 Hardware Layer 之前,我们先说一下硬件加速
关于硬件加速的比较详细的文章,推荐大家看这三篇
硬件加速,实际上应该叫 GPU 加速,软硬件加速的区别主要是图形的绘制究竟是 GPU 来处理还是 CPU,如果是 GPU,就认为是硬件加速绘制,反之,则是软件绘制
目前的 Android 版本, 默认情况下都是开了硬件加速的,如果你的 App 没有特殊声明,那么硬件加速就是默认开启的
上面三篇文章都有介绍,代码级别和原理级别都讲的比较深,这里我从 Systrace 的角度来给大家展示一下硬件加速下 App 的绘制与软件加速的区别
由于默认情况下就是硬件加速,所以我们以最常见的滑动桌面为例,看一下硬件加速情况下 App 在 Systrace 上的表现
硬件加速情况下,App 存在主线程和渲染线程,一帧的绘制是主线程和渲染线程一起配合执行的
我们把 Systrace 放大,来看每一帧主线程和渲染线程是怎么工作的,GPU 是什么时候介入工作,实现”加速”的
GPU 的真正介入是在 RenderThread 中的部分操作中
对应的,软件加速我们也找一个 App 来进行演示:云闪付
首先放一张全景图,可以看到软件渲染下,只有主线程,没有渲染线程,所有的渲染工作,都在主线程完成,同时可以看到,软件渲染下,每一帧的执行时间都非常长,超过1个 Vsync 周期,所以滑动的时候会一卡一卡的,非常难受 Systrace 下载
我们把 Systrace 放大,来看每一帧主线程是怎么工作的
通过上面的对比以及推荐的三篇文章的阅读,你应该对硬件渲染和软件渲染的区别了然于胸,这里总结一下
说完了硬件渲染,我们来说一下 Software Layer 和 Hardware Layer , 这两个概念主要是针对 View 的说的, 与此时 App 是硬件渲染还是软件渲染没有直接关系(但是有依赖关系,稍后会讲).
一个 View 的 layerType 共有三种状态( 后面的英文是官方文档,先读英文我再讲解):
默认情况下,所有的 View 都是这个 layerType,这种情况下,这个 View 不会做任何的特殊处理,该怎么走怎么走
Software layerType , 标识这个 View 有一个软件实现的 Layer ,怎么个软件实现法呢,实际上就是把这个 View,根据一定的条件,变成一个 Bitmap 对象
1 2 3 |
android/view/View.java Bitmap bitmap = createBitmap(mResources.getDisplayMetrics(), width, height, quality); |
Software layer 的作用如下
Hardware layerType ,标识这个 View 有一个硬件实现的 Layer ,通过第一小节我知道,这里的硬件指的是 GPU ,那么硬件实现的 Layer 顾名思义就是通过 GPU 来实现的,通常是OpenGL硬件上的帧缓冲对象或FBO(离屏渲染 Buffer)
注意:这里 Hardware layerType 是依赖硬件加速的,如果硬件加速开启,那么才会有 FBO 或者帧缓冲 ; 如果硬件加速关闭,那么就算你设置一个 View 的 LayerType 是 Hardware Layer ,也会按照 Software Layer 去做处理
而设置 Hardware Layer 对 alpha\translation \ scale \ rotation \ 这几个属性动画性能有帮助(同样的, 设置 Software Layer 也有相同的功效,下面的小例子环节会有详细的讲解),具体的使用如下
动画开始前,设置 LayerType 为 LAYER_TYPE_HARDWARE(代码为官方示例)
1 2 |
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator.ofFloat(view, "rotationY", 180).start(); |
动画结束的时候,重新设置为LAYER_TYPE_NONE(代码为官方示例)
1 2 3 4 5 6 7 8 9 |
view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator = ObjectAnimator.ofFloat(view, "rotationY", 180); .addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd( animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); .start(); |
由于 Hardware Layer 的特性,属性动画( alpha \ translation \ scale \ rotation \ )过程中只更新 View 的 property,不会每一帧都去销毁和重建 FBO,其动画性能会有很大的提升。当然这里要注意属性动画的过程中( 比如 AnimationUpdate 回调中),不要做除了上述属性更新之外的其他事情,比如添加删除子 View、修改 View 的显示内容等,这会使得 FBO 失效,性能反而变差
看 Trace 经常会有这样的情况出现 , 我们知道 Software layer 的生成过程本质上是生成一个 Bitmap Cache ,这个 Cache 的生成是很耗时的, 从下面的 Trace 也可以看出来,每一帧都比一个 Vsync 周期要长。
之所以下面的 Trace 每一帧都去调用了 buildDrawingCache/SW ,是因为每一帧的过程中,这个 View 的内容进行了更新,导致 Cache 失效,所以每一帧都去触发销毁 Cache 和重建 Cache,导致界面滑动卡顿
下面这个 Trace 是微信朋友圈的大图滑动情况 Trace 在 Github 上可以下载
放大来看,每一帧都在做 buildDrawingCache 操作,说明每一帧的缓存都失效了,在进行销毁和重建,性能极差,滑动的时候顿挫感非常严重
简单看一下 LAYER_TYPE_HARDWARE 的代码流程,详细的流程可以看上面推荐的文章
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
buildLayer -> buildDrawingCache -> buildDrawingCacheImpl public void buildLayer() { if (mLayerType == LAYER_TYPE_NONE) return; ...... switch (mLayerType) { case LAYER_TYPE_HARDWARE: // 硬件渲染 updateDisplayListIfDirty(); if (attachInfo.mThreadedRenderer != null && mRenderNode.isValid()) { attachInfo.mThreadedRenderer.buildLayer(mRenderNode); } break; case LAYER_TYPE_SOFTWARE: buildDrawingCache(true); // 软件渲染 break; } } |
其中 buildDrawingCache 的实现, 可以看到对应的 Trace 就是在这里打印的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public void buildDrawingCache(boolean autoScale) { if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ? mDrawingCache == null : mUnscaledDrawingCache == null)) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "buildDrawingCache/SW Layer for " + getClass().getSimpleName()); } try { buildDrawingCacheImpl(autoScale); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } } |
不正确使用 Hardware Layer 和不正确使用 Software Layer 会引起相同的性能问题,比如下面这个场景 (桌面打开文件夹),由于开发的实现问题,多个文件夹小图标都被设置了 Hardware LayerType , 导致 RenderThread 非常耗时,又因为每一帧其中的内容都在变,导致每一帧的 Hardware Layer 都失效,被销毁后重建,所以就有了下面的 Systrace 所展示的情况
我们可以在 设置 - 辅助功能 - 开发者选项 - 显示硬件层更新(Show hardware layers updates) 这个工具来追踪硬件层更新导致的性能问题 。
当 View 渲染 Hardware Layer 的时候整个界面会闪烁绿色,正常情况下,它应该在动画开始的时候闪烁一次(也就是 Layer 渲染初始化的时候),后续的动画不应该再有绿色出现;如果你的 View 在整个动画期间保持绿色不变,这就是持续的缓存失效问题了
查看 Systrace 也可以发现相同的问题, 两个工具可以一起使用,早些发现动画的性能问题。
为了说明上面所说的情况,我们用一个小例子来做示例,演示在各种情况下,其性能表现,代码非常简单(代码项目地址 :https://github.com/Gracker/Android_HardwareLayer_Example), 项目 Systrace 文件夹中包含此文章中涉及的所有例子(这都是好东西,值得收藏)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
//设置动画 animatorSet = new AnimatorSet(); objectAnimator1 = ObjectAnimator.ofFloat(animationText, View.TRANSLATION_X,150); objectAnimator2 = ObjectAnimator.ofFloat(animationText, View.ALPHA,0); objectAnimator3 = ObjectAnimator.ofFloat(animationText, View.TRANSLATION_Y,150); objectAnimator4 = ObjectAnimator.ofFloat(animationText, View.SCALE_X,150); objectAnimator5 = ObjectAnimator.ofFloat(animationText, View.SCALE_Y,150); animatorSet.playTogether(objectAnimator1,objectAnimator2,objectAnimator3,objectAnimator4,objectAnimator5); animatorSet.setDuration(500); //添加动画监听器 objectAnimator1.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_HARDWARE,null); // animationText.setLayerType(View.LAYER_TYPE_SOFTWARE,null); } @Override public void onAnimationEnd(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_NONE,null); } }); objectAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { // text.setText(String.format("%s%d", text.getText().toString(), i)); // i ++ ; } }); //开始动画 startText.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { animatorSet.start(); } }); |
为了得到准确的数据,我们使用 gfxinfo 得到的数据来进行对比( adb shell dumpsys gfxinfo)
gfxInfo 记录的是每一帧的耗时,我们重点看下面几个指标
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
AnimatorListener 和 AnimatorUpdateListener 都不重写, 如下,函数内的都注释掉 objectAnimator1.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { } }); objectAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { } }); |
可以看到有部分黄帧, 渲染线程中 flush commands 方法执行比较久Systrace 下载
可以看到 Janky Frames 比例为 46%,99th percentile: 32ms ,说明性能比较差,同时 Number High input latency = 30 说明主线程的负载是比较高的
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Total frames rendered: 30 Janky frames: 14 (46.67%) 50th percentile: 16ms 90th percentile: 29ms 95th percentile: 32ms 99th percentile: 32ms Number Missed Vsync: 0 Number High input latency: 30 Number Slow UI thread: 0 Number Slow bitmap uploads: 0 Number Slow issue draw commands: 0 Number Frame deadline missed: 0 HISTOGRAM: 5ms=0 6ms=0 7ms=0 8ms=0 9ms=0 10ms=2 11ms=2 12ms=5 13ms=1 14ms=2 15ms=1 16ms=3 17ms=2 18ms=0 19ms=0 20ms=0 21ms=1 22ms=1 23ms=1 24ms=1 25ms=2 26ms=2 27ms=0 28ms=1 29ms=1 30ms=0 31ms=0 32ms=2 34ms=0 36ms=0 38ms=0 40ms=0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
objectAnimator1.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_SOFTWARE,null); } @Override public void onAnimationEnd(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_NONE,null); i = 0; } }); objectAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { } }); |
第一帧执行 buildDrawingCache/SW Layer for AppCompatTextView ,后续的属性动画中,都没有在执行这个方法,可以看到动画过程中所有的帧都是绿色,说明性能很好Systrace 下载
可以看到 Janky Frames 比例为 3%,99th percentile: 16ms ,说明性能非常好,同时 Number High input latency = 0 说明主线程的负载是比较低的
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Total frames rendered: 31 Janky frames: 1 (3.23%) 50th percentile: 9ms 90th percentile: 12ms 95th percentile: 16ms 99th percentile: 16ms Number Missed Vsync: 0 Number High input latency: 0 Number Slow UI thread: 1 Number Slow bitmap uploads: 0 Number Slow issue draw commands: 0 Number Frame deadline missed: 1 HISTOGRAM: 5ms=2 6ms=2 7ms=4 8ms=3 9ms=5 10ms=11 11ms=0 12ms=1 13ms=0 14ms=1 15ms=0 16ms=2 17ms=0 18ms=0 19ms=0 20ms=0 21ms=0 22ms=0 23ms=0 24ms=0 25ms=0 26ms=0 27ms=0 28ms=0 29ms=0 30ms=0 31ms=0 32ms=0 34ms=0 36ms=0 38ms=0 40ms=0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
objectAnimator1.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_HARDWARE,null); } @Override public void onAnimationEnd(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_NONE,null); } }); objectAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { } }); |
可以看到,动画过程全是绿帧,性能非常好Systrace 下载
可以看到 Janky Frames 比例为 0%,99th percentile: 14ms ,说明性能非常好,同时 Number High input latency = 0 说明主线程的负载是非常低的
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Total frames rendered: 31 Janky frames: 0 (0.00%) 50th percentile: 7ms 90th percentile: 9ms 95th percentile: 12ms 99th percentile: 14ms Number Missed Vsync: 0 Number High input latency: 0 Number Slow UI thread: 0 Number Slow bitmap uploads: 0 Number Slow issue draw commands: 0 Number Frame deadline missed: 0 HISTOGRAM: 5ms=3 6ms=2 7ms=15 8ms=7 9ms=2 10ms=0 11ms=0 12ms=1 13ms=0 14ms=1 15ms=0 16ms=0 17ms=0 18ms=0 19ms=0 20ms=0 21ms=0 22ms=0 23ms=0 24ms=0 25ms=0 26ms=0 27ms=0 28ms=0 29ms=0 30ms=0 31ms=0 32ms=0 34ms=0 36ms=0 38ms=0 40ms=0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
objectAnimator1.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { } @Override public void onAnimationEnd(Animator animator) { i = 0; } }); objectAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { animationText.setText(String.format("%s%d", animationText.getText().toString(), i)); i ++ ; } }); |
可以看到动画过程中有部分黄帧,部分帧的 Animation、measure、layout、draw 比较耗时Systrace 下载
可以看到 Janky Frames 比例为 38%,99th percentile: 29ms ,说明性能比较差,同时 Number High input latency = 31 说明主线程的负载是比较高的
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Total frames rendered: 31 Janky frames: 12 (38.71%) 50th percentile: 14ms 90th percentile: 25ms 95th percentile: 29ms 99th percentile: 29ms Number Missed Vsync: 0 Number High input latency: 31 Number Slow UI thread: 0 Number Slow bitmap uploads: 0 Number Slow issue draw commands: 0 Number Frame deadline missed: 0 HISTOGRAM: 5ms=0 6ms=0 7ms=1 8ms=2 9ms=4 10ms=1 11ms=1 12ms=4 13ms=2 14ms=1 15ms=2 16ms=2 17ms=1 18ms=2 19ms=0 20ms=2 21ms=1 22ms=0 23ms=0 24ms=1 25ms=1 26ms=1 27ms=0 28ms=0 29ms=2 30ms=0 31ms=0 32ms=0 34ms=0 36ms=0 38ms=0 40ms=0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
objectAnimator1.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_SOFTWARE,null); } @Override public void onAnimationEnd(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_NONE,null); i = 0; } }); objectAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { animationText.setText(String.format("%s%d", animationText.getText().toString(), i)); i ++ ; } }); |
由于每一帧都在更新内容,所以每次 buildDrawingCache 生成的 Bitmap 都会被销毁和重建,此时的瓶颈都在主线程中,由于 buildDrawingCache 每一帧都执行,导致 Animation 和 Draw 的执行时间都很长Systrace 下载
可以看到 Janky Frames 比例为 41%,99th percentile: 32ms ,说明性能比较差,同时 Number High input latency = 18 说明主线程的负载是比较高的
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Total frames rendered: 29 Janky frames: 12 (41.38%) 50th percentile: 14ms 90th percentile: 30ms 95th percentile: 31ms 99th percentile: 32ms Number Missed Vsync: 0 Number High input latency: 18 Number Slow UI thread: 4 Number Slow bitmap uploads: 0 Number Slow issue draw commands: 0 Number Frame deadline missed: 4 HISTOGRAM: 5ms=0 6ms=1 7ms=0 8ms=0 9ms=0 10ms=1 11ms=6 12ms=3 13ms=2 14ms=3 15ms=0 16ms=1 17ms=1 18ms=1 19ms=2 20ms=0 21ms=1 22ms=0 23ms=0 24ms=2 25ms=1 26ms=0 27ms=1 28ms=0 29ms=0 30ms=1 31ms=1 32ms=1 34ms=0 36ms=0 38ms=0 40ms=0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
objectAnimator1.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_HARDWARE,null); } @Override public void onAnimationEnd(Animator animator) { animationText.setLayerType(View.LAYER_TYPE_NONE,null); i = 0; } }); objectAnimator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { animationText.setText(String.format("%s%d", animationText.getText().toString(), i)); i ++ ; } }); |
与 Software Layer 情况类似,由于每一帧都在更新内容,所以每次 drawLayer 生成的 Buffer 都会被销毁和重建,此时的瓶颈都在主线程 + 渲染线程中,由于每一帧内容更新和 Buffer 销毁重建,导致主线程和渲染线程执行时间都很长,性能比较差Systrace 下载
可以看到 Janky Frames 比例为 46%,99th percentile: 32ms ,说明性能比较差,同时 Number High input latency = 30 说明主线程的负载是比较高的
1 2 3 4 5 6 7 8 9 10 11 12 13 |
Total frames rendered: 30 Janky frames: 14 (46.67%) 50th percentile: 16ms 90th percentile: 29ms 95th percentile: 32ms 99th percentile: 32ms Number Missed Vsync: 0 Number High input latency: 30 Number Slow UI thread: 0 Number Slow bitmap uploads: 0 Number Slow issue draw commands: 0 Number Frame deadline missed: 0 HISTOGRAM: 5ms=0 6ms=0 7ms=0 8ms=0 9ms=0 10ms=2 11ms=2 12ms=5 13ms=1 14ms=2 15ms=1 16ms=3 17ms=2 18ms=0 19ms=0 20ms=0 21ms=1 22ms=1 23ms=1 24ms=1 25ms=2 26ms=2 27ms=0 28ms=1 29ms=1 30ms=0 31ms=0 32ms=2 34ms=0 36ms=0 38ms=0 40ms=0 |
从上面的六个案例可以看到,相同的动画,在不同的 LayerType 之下,其性能表现差别很大,这还只是简单的属性动画,如果碰到更加复杂的动画,性能差别会更大。
我们对上面几个案例和表现出来的性能数据做一下简单的总结:
既然读完了,如果有什么想法可以留言沟通,也可以扫文章下面的微信二维码加好友一起讨论;如有疏漏或者错误的地方,辛苦大家告知一下,我尽早更新以免误导他人;如果觉得有用,也请把这篇文章分享给其他人.
由于博客留言交流不方便,点赞或者交流,可以移步本文的知乎界面
知乎 - Android 中的 Hardware Layer 详解
小厂系统研发工程师 , 更多信息可以点击 关于我 , 非常希望和大家一起交流 , 共同进步 .
一个人可以走的更快 , 一群人可以走的更远
原文作者:Gracker
原文链接:https://androidperformance.com/2019/07/27/Android-Hardware-Layer/
发表日期:July 27th 2019, 2:09:13 pm
更新日期:November 4th 2019, 10:42:25 pm
版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可