标签:als 简单的 efault 混合 android应用 ppa ack apm inf
https://developer.android.com/guide/topics/graphics/hardware-accel.html
在手机客户端尤其是Android应用的开发过程中,我们经常会接触到"硬件加速"这个词。由于操作系统对底层软硬件封装非常完善,上层软件开发者往往对硬件加速的底层原理了解很少,也不清楚了解底层原理的意义,因此常会有一些误解,如硬件加速是不是通过特殊算法实现页面渲染加速,或是通过硬件提高CPU/GPU运算速率实现渲染加速。
本文尝试从底层硬件原理,一直到上层代码实现,对硬件加速技术进行简单介绍,其中上层实现基于Android 6.0。
对于App开发者,简单了解硬件加速原理及上层API实现,开发时就可以充分利用硬件加速提高页面的性能。以Android举例,实现一个圆角矩形按钮通常有两种方案:使用PNG图片;使用代码(XML/Java)实现。简单对比两种方案如下。
CPU(Central Processing Unit,中央处理器)是计算机设备核心器件,用于执行程序代码,软件开发者对此都很熟悉;GPU(Graphics Processing Unit,图形处理器)主要用于处理图形运算,通常所说"显卡"的核心部件就是GPU。
下面是CPU和GPU的结构对比图。
其中:
注:很多计算机中的GPU有自己独立的显存;没有独立显存则使用共享内存的形式,从内存中划分一块区域作为显存。显存可以保存GPU指令等信息。
为了方便理解,这里先从底层电路结构的角度举一个例子。如下图为一个加法器,对应实际的数字电路结构。
现在我们要计算8个整数的和。
对于CPU这种串行结构,代码编写很简单,用for循环把所有数字逐个相加即可。串行结构只有一个加法器,需要7次求和运算;每次计算完部分和,还要将其再转移到加法器的输入端,做下一次计算。整个过程至少要消耗十几个机器周期。
而对于并行结构,一种常见的设计是级联加法器,如下图,其中所有的clock连在一起。当需要相加的8个数据在输入端A1~B4准备好后,经过三个时钟周期,求和操作就完成了。如果数据量更大、级联的层级更大,则并行结构的优势更明显。
由于电路的限制,不容易通过提高时钟频率、减小时钟周期的方式提高运算速度。并行结构通过增加电路规模、并行处理,来实现更快的运算。但并行结构不容易实现复杂逻辑,因为同时考虑多个支路的输出结果,并协调同步处理的过程很复杂(有点像多线程编程)。
假设我们有如下图像处理任务,给每个像素值加1。GPU并行计算的方式简单粗暴,在资源允许的情况下,可以为每个像素开一个GPU线程,由其进行加1操作。数学运算量越大,这种并行方式性能优势越明显。
Android 3.0 (API level 11), 开始支持
所有的View 的canvas都会使用GPU,但是硬件的加速会占有一定的RAM。
在API >= 14上,默认是开启的,如果你的应用只是标准的View和Drawable,全局都打开硬件加速,是不会有任何问题的。
然而,硬件加速并不支持所有的2D画图的操作,这时开着它,可能会影响到你的自定义控件或者绘画,出现异常等行为,
所以android对于硬件加速提供了可选性
如果你的应用执行了自定义的绘画,可以通过在真机上测试开启硬件加速查找问题
你可以在不同level控制hardware acceleration:
<application android:hardwareAccelerated="false" ...> </application>
<application android:hardwareAccelerated="true"> <activity ... /> <activity android:hardwareAccelerated="false" /> </application>
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
Note:
在代码中:
myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
或者在布局中android:layerType="software":
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" android:paddingLeft="2dp" android:layerType="software" android:paddingRight="2dp" >
Note: 并不是关闭硬件加速,而是强制使用软件加速,如果硬件加速是关闭的,那么就算设置了LAYER_TYPE_HARDWARE也使用软件加速。
如果必须进行这样的验证,建议你在draw的代码块中使用:Canvas.isHardwareAccelerated(),
因为如果当一个View被attach到一个硬件加速的Window上,仍然能使用非硬件加速的canvas来绘制。
比如:将一个View以bitmap的形式进行缓存
当硬件加速被启用,Android framework就会使用一个新的绘制模型,这个新的绘制模型使用display lists来渲染你的app,显示到屏幕上。
要理解硬件加速display lists,及它是如何影响你的app的,就需要先了解一下软件加速时Android是怎么来绘制view的。
在软件绘制模型中,view的绘制有两个步骤:
当一个app要去更新它的一部分ui时,app中的那些内容改变的view就会调用 invalidate()或类似的方法。Invalidate的消息按照View的层级关系向上传递用以计算需要重画的部分(即脏区域),然后Android系统会对和脏区域有交集的所有View进行绘制。(或者说有绘图缓存时,此view和所有父view)
不幸的是这种模型中有两个缺点:
Note: Android views automatically call invalidate() when their properties change, such as the background color or the text in a TextView.
Android系统硬件加速仍然使用invalidate()和draw()去请求屏幕更新和渲染view,但是实际过程是不同的。
当invalidate()时并不会立即执行绘制动作,而是Android系统会记录绘制动作到一个display lists里边,这个display lists包含了View的hierarchy的绘制代码的输出。
另外一个优化就是,Android系统只需要记录和更新 调用invalidate()的view中被标记为脏区域。没有被请求invalidated 的view可以通过重新发布(re-issuing)先前的记录的display list以便重画。
新的绘制模型包含三步:
使用这个模型,你不能依赖一个View和脏区域有交集就会执行draw()方法。要保证Android系统记录了一个View的display list,你必须调用invalidate()方法,如果忘记了调用,会使View即便是发生了改变后也会看起来相同,这是一个比较容易发现bug的方式。
使用display lists的方式对动画的表现也是很有好处的,因为设置指定的属性值就不需要请求刷新目标View(这将自动执行),比如透明度或者旋转。
这项优化也应用于有display lists的Views(启用了硬件加速的View),
例如,现在有一个LinearLayout包含了一个ListView和Button,listview在button的上面。这时候LinearLayout的display lists如下所示:
假设现在,你想要改变ListView的透明度,在调用ListView的setAlpha(0.5f) 之后,LinearLayout的display lists如下所示:
这时候绘制Listview的复杂绘制过程就不会被执行,取而代之的是简单的更新了LinearLayout的display list。
而如果没有启用硬件加速,Listview和它的父view的绘制代码都会重新执行。
开启GPU硬件加速会提升程序的绘图效率,但是也存在一定的局限性。
1.启用GPU硬件加速会增加内存的使用。
2.Android中有些2D绘图API在GPU硬件加速时不能使用或者要到某个指定的版本才能使用。
第一列是受限制的方法,第二列是开始支持的API Level,红叉代表到目前还不支持。
以下为Canvas中在GPU硬件加速时受限制的功能:
drawBitmapMesh() (colors array) |
18 |
drawPicture() |
23 |
drawPosText() |
16 |
drawTextOnPath() |
16 |
drawVertices() |
? |
setDrawFilter() |
16 |
clipPath() |
18 |
clipRegion() |
18 |
clipRect(Region.Op.XOR) |
18 |
clipRect(Region.Op.Difference) |
18 |
clipRect(Region.Op.ReverseDifference) |
18 |
clipRect() with rotation/perspective |
18 |
以下为Paint中在GPU硬件加速时受限制的功能:
setAntiAlias() (for text) |
18 |
setAntiAlias() (for lines) |
16 |
setFilterBitmap() |
17 |
setLinearText() |
? |
setMaskFilter() |
? |
setPathEffect() (for lines) |
28 |
setShadowLayer() (other than text) |
28 |
setStrokeCap() (for lines) |
18 |
setStrokeCap() (for points) |
19 |
setSubpixelText() |
28 |
以下为Xfermode在GPU硬件加速时受限制的功能:
PorterDuff.Mode.DARKEN (framebuffer) |
28 |
PorterDuff.Mode.LIGHTEN (framebuffer) |
28 |
PorterDuff.Mode.OVERLAY (framebuffer) |
28 |
以下为Shader在GPU硬件加速时受限制的功能:
ComposeShader inside ComposeShader |
28 |
Same type shaders inside ComposeShader |
28 |
Local matrix on ComposeShader |
18 |
硬件加速对Canvas缩放导致绘图质量会明显降低 ,
Android中硬件加速的2D渲染管线最初只支持无缩放的绘图,这会导致在将缩放比例设置为较大的时候,绘图质量会明显降低。最初,GPU加速下的2D绘图操作会被渲染成一个缩放比例为1.0的纹理,然后GPU会将它缩放到指定比例尺。在API Level小于17的时候,随着缩放比例scale的变大,绘图质量就更加难以保证。从API级别28开始,所有绘图操作都可以无问题地缩放。
下面的表格表示了从什么版本开始Android能在GPU硬件计算下正确处理2D图形的大比例缩放问题:
Drawing operation to be scaled |
First supported API level |
drawText() |
18 |
drawPosText() |
28 |
drawTextOnPath() |
28 |
Simple Shapes* |
17 |
Complex Shapes* |
28 |
drawPath() |
28 |
Shadow layer |
28 |
注意: ‘Simple‘ shapes是drawRect(),drawCircle(),drawOval(),drawRoundRect()和drawArc()(使用useCenter = false)命令,使用没有PathEffect的Paint发出,并且没有 包含非默认joins (通过setStrokeJoin()/ setStrokeMiter())。 这些绘图命令的其他实例属于上图中的"‘Complex"。
现在我们开发的App一般将targetSdkVersion写为最新版本,并且市场上的手机绝大部分都是Android 4.0以上的,所以我们现在开发的App默认情况下在绝大部分手机上基本都是默认开启了GPU硬件加速的。如果我们自己要自定义一个View,我们要重写其onDraw方法,通过调用各种绘图方法实现复杂的效果,但是如果我们调用的API在GPU硬件加速下不支持的话,就画不出我们想要的效果,举个例子,比如我们想在自定义View中绘制一个具有模糊效果的椭圆,需要调用画笔Paint的setMaskFilter()方法,但是我们通过上面的受限API列表可以发现,在GPU硬件加速下,Pait的setMaskFilter()方法不被支持,虽然调用不报错,但是不会起到任何效果。为了画出我们想要的效果,我们可以通过View的setLayerType(View.LAYER_TYPE_SOFTWARE, null)方法单独把我们的View禁用掉GPU硬件加速,这样在软件渲染模式下所有的2D绘图API都可以正常使用了。
最后有点需要说明,上述Android在GPU硬件加速下2D图形绘制API存在的局限问题是基于当前最新API Level 28的,随着以后更新Android版本的发布,可能上述受限API会逐渐在GPU下得到更好的支持。
在Android的所有版本中,View都能够通过使用View的drawing cache或使用Canvas.saveLayer()渲染到屏幕外缓冲区(off-screen buffers)。 屏幕外缓冲区或layers具有多种用途。 在呈现view复杂的动画或应用合成效果时,可以使用它们来获得更好的性能。 例如,您可以使用Canvas.saveLayer()实现淡入淡出效果,以暂时将view 渲染到一个layer 中,然后使用不透明度因子将其合成到屏幕上。
从Android 3.0(API level 11)开始,你就能够通过View.setLayerType(int layerType, Paint paint)方法对何时以及如何使用layers 有了更多的控制,这个API具有两个参数:一个是你想使用的layerType,另外一个是可选参数Paint(表明了Layer是如何被合成到一起的)。你可以把Paint参数应用color filters,或特殊的混合模式 或者是对一个layer进行不透明效果。
一个View可以使用如下的三种layer类型之一:
使用何种类型的layer全靠你的目的:
当你的应用程序已经使用了硬件加速的话,硬件层能够带来更为快速和更为平滑的动画效果。当对一个复杂的View进行动画操作时,因为要进行很多的画操作,所以并不可能总是能达到60帧每秒。This can be alleviated(减轻) by using hardware layers to render the view to a hardware texture.。硬件纹理操作可以用作对一个view进行动画操作,当进行动画的时候可以消除对View自身频繁的重绘。view不会重绘,除非你要改变这个view的属性(那些自动调用invalidate()的属性 或者 你手动的调用invalidate())。如果在你的应用中运行一个动画,但是并没有得到你想要的平滑效果,可以考虑为你要动画的view开启硬件层。
在硬件加速的情况下,下列属性的改变不会使得view的displaylist进行重绘,而只用改变displaylist中对应的属性即可,大大提升了渲染效率。
这些属性是通过ObjectAnimator对一个view进行动画操作时所使用的,如果你想访问这些属性,直接调用这些属性的setter或者getter方法,例如想改变View的alpha则直接调用setAlpha()。
如下的代码片段显示了一个View通过Y轴进行3D旋转。
因为硬件层消耗显存,强烈建议只为动画期间开启,动画结束禁用。这一点可通过animation listeners实现:
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();
或者如果是使用 ViewPropertyAnimator,那么更简单:
view.animate() .rotationY(90) .withLayer(); // withLayer() 可以自动完成上面这段代码的复杂操作
切换到硬件加速的2D图形可以立即提高性能,但仍建议遵循以下推荐规范来设计应用程序以便有效地利用GPU:
Reduce the number of views in your application
The more views the system has to draw, the slower it will be. This applies to the software rendering pipeline as well. Reducing views is one of the easiest ways to optimize your UI.
Avoid overdraw
不要过多的叠加层,当一个View被其他view完全遮挡住了的话,最好把被遮挡的view移除掉。如果你需要绘制多个layer做一个叠加效果的话,考虑把这些层合并为一个层。就现在的硬件来看,有一个好的经验就是每帧不要绘制多余屏幕像素2.5倍的像素数量(bimap中的透明像素也计算在内)。
Don‘t create render objects in draw methods
一个常见的错误是,每一次渲染方法被调用的时候都创建一个新的Paint或一个新的Path。这会迫使gc更加频繁,也会绕过硬件管道的缓存和优化。
Don‘t modify shapes too often
复杂的形状如Path,Circle,都是通过纹理masks来渲染的,每次创建或者修改Path,这个硬件管道会创建一个新的mask,这个代价是十分昂贵的。
Don‘t modify bitmaps too often
Every time you change the content of a bitmap, it is uploaded again as a GPU texture the next time you draw it.
小心使用alpha
当你使用setAlpha ,AlphaAnimation或者ObjectAnimator设置一个View的透明效果时,它将需要双倍填充率的屏幕外缓存,当应用alpha到一个大的View上的时候,考虑设置view 层的类型为LAYER_TYPE_HARDWARE。
标签:als 简单的 efault 混合 android应用 ppa ack apm inf
原文地址:https://www.cnblogs.com/muouren/p/11706381.html