Android 图形 III-硬件加速

概述:

从Android 3.0开始, Android的2D渲染通道开始支持硬件加速. 这代表所有的在View的Canvas上的绘制操作都将使用GPU. 因为要启用硬件加速增加了资源, 所以APP将会消耗更多的内存.

如果Android API的版本>=14的话, 那么默认情况下就会启动硬件加速, 也可以明确的启动/不启动. 如果我们的APP只使用标准的View和Drawable, 全部打开硬件加速不会产生任何不利的效果. 但是因为硬件加速并不是支持所有的2D绘制操作, 开启硬件加速可能会影响一些自定义的View或者绘制方法. 问题通常表现为元素不可见, 异常或者错误渲染. 为了解决这个问题, Android为我们提供了多种开启/不开启硬件加速的等级. 稍后介绍这个功能. 如果APP需要处理自定义的绘制, 那么就需要在真实的硬件条件下测试硬件加速功能. 下面会有章节介绍硬件加速已知的问题, 并如何避开它们.

控制硬件加速:

我们可以使用下面这些级别来控制硬件加速: Application, Activity, Window, View. 下面来依次介绍:

Application: 在Manifest文件中添加下列属性到<application>中就可以为整个APP提供了硬件加速:

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

Activity: 如果我们的APP不适合全局都启用硬件加速,那么也可以指定为某个Activity单独启用硬件加速. 想要这样做我们需要使用<activity>的android:hardwareAccelerated属性, 这个栗子是全局启用但是单个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是否使用硬件加速:

有些时候, APP需要知道当前是否启用了硬件加速, 特别是对那些自定义的View. 如果我们做了很多自定义的绘制, 并且并不是所有的渲染管道都支持的时候就应该关注一下这点. 有两种方法可以让我们知道APP是否启动了硬件加速:

1.      使用View.isHardwareAccelerated()方法, 如果返回true则表示View关联了一个硬件加速的window.

2.      Canvas.isHardwareAccelerated()方法, 如果返回true则表示canvas被硬件加速了.

如果我们必须查看的话, 尽量使用Canvas.isHardwareAccelerated()方法代替View.isHardwareAccelerated().当view关联了硬件加速的window的时候, 它依然可以使用一个没有硬件加速的Canvas. 比如我们绘制view到一个高速缓存的位图中的时候.

Android的绘制模式:

当硬件加速可用的时候, Android framework采用了一种新的渲染方式:利用display list来渲染我们的APP到屏幕上. 要充分的理解display list和它是如何影响我们的APP的, 就得先理解Android是如何在没有硬件加速的情况下绘制view的. 下面会介绍软件绘制和硬件加速模式.

基于软件的绘制模式:

在软件绘制模式下, 绘制一个view需要以下两步: invalidate the hierarchy和draw the hierarchy. 不管什么时候只要APP需要刷新它的一部分UI, 它就得调用它所在view的invalidate()方法. 这个invalidation消息一路沿着view层计算出哪些区域需要重绘(这些需要重绘的区域叫dirtyregion)并传递给Android, 然后Android负责重绘它们, 但是这种重绘机制有两个缺点:

首先, 这种模型需要在每次绘制的时候执行很多代码. 栗如: 有个button在一个view上, 这时候我们的APP调用了这个button的invalidate()方法, 这时候计算这个view没有改变, Android还是会重绘它.

其次, 这种机制可能会隐藏一些我们APP中的bug. 因为当dirty region交叉时, Android将会重绘view, 所以有时候有些被更改的View即使没有调用validate()方法也可能会被重绘. 当这种事情发生的时候, 我们需要为另一个可能被重绘的view保留正确行为. 每次修改app的时候这一行为都可以发生改变. 因为这样我们在修改影响view绘制的数据或者状态的时候总是应该调用invalidate().

Android在view的属性发生改变的时候, 总是会调用invalidate().比如背景色或者TextView中的文本发生了变化.

硬件加速绘制模式:

当需要屏幕更新和渲染view的时候Android使用invalidate()和draw()方法, 但是处理实际绘制(actual drawing)的时候却又不同. Android不会直接执行绘制命令, 而是将它们记录到display list中, display list包含了view层绘制代码的输出. 还有一个优化措施是, android只会为invalidate()方法标记的view来记录和更新display list. 还没有被重绘的view可以通过简单的重发前一个display list记录来重新绘制. 这种新的绘制模式包含三个阶段:

