Android硬件加速(译文)

翻译自google官方文档:https://developer.android.com/guide/topics/graphics/hardware-accel

从Android3.0(API Level 11)开始,Android 2D渲染管道能够更好的支持硬件加速。硬件加速执行的所有的绘图操作都是使用GPU在View对象的画布上来进行的。因为启用硬件加速会增加资源的需求,因此这样的App会占用更多的内存。
硬件加速在target api >= 14的情况下,会默认开启,但是我们也可以显式地开启硬件加速。如果应用程序只使用标准的View和Drawable,那么打开全局硬件加速不会导致任何不良的绘制影响。然而,由于硬件加速并不支持所有的2D图形绘制操作,因此对于自定义View和绘制调用来说,打开全局硬件加速,可能会造成影响。带来的影响通常是,出现元素不可见、绘制异常、错误的像素渲染。为了避免这种问题,Android提供了多个级别的硬件加速操作(开启或者关闭),具体可见下文的硬件加速控制。
如果你的App执行了定制化的绘图,并且开启了硬件加速,那么就要在带有硬件加速的真机上测试,以便发现问题。下文中“不支持的绘制操作”部分描述了硬件加速的已知问题,以及如何解决它们。

硬件加速控制(Controlling Hardware Acceleration)

android提供了以下四个级别的硬件加速控制:

  • Application
  • Activity
  • Window
  • View

Application级别

在应用的Android清单文件中,把下列属性添加到<application>元素中,能够开启整个应用程序的硬件加速:

<application android:hardwareAccelerated="true" ...>

Activity级别

如果不能再应用程序级别全局打开硬件加速,那么也可以在Activity级别上进行控制。在<activity>元素中使用android:hardwareAccelerated属性,能够启用或禁止Activity级别的硬件加速。以下示例启用全局的硬件加速,但却禁止了一个Activity的硬件加速:

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

Window级别

如果需要更精细的控制,就可以使用下列代码来针对给定的Window来启用硬件加速:

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

注意:目前无法在Window级别禁止硬件加速。

View级别

在运行时,可以针对一个独立的View对象使用下列代码来禁止硬件加速:

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

注意:目前不能在View级别开启硬件加速。View Layer除了禁止硬件加速以外,还有其他的功能,更多的相关信息请看下文的View layer。

判断一个View对象是否被硬件加速

有些时候,知道当前的View对象(尤其是自定义View)是否被硬件加速对应用程序来说是非常有用的。如果应用程序做了很多定制的绘图操作,并且不是所有的操作都会被新的渲染管道所支持,那么这种判断就特别有用。
有两种不同的方法来检查应用程序是否被硬件加速了:

  • View.isHardwareAccelerated():如果View对象被绑定在硬件加速的Window,则返回true
  • Canvas.isHardwareAccelerated():Canvas对象被硬件加速,则返回true
    如果有必要在绘制代码中做这种检查,那么在可能的情况下,要使用Canvas.isHardwareAccelerated()方法来代替View.isHardwareAccelerated()方法。这是由于,当一个View对象跟一个被硬件加速的窗口绑定的时候,它依然能够使用使用一个非硬件加速的Canvas对象。例如,把一个View对象绘制到缓存中的一个位图时就会发生这种情况。

Android的绘制模式(Android Drawing Models)

当硬件加速启用时,Android Framework会采用一个新的绘图模式,这种模式利用DisplayList把应用程序渲染在屏幕上。充分理解DisplayList,以及它们是如何影响应用程序的,对于理解Android是如何绘制没有硬件加速的View对象也是有益的。
下面分别介绍基于软件和硬件加速的绘图模式。

纯软件绘制模式(Software-based drawing model)

在纯软件绘制模式中,View对象是通过以下两个步骤来绘制的:

  1. 让View树失效,标志脏区(Invalidate the hierarchy)
  2. 绘制View树(Draw the hierarchy)

无论何时,当应用程序需要更新它的UI部分时,它都会调用View#invalidate()(或者invalidate方法的相关变体)使UI内容改变。失效的消息请求会在View对象层次结构上进行一路传递,以便计算出需要重绘的屏幕区域(脏区)。然后,Android系统就会在View层次结构中绘制所有跟脏区相交的区域。
不幸的是,这种绘图模式有两个缺点:

  • 第一个问题,在每个绘图传递中,这种绘图模式都需要很多的代码执行。例如,如果应用程序调用了一个按钮的invalidate()方法,并且该按钮位于另一个View对象的上方,那么即使该View对象没有变化,那么Android系统也要重新绘制这个View对象。
  • 第二个问题是这个种绘图模式能够隐藏应用程序中的bug。因为当View对象跟脏区相交时,Android系统就会重新绘制它,所以即使没有调用View对象上的invalidate()方法,那么View对象内容的改变也可能会导致它被重绘。当发生这种情况时,就要依赖另一个被失效的View对象来获取正确的行为。这种行为能够改变每次你对应用程序的修改。因为这个原因,在修改影响View对象的绘图代码的数据和状态是,应该始终调用该定制View对象的invalidate()方法。

