×

关于硬件加速的那么点儿东西

96
黑白咖
2016.09.04 23:29* 字数 4725

为什么会突然学习硬件加速呢?因为在绘图的时候,并不是所有的函数都支持硬件加速,我就有一个疑问,硬件加速不是好东西来的吗?干嘛不支持,百思不得解,于是遍寻资料,最后发现还是研究官方的API文档更加的靠谱。

硬件加速

安卓的硬件加速是自安卓3.0(API 11)之后才有的,安卓2D绘制管线支持硬件加速,但因为启用硬件加速需要增加资源,所以将会消耗更多的RAM。

在API14之后(包括14),硬件加速默认是开启的,侧面也说明,从11-13默认是关闭的,不过我想这个现在我们已经不考虑了,毕竟现在开发都不考虑低版本的手机了。启用硬件加速的最简单方式就是整个application都启用,简称一锅端,如果只是使用标准的view或者drawable,那么应该不会产生不好的绘制效果(这也是android官方API文档的原话,所以请放心使用)。但是,因为硬件加速并不支持所有的2D绘制操作,如果你的应用有自定义的view或者调用某些绘制函数,启用硬件加速可能会对你的应用有一些影响,比如看不见一些元素或者呈现错误的像素,所以为了解决这个不兼容的问题(在API14之后绘图时使用不支持硬件加速的方法),google大佬们的建议是,通过真机测试上启用硬件加速,遇到问题的话对以下四个级别进行控制,我们可以选择启用或者不启用硬件加速:

Application级别:

在安卓manifest文件,添加下列属性到<application>标签就可以给整个应用添加硬件加速:

Activity级别:

如果不想全局都启用硬件加速,那么个别Activity可以不指定硬件加速,在Activity级别启动或者不启用硬件加速,你可以<activity>标签指定android:hardwareAccelerated属性

window级别:

如果需要更细粒度的控制,可以用以下代码为一个window启用硬件加速

PS:目前还不允许在window级别关闭硬件加速的

view级别:

你可以在运行时对指定的View关闭硬件加速

第二个参数是Paint

或者在xml中的控件属性中,使用android:layerType=”software”来关闭硬件加速:比如

PS:目前还不允许运行时启用硬件加速,View layers还是一些函数是不支持硬件加速的。

判断一个View是否开启硬件加速

有时候我们想知道应用是否开启了硬件加速,尤其是对于自定义View的情景,如果你的应用有很多的自定义绘制并且不是所有的操作都支持新的绘制管线时(硬件加速)。那么这两个不同的方法就变得特别的有用,可以检测应用是否是硬件加速的:

View.isHardwareAcclerated() 返回true如果View是依附到启动了硬件加速的window

Canvas.isHardwareAccelerated 返回true如果canvas是硬件加速的

但是,强烈建议使用Canvas.isHardwareAcclerated代替View.isHardwareAcclerated,因为View的这个方法真的非常不靠谱,即使这个View是依附在一个硬件加速的Window上,但是仍然可以使用一个不启用硬件加速的Canvas进行绘制,比如当我们把View绘制到bitmap上的时候。而且很多时候,我们如果对window不熟悉的话,view有没有跟Window绑定在一起都不知道。

Android绘制模式

在硬件加速出现之前,原来的图像处理,渲染工作是由软件实现的。

当我们启用硬件加速的时候,android Framework会使用一个新的绘制模式,这个模式会使用display list来渲染画面。为了搞清楚display list以及它是怎么运行的,在学习心得绘制模式之前,我们应该学习一下在硬件加速出现之前,android使用的基于软件的绘制模式。

基于软件的绘制模式

在软件绘制模式,view是按照下面两个步骤进行绘制的:

1、无效化View层次结构

2、绘制View的层次结构

当应用需要更新它的一部分UI,它会调用view的invalidate方法,无效化消息就会通过各种途径传递到View的层次结构,然后计算屏幕中需要重绘的区域(脏区域),android系统还会对View层次结构中脏区域相交的所有view进行绘制,对于这种绘制模式,有两个不好的地方:

第一、这个模式在每一次绘制都需要执行大量的代码,比如,如果你的应用对一个button调用invalidate,而这个button坐标在其他view的上方,那么android系统就会重绘这些view,即使他们没有发生改变,仅仅因为它们处于和button相交的区域

第二、绘制模式会有一些隐藏bug,当android系统重绘那些相交的views的时候,即使你没有调用invalidate,相交区域内的一个被改变过的view可能也会进行重绘,这个时候,你通过其他的view的绘制来给这个view进行重绘,当你不经意间修改你的代码,这个时候可能你已经忘了这一段代码有这个隐藏的bug,修改代码后你发现显示的效果有问题,本该进行重绘的区域没有重绘,你就会怀疑是自己的代码逻辑出现问题了,所以应该尽可能在修改view的数据或状态的时候,对每一个你修改过的自定义View,主动调用他们的invalidate方法。

PS:当view的属性发生改变的时候,例如TextView上的background color或者text,这个时候android view会自动调用invalidate,进行重绘

