硬件加速

原文链接:https://developer.android.com/guide/topics/graphics/hardware-accel.html

从Android 3.0(API级别 11)开始,Android 2D渲染管道支持硬件加速,这意味着在View画布(Canvas)上执行的所有绘图操作都使用GPU。因为开启硬件加速会有额外的资源消耗,app可能会占用更多的内存。

硬件加速默认在API级别14(含)以上开启,也可以显式开启。如果你的应用仅仅使用一些标准的View和Drawable,则全局打开硬件加速也不会产生任何不利的绘图效果。但是,因为硬件加速并不支持所有的2D绘图操作,因此开启硬件加速可能会影响一些自定义View或绘图调用。问题通常表现为不可见元素、异常或者错误渲染的像素。为了解决这个问题,Android提供了在多个级别上开启或禁用硬件加速的选项。

如果你的应用做了一些自定义绘制,请在开启硬件加速的真机上测试应用来发现问题。

控制硬件加速

可以在四个级别上控制硬件加速:

  • Application
  • Activity
  • Window
  • View

Application级别

<application>标签的hardwareAccelerated属性为整个应用打开或者关闭硬件加速。优先级最低

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

Activity级别

可以对Activity进行单独控制。优先级高于Application级别。

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

Window级别

如果需要更细致的控制,可以对给定Window开启硬件加速。注意:不支持在Window级别关闭硬件加速。

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

View级别

可以在运行时对单个View关闭硬件加速:

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

注意:当前不能在View级别开启硬件加速。设置View Layer除了禁用硬件加速外还有别的功能。

判断View是否开启了硬件加速

有两种不同方式检查是否应用开启了硬件加速:

  • View.isHardwareAccelerated()返回true如果View附着到了开启硬件加速的Window上
  • Canvas.isHardwareAccelerated()返回true如果Canvas是硬件加速的

如果你自定义View进行绘制的时候需要做检查的话,尽可能优先使用Canvas.isHardwareAccelerated()。因为,当View附着到开启了硬件加速的Window时,它仍然可能使用非硬件加速的Canvas。比如,为了缓存目的,将View绘制到Bitmap时会发生这种情况。

Android绘制模型

当硬件加速开启时,Android框架将采用新的绘制模型,其利用显示列表将应用程序呈现在屏幕上。为了完全理解显示列表(display lists)以及它们如何影响应用程序,理解Android如何在没有硬件加速的模式下绘制View是非常重要的。

基于软件的绘制模型

在软件绘制模型中,View通过下面的两步绘制出来:

  • 使层级失效(invalidate)
  • 绘制层级

每当应用程序需要更新部分UI时,它会在任何更改了内容的View上调用invalidate()(或等效方法)。失效消息在View层级中一路传递,来达到计算出需要重绘的屏幕区域(即脏区域)的目的。Android系统然后重绘在View层级中与脏区域相交的任何View。不幸的是,在这种绘图模型中有两个缺点:

  • 第一,这个模型在每次绘制传递的时候需要执行大量代码。比如,如果你的应用程序对一个Button调用invalidate(),而那个Button覆盖在另一个View之上,则Android系统将会重绘这个View,即使它什么也没变。
  • 第二个问题是绘制模型可能会隐藏一些你应用的bug。因为Android系统会重绘那些与脏区域相交的View,一个你已经改变了内容的View可能会被重绘,即使在其invalidate()方法并没有被调用。当发生这种情况时,你正在依靠别的视图失效来获得正确的行为。一旦修改了程序,这种依赖关系可能就会变化。因此,无论何时当你修改数据或者状态影响到了你自定义的View,你都应该调用其invalidate()方法。

注意:Android View会在其属性发生变化时候自动调用invalidate()方法,比如TextView的文本或者背景颜色发生变化。

硬件加速绘制模型