注意:在View对象的属性发生变化时,如背景色或TextView对象中的文本等,Android会自动的调用该View对象的invalidate()方法。

硬件加速绘制模式(Hardware accelerated drawing model)

这种模式下,Android系统依然会使用invalidate()和draw()来请求屏幕更新并且渲染View,但是实际的绘图操作与基于软件的绘图模式是不同的。它会立即执行绘图命令,Android系统把这些命令记录在内部的DisplayList中,这个列表包含了View对象层次结构的绘图代码的输出。另一个优化是:Android系统只需要针对由invalidate()方法调用所标记的View对象的脏区进行记录和更新显示列表。没有失效的View对象能够通过重新发布先前被记录的DispalyList来进行简单的重绘工作。
这种新的绘图模式包含三个阶段:

  1. 让View树失效(Invalidate the hierarchy)
  2. 记录和更新display lists(Record and update display lists)
  3. 绘制display lists(Draw the display lists)

使用这种模式,不能够依赖相交的脏区的View#draw()执行。要确保Android系统记录一个View对象的DisplayList,就必须调用invalidate()方法,如果忘记调用该方法,那么在变化发生后,View对象看上去会跟变化之前相同。
使用display list对提升动画的性能也是有好处的,因为设置特殊属性,诸如透明度、旋转等属性时,不需要请求目标View对象失效(系统会自动做这件事)。这种优化还适用于带有display list的View对象(应用程序被硬件加速时的任意View对象)。例如,假设有一个包含了一个Button对象的ListView对象的LinearLayout布局,那么LinearLayout布局的display list如下:

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

假设现在要改变ListView对象的透明度,那么在调用ListView对象的setAlpha(0.5f)方法后,display list就包含了以下处理:

  • SaveLayerAlpha(0.5);
  • DrawDisplayList(ListView);
  • Restore;
  • DrawDisplayList(Button).
    这里没有执行复杂的ListView对象的绘制代码。相反,系统只是比较简单的更新了LinearLayout对象的display list。在一个没有启用硬件加速的应用程序中,该列表(ListView)和它的父对象都会再次执行绘制代码。

不支持硬件加速的操作

在硬件加速的时候,2D渲染管道支持大多数的通常用于Canvas的绘图操作,以及一些很少使用的操作。被用于渲染应用程序的所有的绘图操作都有发送给Android系统,默认的Widget和布局,以及一些常用的可视效果,如反射和瓷砖的纹理效果都是被支持的。
下面给出一个链接——不同API级别上不支持的硬件加速的操作https://developer.android.com/guide/topics/graphics/hardware-accel#unsupported
如果你的App被硬件加速不支持的特性所影响,那么可以通过View#setLayerType(View.LAYER_TYPE_SOFTWARE, null)关闭硬件加速。通过这种方式,你的App仍然可以在其他地方享受硬件加速带来的好处。

View Layers