1.      Invalidate the hierarchy.

2.      Record and update display lists.

3.      Draw the display lists.

通过这个模式, 我们就不能指望一个与dirty region相交叉的地方去自动执行它的draw()方法了. 为了保证Android记录一个view的display list, 我们必须调用invalidate()方法. 如果忘记这样做会导致一个view在它改变属性之后还是老样子不变.

使用display list也会是动画性能受益, 因为设置指定的属性比如透明度, 旋转, 都不需要重绘目标view(它会自动执行). 这个优化也适用于View(任何启用硬件加速的view). 比如, 假设有个LinearLayout, 包含一个在Button上的ListView. LinearLayout的display list看起来是这样:

DrawDisplayList(ListView)

DrawDisplayList(Button)

假设现在想要修改ListView为不透明. 调用setAlpha(0.5f)之后, display list现在是这样的:

SaveLayerAlpha(0.5)

DrawDisplayList(ListView)

Restore

DrawDisplayList(Button)

ListView复杂的绘制代码并没有被执行, Android只是升级了LinearLayout的displaylist. 在不启用硬件加速的APP中, 这些list中的绘制代码会在ListView和它的父容器中执行两次.

不支持的绘制操作:

当启用硬件加速的时候, 2D渲染管道对很多不常用的操作和常用的Canvas绘制操作支持的一样好. 所有的Android附带APP或者默认控件和layout的绘制操作, 还有常用高级的视觉效果比如反射和瓷砖纹理都可以被支持. 下表描述了不同API等级所支持的操作:

 

首次支持的API等级

Canvas

 

drawBitmapMesh()(colors array)

18

drawPicture()

23

drawPOSText()

16

drawTextOnPath()

16

drawVertices()

X(不支持)

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

drawArc()

21

drawRoundRect()

21

saveLayer() with RectF dimensions

21

saveLayer() with float dimensions

21

saveLayerAlpha() with RectF dimensions

21

saveLayerAlpha() with float dimensions

21

Paint

 

setAntiAlias()(for text)

18

setAntiAlias()(for lines)

16

setFilterBitmap()

17

setLinearText()

X

setMaskFilter()

X

setPathEffect()(for lines)

X

setRasterizer()

X

setShadowLayer()(other than text)

X

setStrokeCap()(for lines)

18

setStrokeCap()(for points)

19

setSubpixelText()

X

getFontFeatureSettings()

21

isElegantTextHeight()

21

isElegantTextHeight()

21

setFontFeatureSettings()

21

setLetterSpacing()

21

Xfermode

 

AvoidXfermode

X

PixelXorXfermode

X

PorterDuff.Mode.DARKEN(framebuffer)

X

PorterDuff.Mode.LIGHTEN(framebuffer)

X

PorterDuff.Mode.OVERLAY(framebuffer)

X

Shader

 

ComposeShader inside ComposeShader

X

Same type shaders inside ComposeShader

X

Local matrix on ComposeShader

18

View层:

在所有版本的Android中, view都有能力渲染到屏幕外缓冲区中, 不管是通过使用view的drawing缓存, 或者是使用Canvas.saveLayer(). 屏幕外缓冲区(Off-screen buffers), 或者层(layers)有多种用途. 我们可以使用它们在实现view的复杂动画或者组合效果的时候获得更好的性能. 栗如, 我们可以通过使用Canvas.saveLayer()方法临时渲染一个view到一个层中, 然后用不透明特性将其组合到屏幕上来实现一个褪色的效果.

从Android 3.0开始, 我们可以通过View.setLayerType()方法来决定如何/什么时候使用层. 这个API接收两个参数: 想要使用的层的类型和一个可选的描述了层该被如何组合的Paint对象. 我们可以使用Paint参数来应用一个颜色过滤器, 指定混合模式, 或者让一个层不透明. 一个View可以使用下列三种层类型:

LAYER_TYPE_NONE: 这个view使用普通的方法渲染, 不会由一个屏幕外缓冲区备份(backed).这是默认的行为.

LAYER_TYPE_HARDWARE: 如果APP是硬件加速的, 那么这个view会被硬件渲染为一个硬件纹理(hardwaretexture). 如果APP不是硬件加速的, 那么这个层类型的行为跟下面的LAYER_TYPE_SOFTWARE一样.