Android系统仍然使用invalidate()draw()来请求屏幕更新以及渲染View,但是处理实际的绘制有不同。Android系统不是立即执行绘制命令,而是将它们记录到包含了View层级的绘制代码输出的显示列表(display lists)中。另一个优化是,Android系统仅仅为那些通过调用invalide()而被标记成脏的View记录和更新显示列表。没有失效的View仅仅通过重新发布之前记录的显示列表来重新绘制。新的绘制模型包含三步:

  • 失效View层级
  • 记录和更新显示列表
  • 绘制显示列表

在这种模型里,你不能依赖于View与脏区域相交来让它的draw()方法被执行。为了确保Android系统记录View的显示列表,你必须调用其invalidate()方法。忘记调用会导致View显示的和之前一样,即使它的内容已经更改了。

使用显示列也有利于动画性能,因为设置特定属性(比如alpha或旋转)不要求让目标View失效(自动完成的)。此优化也适用于具有显示列表的View(硬件加速开启时的任何View)。比如,假设有一个LinearLayout包含一个ListView和Button,ListView位于Button的上方。这个LinearLayout的显示列表像这样:

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

假设现在你想要改变这个ListView的透明度。在对ListView调用setAlpha(0.5f)后,显示列表现在包含:

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • Restore
  • DrawDisplayList(Button)

ListView的复杂的绘制代码不会被执行。相反,系统仅仅更新更简单的LinearLayout的显示列表。在没有开启硬件加速的应用中,这个ListView和它的Parent的绘制代码将会再次执行。

不支持的绘制操作

当硬件加速时,2D渲染管道支持最常使用的Canvas绘图操作以及许多较少使用的操作。支持所有用于渲染Android应用程序的绘图操作,默认窗口小部件和布局以及常见的高级视觉效果,例如反射和平铺纹理。

画布缩放(Canvas Scaling)

硬件加速的2D渲染管道是为支持未缩放的绘图而建立的,一些绘图操作在较高的缩放比下会显著降低质量。在缩放比为1.0(未缩放)的情况下,这些操作被实现为纹理绘制(texture drawn),由GPU进行转换。在API级别小于17时,使用这些操作会导致随着缩放比的增加缩放工序增加。

下表显示什么时候支持正确处理大缩放比

需要缩放的绘制操作 开始支持的API级别
drawText() 18
drawPosText() 不支持
drawTextOnPath() 不支持
简单图形 17
复杂图形 不支持
drawPath() 不支持
Shadow layer 不支持

注意:简单图形是指使用不带PathEffect的Paint,不包含默认非默认连接(setStrokeJoin() / setStrokeMiter()),利用drawRect()drawCircle()drawOval()drawRoundRect()drawAcr()(使用userCenter = false)命令绘制而成的图形。其他情况属于复杂图形。

如果你的应用受上面的限制而被影响,你可以对于这些影响点通过调用setLayerType(View.LAYER_TYPE_SOFTWARE, null)来关闭硬件加速。

视图图层

在Android的所有版本,View都可以渲染到离屏缓冲区中,要么使用View的绘制缓冲区,要么使用Canvas.saveLayer()。离屏缓冲区或者图层,有几个应用。可以在对复杂View做动画或者应用组合效果的时候利用他们来获得更好的性能。比如,你可以利用Canvas.saveLayer()临时将View渲染到图层,然后用一个不透明度因子和其组合再渲染回屏幕,来实现渐入渐出效果。

从Android 3.0(API 11)开始,对于何时以及如何使用图层你可以通过View.setLayerType()方法获得更多的控制能力。这个API有两个参数:图层类型,可选的Paint对象用来描述如何组合图层。你可以用Paint参数将颜色过滤器、特殊混合模式或者不透明度应用到图层。View可以使用下面三个图层类型之一:

  • LAYER_TYPE_NONE:View按标准方式渲染,不会由离屏缓冲区支持。默认行为。
  • LAYER_TYPE_HARDWARE:应用支持硬件加速时,将会被硬件渲染成硬件纹理。如果应用不支持硬件加速,这个图层类型等效于LAYER_TYPE_SOFTWARE。
  • LAYER_TYPE_SOFTWARE:View由软件渲染成Bitmap。