硬件加速绘制模式

android系统依然使用invalidate和draw函数来请求屏幕刷新渲染界面,但实际上绘制的时候是有区别的,不同于立即执行绘制命令,android系统会先把它们记录在display list上,这个display lists包含view的层次结构的绘制代码。其他的优化是android系统只需要记录和更新display lists,通过调用invalidate函数来标记那些脏view,那些没有被标记为invalidate的view可以简单的进行重绘通过事先记录在display list上的记录。新的绘制模式包含三个步骤:

1、无效化View的层次结构

2、记录和更新显示列表

3、绘制显示列表

使用这个模式,你不能再依赖脏区域内相交的view来绘制其他的view,为了确保android系统记录这个view的display list,你必须调用invalidate,不调用的话,就会导致一个view看起来跟之前是一样的,即使你已经改变它了。所以这就一次性解决了两个问题,对于View层次结构中不想重绘的View,只要不调用那个View的invalidae即可。

使用display list对动画效果也有好处,因为设置指定的属性,例如alpha或者rotation,不需要调用目标view的invalidate,因为它会自动完成,这个优化也应用到view的display list。例如,假设有一个LinearLayout在Button上面有一个ListView,那么对于LinearLayout的display list就会像这样的:

DrawDisplayList(ListView)

DrawDisplayList(Button)

假设现在你通过调用setAlpha(0.5)来修改ListView的透明度,那么display list就变成这样了:

SaveLayerAlpha(0.5)

DrawDisplayList(ListView)

Restore

DrawDisplayList(Button)

关于ListView的复杂的绘制代码并没有被执行,系统只是更新了LinearLayout的display list,如果应用没有启用硬件加速,那么listview以及它的父容器LinearLayout的绘制代码都会再次执行。

不支持的绘制操作

当启用了硬件加速后,2D渲染管线支持大部分的canvas的常用绘制操作和一些不常用的操作,分别列举一下,其实我们也只有在自定义View的时候才担心canvas调用的函数会不会不支持硬件加速,所以我们只需要真正用到的时候查阅一下即可。

今天的主角


Canvas Scaling

The hardware accelerated 2D rendering pipeline was built first to support unscaled drawing, with some drawing operations degrading quality significantly at higher scale values. These operations are implemented as textures drawn at scale 1.0, transformed by the GPU. In API level <17, using these operations will result in scaling artifacts increasing with scale.

The following table shows when implementation was changed to correctly handle large scales:

Note: 'Simple' shapes aredrawRect(),drawCircle(),drawOval(),drawRoundRect(), anddrawArc()(with useCenter=false) commands issued with a Paint that doesn't have a PathEffect, and doesn't contain non-default joins (viasetStrokeJoin()/setStrokeMiter()). Other instances of those draw commands fall under 'Complex,' in the above chart.

If your application is affected by any of these missing features or limitations, you can turn off hardware acceleration for just the affected portion of your application by callingsetLayerType(View.LAYER_TYPE_SOFTWARE, null). This way, you can still take advantage of hardware acceleration everywhere else. SeeControlling Hardware Accelerationfor more information on how to enable and disable hardware acceleration at different levels in your application.

中间这段实在是看的够呛,希望有大牛看懂了能够告诉一下小弟。

View Layers

在android的所有版本中,views可以在离屏渲染,或者使用view的绘制缓存,或者通过使用Canvas.saveLayer()。离屏缓存,或者图层,有几种用途。你可以使用它们获得更好的显示效果,当对一些复杂的views使用动画效果或者一些合成效果,比如,你可以实现渐变效果使用Canvas.saveLayer()来临时渲染一个view到一个layer,然后把不透明的元素合成到屏幕上。

android3.0开始,使用layers的时候有更多的控制方法了,通过View.setLayerType()函数,这个API有两个参数:layer的类型,和一个可选的描述layer如何合成的Paint对象,你可以使用Paint参数来给layer添加Color Filter,指定混合模式,或者不透明度,可以选择以下三种layer type:

LAYER_TYPE_NONE:view只会普通地进行渲染,并且不会使用离屏缓存回退,这是默认的行为。

LAYER_TYPE_HARDWARE:如果应用启动了硬件加速,那么这个view就会使用硬件里面的texture渲染,如果应用不能够硬件加速,那么它的效果就跟LAYER_TYPE_SOFTWARE一样。

LAYER_TYPE_SOFTWARE:这个view将会使用软件来渲染到一个bitmap里。

texture:纹理,在3D游戏开发里面叫做贴图,存放图片到texture里面,运行时会读取,而在这里当我们需要重绘的时候也是一样的原理,从硬件中读取纹理然后进行绘制。

这几种类型的layer取决于你的用途:

Performance:使用一个硬件图层类型来渲染view到硬件中的texture,自从view被渲染到一个图层,它的绘制代码就不再被执行直到view调用invalidate方法,一些动画,例如alpha动画,使用GPU来实现就可以高效地直接作用于图层上。

