Android硬件加速

722 阅读6分钟

Android硬件加速

最近项目中遇到了因为硬件加速引起的一些问题,故这里深入学习了解一下关于硬件加速的一些东西

背景

什么是硬件加速?

硬件加速是Android系统在绘制图形时采取的一种方式。

图形的绘制,本质上就是界面的渲染。在渲染界面的时候,是要经过一系列计算的,这部分计算通常是逻辑较简单,但数据量庞大的浮点运算。

在操作系统层面,有一个东西叫中央处理器——CPU,他是计算机设备的核心器件之一,主要功能是解释计算机指令以及处理计算机软件中的数据。除此之外,计算机还有一个器件,叫做图形处理器——GPU,他类似于CPU,但是是专门为运行绘图运算的微处理器。

那么CPU和GPU的区别在哪里呢?

  • CPU内部算数逻辑单元(ALU)较少,控制器较复杂,适合进行复杂的逻辑运算
  • GPU控制器简单,但是包含了较多的算数逻辑单元,可并行运行大量计算

结果显而易见,因为界面渲染的计算是逻辑简单但是数据量很大的浮点运算,所以如果使用CPU来对界面渲染做运算,效果自然比不了GPU。

所以,硬件加速绘制图形是一般会采用的软件绘制就是由CPU来绘制的。硬件加速,就是通过底层代码,将CPU中一部分不擅长的图形计算转换成GPU专用指令,然后交给GPU来完成。而对于Android来讲,硬件加速就是将View的绘制工作从原来的CPU转交给GPU来做。

原理

硬件绘制之所以比软件绘制“快速”,除了如上所述的奖一部分计算量交给更适合的硬件来做外,还有一个很重要的原因在于绘制区域即绘制内容的选择不一样。

在关闭了硬件加速,即采用软件绘制时,绘制区域是这样获取的:从要执行 invalidate() 方法的View开始,遍历从跟View开始的整个View结构,标记出需要重新绘制的 脏区域。在这个过程中,除了我们直接修改的View需要绘制外,其他的所有View,都可能因为遮盖、相交等原因,被标记为需要绘制,这样一来绘制的区域就会变的很大。这样一来一旦开始绘制,搞不好会有很多“无辜”的View也被重新绘制,虽然这些View未必真的需要被重绘。

而采用硬件加速时,就完全不一样了。硬件绘制,首先将View抽象为 RenderNode 节点,将对View的绘制,抽象为 DrawOp ,每个View不仅持有自己的绘制操作 DrawOp 组成的List,还持有其子View的绘制入口,而 DrawOp 中保存有对应的 OpenGL 绘制命令,这样便形成了一个完整的树状结构。其次,硬件绘制是直接交给一个Render线程来执行绘制的,而不是主线程,这样也缓解了主线程的部分压力。最后,在进行实际绘制时,每个View的实际绘制操作对应于 DrawOp ,在绘制时只需更新其中保存的绘制命令,即可完成这个View单独的绘制,而不会影响到其他View。

问题

虽然硬件加速有很多优点,但是也有许多坑。

首先,一些Api方法是不支持硬件加速的:

其次,在使用Webview时,如果启用了硬件加速,那么有时会出现花屏、闪烁等异常状况。

最后,正如前面说的,由于不支持一些Api,所以在做自定义View时,有可能因为开启硬件加速导致View的绘制效果不理想。

如何使用

一开始Android是默认关闭硬件加速的。从Android4.0版本开始,默认是开启了硬件加速的。硬件加速固然有很多优点,但是由于种种原因(系统设计、历史遗留、以及自身的局限性)导致在有些情况会出现一些问题,这个时候又需要我们手动关闭了。

硬件加速的开关分为四个级别,分别为App级别、Activity级别、Window级别以及View级别。

  • App级别:直接在 AndroidManifest.xml 文件中, <application> 标签下加入一个属性,属性值为 true 为开启, false 为关闭:
<application android:hardwareAccelerated="true">
  • Activity级别:类似于App级别,在 <activity> 标签下加入同样的属性:
<activity android:hardwareAccelerated="true">
  • Window级别: 在Window级别,只能通过Java代码形式动态的开启硬件加速而不能关闭:
getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
  • View级别:View层比较特殊,这里只允许关闭硬件加速,而无法开启。而且相关的接口并不是专门用来做硬件加速开关的,而是在给View设置Layer时“顺便”关闭了硬件加速:
view.setLayerType(LAYER_TYPE_SOFTWARE, null); 

这个方法只是给View设置了一个LayerType,而且参数有三种:LAYER_TYPE_SOFTWARE LAYER_TYPE_HARDWARE LAYER_TYPE_NONE,这是什么意思呢?关于这一部分,在官网中有详细的解释:

You currently cannot enable hardware acceleration at the view level. View layers have other functions besides disabling hardware acceleration. See View layers for more information about their uses.

官方说在View层只能关闭,不能开启,至于为什么,引用一段扔物线大佬的解释:

setLayerType() 这个方法,它的作用其实就是名字里的意思:设置 View Layer 的类型。所谓 View Layer,又称为离屏缓冲(Off-screen Buffer),它的作用是单独启用一块地方来绘制这个 View ,而不是使用软件绘制的 Bitmap 或者通过硬件加速的 GPU。这块「地方」可能是一块单独的 Bitmap,也可能是一块 OpenGL 的纹理(texture,OpenGL 的纹理可以简单理解为图像的意思),具体取决于硬件加速是否开启。采用什么来绘制 View 不是关键,关键在于当设置了 View Layer 的时候,它的绘制会被缓存下来,而且缓存的是最终的绘制结果,而不是像硬件加速那样只是把 GPU 的操作保存下来再交给 GPU 去计算。通过这样更进一步的缓存方式,View 的重绘效率进一步提高了:只要绘制的内容没有变,那么不论是 CPU 绘制还是 GPU 绘制,它们都不用重新计算,而只要只用之前缓存的绘制结果就可以了。

所以,如果给View设置了Layer,且值为SOFTWARE,那么就是用软件来做View Layer,自然就关闭了硬件加速。而如果硬件加速已经关闭,参数HARDWARE的作用跟SOFTWARE一样,自然也无法开启硬件加速。而值为NONE时,直接就关闭了ViewLayer,所以在View层只能关闭、不能开启,正如官方文档所说:

  • LAYER_TYPE_NONE: The view is rendered normally and is not backed by an off-screen buffer. This is the default behavior.
  • LAYER_TYPE_HARDWARE: The view is rendered in hardware into a hardware texture if the application is hardware accelerated. If the application is not hardware accelerated, this layer type behaves the same as LAYER_TYPE_SOFTWARE.
  • LAYER_TYPE_SOFTWARE: The view is rendered in software into a bitmap.