LAYER_TYPE_SOFTWARE: 这个view被软件渲染为一个Bitmap.

我们使用的层的类型由我们的目的决定(更重视哪个):

性能: 这种情况下应该使用一个硬件层类型来渲染一个view为一个硬件纹理. 一旦一个view被渲染到了一个layer里, 它的绘制代码在调用invalidate()的时候才会被执行. 有些动画, 比如透明度动画, 可以被直接应用于层上, GPU完成这些操作十分的高效.

视觉效果: 这种情况应该使用一个硬件或者软件层, 然后通过Paint指定一个特殊的视觉效果. 栗如: 我们可以使用ColorMatrixColorFilter来绘制一个黑白相间的view.

兼容性: 这种情况下应该使用一个软件层类型来强制view由软件渲染. 如果一个view由硬件渲染, 那么可能会存在渲染问题, 这是一个规避硬件渲染管道限制简单的方法.

View层和动画:

当我们的APP使用硬件加速的时候, 硬件层可以提供更快和更流畅的动画体验. 当动画很复杂的时候, 并不能保证总是保持60帧, 这种问题可以通过硬件层将view渲染到一个硬件纹理上来解决. 这样就可以通过硬件纹理来完成view的动画, 而不用view不断的重绘自己. 除非view的属性被改变(这时候会调用invalidate()方法)或者手动调用invalidate()方法, 否则view不会重绘. 如果我们想要运行的APP的动画并没有获得我们理想中的流畅效果, 那么就应该考虑在view上使用硬件层.

当view处在一个硬件层中的时候, 它的某些属性将会由该层与屏幕合并的方式决定. 设置这些属性的效率很高, 因为它们不会导致view被重绘. 下面的属性将会影响层合并的方式:

Alpha: 修改层的透明度.

x, y, translationX, translation: 修改层的位置.

scaleX, scaleY: 修改层的尺寸.

rotation, rotationX, rotationY: 在3D空间中修改层的方向.

pivotX, pivotY: 修改层的变换的原点.

当view使用ObjectAnimator实现动画的时候, 可以通过名字指定这些属性. 如果我们想要访问这些属性, 只需要调用相应的setter和getter. 栗如: 想要修改alpha, 调用setAlpha()方法. 下面的代码段演示了在3D下绕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图片的处理性能, 但是我们仍然应该通过下面的小技巧设计APP以高效的利用GPU:

1.      减少APP中的view: 绘制越多的view就会越慢. 这一原则对于软件渲染通道同样适用. 减少view数量是最简单的优化UI的方法了.

2.      避免过度绘制: 不要在层上面叠加太多. 应该移除任何被不透明的view盖住的view. 如果需要将多个层叠加混合使用, 应该考虑将它们放在同一个层里.

3.      不要在绘制方法中创建渲染对象: 一个常见的的错误是在每次调用渲染的方法的时候都创建一个新的Paint或者一个新的Path. 这迫使垃圾回收系统更加频繁的运行, 同时也绕过了缓存和硬件管道优化.

4.      不要太频繁的修改外形: 复杂的形状, 路径和圆形都会使用texture标记渲染. 每次创建或者修改一个路径, 硬件管道都会创建一个新的标记, 这会付出不少代价.

5.      不要太频繁的修改Bitmap: 每次我们修改Bitmap的时候, 它都会作为一个GPU texture被重新绘制.

6.      慎用透明度: 当我们通过setAlpha(), AlphaAnimation或者ObjectAnimator修改view的透明度的时候, 它都会被渲染到一个屏幕外缓冲区, 它会消耗双倍的填充率(fill-rate). 当修改一个大的view的透明度的时候, 应该考虑设置view层的类型为LAYER_TYPE_SOFTWARE.

 

总结:

硬件加速就是使用GPU来处理图形操作.

硬件加速可以按级别提供给APP, 包括Application, Activity, Window, View.

 

参考: https://developer.android.com/guide/topics/graphics/hardware-accel.html

 