在所有Android版本中,通过使用View对象的绘图缓冲,或使用Canvas.saveLayer()方法,View都具有渲染到离屏(off-screen)缓冲区的能力。离屏缓冲区,或layers,有多种用途,在呈现复杂的动画或使用组合效果时,能够获得更好的性能。例如,使用Canvas.saveLayer()可以实现淡入淡出的效果,先暂时把一个View对象渲染在一个层中,然后把它和不透明因子合成到屏幕上。
从Android3.0(API Level 11)开始,在如何和什么时候使用View.setLayerType()问题上,Android提供了更多的控制。这个API携带两个参数:一个是层的类型,另一个是可选的Paint对象,这个对象描述层应该如何被合成的。使用这个Paint对象能够进行颜色过滤、特殊的混合模式、或者层的透明度。
View对象能够使用以下三种层类型:

  • LAYER_TYPE_NONE:View对象用普通的方式来渲染,并且不是由离屏缓存来返回的。这种类型是默认的行为。
  • LAYER_TYPE_HARDWARE:如果应用程序是硬件加速的,那么该View对象被渲染在硬件的一个硬件纹理(texture)中。如果没有开启硬件加速,那么这种层类型的行为与LAYER_TYPE_SOFTWARE相同。
  • LAYER_TYPE_SOFTWARE:View对象会被软件渲染在一个位图中。
    选择何种layer类型,取决于你的目标:
  • 性能:使用hardware layer类型,把View渲染到一个硬件纹理中。一旦该View对象被渲染到一个层中,那么它的绘图代码直到调用该View对象的invalidate()方法时才会被执行。对于某些动画,如alpha动画,就能够直接使用该层,这么做对于GPU来说是非常高效的。
  • 视觉效果:使用software layer或者hardware layer和一个Paint对象,能够把一些特殊的视觉处理应用给一个View对象。例如,使用ColorMatrixColorFilter对象绘制一个黑白相间的View对象。
  • 兼容性:使用software layer类型会强制把一个View对象渲染在软件中。如果被硬件加速的View对象(例如,如果整个应用程序都被硬件加速)发生渲染问题,那么使用软件层类型来解决硬件渲染管道的限制是一个简单的方法。

View layers and animations

当应用程序被硬件加速的时候,硬件层能够传递更快、更平滑的动画。当播放具有复杂的绘图操作的动画时,不总是能达到每秒60帧的速度。但是,可以通过使用硬件层把View对象渲染在硬件纹理中,缓解这种情况。硬件纹理能够被用于动画视图,这样在该View对象呈现动画时,就可以消除View对象所需要的重绘操作。除非该View对象的属性发生变化时(invalidate()方法被调用),它才会被重绘。如果在应用程序运行一个动画,并且没有获得想要的平滑结果,就要考虑在动画View上启用硬件层。
当一个View从硬件层被返回时,通过层方法处理的某些属性会被合成到屏幕上。因为它们不需要让View对象失效和重绘,所以设置这些属性是非常高效的。下面列出了影响层被合成的方式。调用这些属性设置器,会导致失效处理的优化,并且不会对目标View对象进行重绘:

  • alpha:改变层(layer)的透明度;
  • x,y,translation,translation:改变层的位置;
  • scaleX,scaleY:改变层的尺寸;
  • rotation,rotation,rotationY:改变3D空间中层的方向;
  • pivotX,pivotY:改变层的变换起源。
    这些属性是在用ObjectAnimator对象给View对象设置动画时所使用的名称。如果想要访问这些属性,就要调用相应的set或get方法。例如,要修改alpha属性,就要调用setAlpha()方法。下面的代码展示了在3D空间中围绕Y轴旋转View对象的最有效的方法:
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

因为硬件层会消耗显示内存,因此强烈推荐只在动画播放期间启用硬件层,并且在动画播放结束后就禁用该硬件层。能够使用动画监听器来完成这种操作:

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        view.setLayerType(View.LAYER_TYPE_NONE, null);
    }
});
animator.start();

更多的属性动画的信息,请看Property Animation.

使用建议

选择硬件加速来渲染2D图形可以有效改善性能,但是为了更有效的使用GPU,应该按照以下建议设计应用程序:

减少应用程序中View对象的数量

系统绘制越多的View对象,就会越慢。这种情况也适用于软件渲染管道。减少View对象数目是优化UI性能最有效的方式之一。

避免过度绘图

在彼此的顶部不要绘制太多的层。
移除那些完全被别的不透明View遮盖的View。
对于当前硬件的一个好的原则是,每帧的像素数不要大于屏幕上像素数的2.5倍(以位图的透明点阵数来计算)。

不要在绘图方法中创建渲染对象

一个常见的错误是每次调用渲染方法时创建一个新的Paint对象或Path对象。这样就会频繁地触发垃圾回收,导致硬件管道中的缓存和优化失效。

不要经常的编辑形状

对于复杂的形状,如路径和圆,是使用纹理掩码来呈现的。每次创建或修改路径,硬件通道都要创建一个新的纹理遮罩,这样会消耗大量的资源。

不要经常的编辑位图

每次改变位图内容,它都会被再次上传到GPU的纹理,以供下次绘制。

要小心的使用alpha相关的方法

当使用setAlpha()、 AlphaAnimation或ObjectAnimator,让一个View对象半透明时,需要双倍填充率来渲染到离屏缓存。当在一个大的View对象上应用透明效果时,要考虑把View对象的层类型设置为LAYER_TYPE_HARDWARE。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容