Visual effects:使用一个硬件或者软件图层类型,还有一个Paint对view进行一些特殊的处理,比如,你可以使用黑色和白色通过ColorMatrixColorFilter来绘制一个view。

Compatibility:使用一个软件图层类型来强迫一些view使用软件来渲染,如果一个view是硬件加速的(比如,如果你整个应用都是硬件加速的),那么就会出现渲染问题,最简单的解决方式就是限制硬件渲染管道(关闭View的硬件加速)。

View layer和animations

当你的应用是硬件加速的,硬件图层类型可以传达更快和更加顺滑的动画,当你在处理的是一个复杂的又很多绘制操作的view的时候,运行一个动画不总是60帧每秒的。可以通过使用硬件层来渲染view到一个硬件的texture中来优化这个问题,硬件texture可以用来对view进行动画,排除开始动画的时候需要重绘自己的View,view不会重新重绘除非你改变它的属性,然后调用invalidate()。如果你运行一个动画在你的应用上,但是得不到一个你想要的顺滑结果,考虑启用硬件加速在你的动画view上。当view从硬件图层回退的时候,它的一些属性会通过图层合成到屏幕上的方式进行处理,设置这些属性将会更加高效,因为它不需要view重绘或者无效化,下面的一些属性可以通过这种方式来合成到屏幕上,调用这些属性的setter方法就可以在目标view上不需要重绘。

alpha:改变图层的不透明度

x,y,translationX,translationY:改变图层的位置

scaleX,scaleY:改变图层的大小

rotation,rotaionX,rotationY:改变图层在三维空间的排列方向

pivotX,pivotY:改变图层的转移点

这些属性都是同样的用法当动画一个view使用ObjectAnimator,如果你想要访问这些属性,调用这些属性的getter和setter,比如,为了修改alpha属性,调用setAlpha,接下来的代码片段展示了最有效的方式来围绕Y轴旋转一个view在3D:

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);

ObjectAnimator.ofFloat(view, "rotationY", 180).start();

因为硬件层消耗video存储,所以强烈建议启用它们只有在动画时长并且关闭它们当动画完成的时候,你可以完成这个通过使用动画监听器,这一个比较细节,但能够对View进行优化,毕竟手机内存这么少

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


最后是一些API提到的小tips和技巧

切换到硬件加速,界面固然是更加流畅了,但是我们开发应用的时候要想让GPU的效率更加的高,应该注意以下几点:

减少应用中view的数量

要绘制的view越多,那么必然就越慢,使用软件渲染管道也是一样的,减少view的数量是优化UI的最简单的途径。

避免透支

不要在顶部画太多互相混合的图层,移除那些完全被其他不透明View覆盖的View,如果你需要在顶部画几个互相混合的图层,考虑把他们放到一个单一的图层里,一个很好的经验法则与当前硬件是不绘制超过2.5倍的每帧屏幕上的像素数(在一个位图的像素的透明像素!),简而言之就是不要嵌套太多层。

不要老是创建Paint和Path对象

一个普遍的错误是,每次调用draw方法的时候总是new一个Paint对象,或者new一个Path对象,这样就强迫垃圾回收器频繁地运行,而且也失去了硬件管道中的缓存和优化。

不要频繁修改外形

比如复杂的外形,路径和圆,它们都是使用texture mask进行渲染的,每次修改路径,硬件管道就创建一个新的mask,这样开销是很大的。

不要频繁修改bitmap

每一次修改一个bitmap中的内容,当你下次绘制它的时候,它就会再次上传到GPU中的texture。

小心使用alpha

当你使用setAlpha,或者AlphaAnimation,或者ObjectAnimator来改变一个View的透明度时,它渲染在离屏缓存中需要两倍填充率,当需要在在一个非常大的view上修改alpha,就要考虑设置view的layer type为LAYER_TYPE_HARDWARE

最后总结一下硬件加速我们应该知道:

1、硬件加速是从API 11引入,API 14之后才默认开启。对于标准的绘制操作和控件都是支持的,但是对于自定义View的时候或者一些特殊的绘制函数就需要考虑是否需要关闭硬件加速。

2、我们面对不支持硬件加速的情况,就需要限制硬件加速,这个兼容性的问题是因为硬件加速是把View的绘制函数转化为使用OpenGL的函数来进完成实际的绘制的,那么必然会存在OpenGL中不支持原始回执函数的情况,对于这些绘制函数,就会失效。

3、硬件加速的消耗问题,因为是使用OpenGL,所以就需要把系统中OpenGL加载到内存中,所以OpenGL API调用就会占用8MB,而实际上会占用更多内存,并且使用了硬件必然增加耗电量了。

4、另一方面,硬件加速的优势还有display list这个设计,使用这个的话,我们就不需要每次重绘都执行大量的代码,因为对脏区域的,基于软件的绘制模式会重绘脏区域内的所有控件,而display只会更新列表,然后绘制列表内的控件。

哎,还需要好好学习英语才行啊!接下来准备啃一下xfermode。

已发布
Web note ad 1