### 回答1: ucos-iii-tcpip.a是一个软件库,用于嵌入式系统中使用μC/OS-III操作系统和TCP/IP协议栈进行网络通信的开发。 ucos-iii-tcpip.a提供了一系列的接口和函数,帮助开发人员在嵌入式系统中实现TCP/IP网络通信功能。它包含了TCP/IP协议栈的实现以及与μC/OS-III操作系统的接口,使得开发人员可以简单地在嵌入式系统中实现TCP/IP网络通信功能。 使用ucos-iii-tcpip.a可以方便地创建网络任务,并通过接口函数来管理和控制网络通信。它提供了一些常用的网络协议,如TCP、UDP、IP等,并且支持网络连接、数据传输等基本的网络操作。此外,ucos-iii-tcpip.a还提供了一些高级功能,如网络管理、安全性、QoS等,以满足不同应用场景的需求。 通过使用ucos-iii-tcpip.a,嵌入式系统开发人员可以快速、简便地添加网络通信功能,提高系统的灵活性和可扩展性。它提供了一种可靠、高效的方式来实现嵌入式系统与外部设备之间的数据交换,使得嵌入式系统能够连接到互联网,实现远程控制、数据传输等功能。 总之,ucos-iii-tcpip.a是一款强大的软件库,它结合了μC/OS-III操作系统和TCP/IP协议栈,为嵌入式系统提供了完善的网络通信功能,帮助开发人员快速构建并实现网络连接和数据传输等功能,使得嵌入式系统能够更好地适应各种应用场景的需求。 ### 回答2: ucos-iii-tcpip.a是一个TCP/IP协议栈的静态库文件。uCos-III是一个开发嵌入式实时操作系统的工具。它提供了一种可靠的方法来构建实时系统,并可以方便地适应不同的硬件和应用需求。 ucos-iii-tcpip.a库是基于uCos-III实时操作系统的一个扩展库,用于支持TCP/IP协议栈的功能。TCP/IP协议栈是计算机网络通信的基础,它包括传输控制协议(TCP)和互联网协议(IP)。通过使用这个库,开发者可以轻松地在嵌入式系统中实现TCP/IP网络通信功能。 ucos-iii-tcpip.a库提供了一系列接口和函数,用于处理与TCP/IP协议相关的任务,如网络连接的建立和断开、数据传输的处理等。它还包含了各种网络协议的实现,如IP、TCP、UDP等,以及网络应用层协议的支持,如HTTP、FTP等。开发者可以通过调用这些函数和接口来完成各种网络通信任务。 使用ucos-iii-tcpip.a库可以极大地简化开发者在嵌入式系统中实现TCP/IP通信的工作量。它提供了一个高效可靠的通信框架,可以帮助开发者快速构建自己的网络应用。一旦完成了TCP/IP通信的底层实现,开发者可以更加专注于应用层的业务逻辑开发,而不需要过多关注网络通信的细节。 总结来说,ucos-iii-tcpip.a是一个用于嵌入式系统中支持TCP/IP协议栈的静态库文件,它提供了一系列接口和函数,帮助开发者实现TCP/IP网络通信功能。使用这个库可以简化开发工作,提高效率,同时为开发者提供了一个可靠的通信框架。 ### 回答3: ucos-iii-tcpip.a是一个软件库,主要用于嵌入式系统中实现TCP/IP网络协议栈。它基于Micrium公司开发的uC/OS-III实时操作系统,提供了一套完整的TCP/IP协议栈的实现。该软件库的目标是为嵌入式系统开发人员提供一个方便快捷且可靠的TCP/IP协议栈,使他们能够更容易地将网络功能集成到嵌入式应用中。 ucos-iii-tcpip.a具有多线程处理能力,能够同时处理多个网络连接和数据包,提供了 TCP、UDP、IP、ICMP等网络协议的实现。它支持IPv4和IPv6协议,可以实现IP地址的分配和路由功能,支持网络数据包的传输和接收,还提供了socket API接口供应用程序调用。 使用ucos-iii-tcpip.a,嵌入式系统开发人员可以方便地添加网络功能到他们的嵌入式应用中,无需从头开始开发TCP/IP协议栈,大大加快开发进度。在嵌入式系统中,网络功能往往是非常重要的,通过使用ucos-iii-tcpip.a,开发人员可以轻松实现远程控制、数据采集、与外部设备通信等功能。 总之,ucos-iii-tcpip.a是一个基于uC/OS-III实时操作系统的TCP/IP协议栈软件库,提供了完整的TCP/IP协议栈的实现,方便嵌入式系统开发人员快速添加网络功能到他们的应用中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值