怎么设置图层类型依赖于你的目标:

  • 性能:使用硬件层类型将View渲染成硬件纹理。一旦View被渲染进图层,它的绘制代码只有在View调用了invalidate()之后才会被执行。一些动画,比如透明度动画,可以直接应用到图层,这对于GPU来说是非常高效的。
  • 视觉效果:使用硬件层或者软件层类型加上一个Paint可以对View应用一些特殊的视觉效果。例如,你可以使用ColorMatrixColorFilter对View实现黑白效果。
  • 兼容性:使用软件层来强制View使用软件的方式绘制。如果一个View是硬件加速的(比如,你的整个应用是硬件加速的),同时有渲染问题,这种方式很容易处理因为硬件渲染管道的限制导致的问题。

视图图层和动画

当应用程序是硬件加速时,硬件层可以带来更快更平滑的动画效果。当对包含很多绘制操作的复杂View做动画想要达到每秒60帧的速度几乎不可能。可以使用硬件层将View渲染成硬件纹理来进行优化。硬件纹理操作可以用作对一个view进行动画操作,当进行动画的时候可以减少对View自身频繁的重绘。除非你改变这个view的属性(调用invalidate()方法)或者你手动的调用invalidate()。如果在你的应用中运行一个动画,但是并没有得到你想要的平滑效果,可以考虑为你要动画的view开启硬件层。

当View由硬件层支持时,一些属性由图层在屏幕上的复合方式操控。设置这些属性的时候是很高效的,因为他们不需要View失效或重绘。下面列出的属性会影响图层的叠加。设置这些属性会使View刷新,但是不会重绘目标View。

  • alpha:改变图层的不透明度
  • x,y,translationX,translationY:改变图层的位置
  • scaleX,scaleY:改变图层的大小
  • rotation,rotationX,rotationY:在3D空间改变图层的方向
  • pivotX,pivotY:改变图层的变换原点

这些属性是使用ObjectAnimator对View进行动画操作时用到的名字。如果你想要访问这些属性,调用对应的setter和getter方法。比如,想要修改alpha属性,调用setAlpha()。下面的代码片段展示在3D空间将View绕着Y轴旋转的最高效方式:

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();

小贴士和技巧

切换到硬件加速的2D图形可以立即提高性能,但您仍然应该按照以下建议来设计应用程序以有效使用GPU:

  • 减少应用中View数量
    • 系统需要绘制的View越多,越慢。 对于软件渲染管道也是一样。减少view数量是优化UI的最容易的方法之一。
  • 避免过度绘制
    • 不要在一个点画太多层。移除那些完全不可见的View。如果你需要绘制几层叠起来,可以考虑将它们合并到一层上。就现在的硬件来看,有一个好的经验就是动画的每帧不要绘制多余屏幕像素2.5倍的像素数量(bimap中的透明像素也计算在内)。
  • 不要在draw方法中创建渲染对象
    • 一个常见的错误就是当绘制方法被调用的时候,每次都要创建一个新的Paint或者Path。这将迫使垃圾回收器过于频繁的运行,这将对缓冲和硬件的绘制造成影响
  • 不要频繁修改形状
    • 比如,复杂的图形、路径和圆使用纹理蒙层渲染。每次你创建或修改路径时,硬件管道都会创建一个新的蒙层。这个过程是很昂贵的。
  • 不要频繁修改Bitmap
    • 你每次改变Bitmap的内容时,下次绘制的时候都会以GPU纹理形式再上传一次。
  • 要小心使用alpha通道
    • 当你使用setAlpha()AlphaAnimation或者ObjectAnimator改变View的透明度时,它都会被渲染到离屏缓冲区中,这将导致双倍的填充率。当对非常大的View应用不透明度效果时,可以考虑设置这个View的图层类型为LAYER_TYPE_HARDWARE
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,392评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,258评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,417评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,992评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,930评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,199评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,652评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,327评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,463评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,382评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,432评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,118评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,704评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,787评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,999评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,476评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,057评论 2 341