View系列:硬件加速

本文为过往笔记整理, 在此只作记录,不做严谨的技术分享。

所谓硬件加速,指的是把某些计算工作交给专门的硬件来做,而不是和普通的计算工作一样交给 CPU 来处理。这样不仅减轻了 CPU 的压力,而且由于有了「专人」的处理,这份计算工作的速度也被加快了。这就是「硬件加速」。

而对于 Android 来说,硬件加速有它专属的意思:在 Android 里,硬件加速专指把 View 中绘制的计算工作交给 GPU 来处理。进一步地再明确一下,这个「绘制的计算工作」指的就是把绘制方法中的那些 Canvas.drawxxx() 变成实际的像素这件事。

  • Android 3.0(API 11)开始,2D 渲染管道支持硬件加速,在 View 画布上执行的所有绘制操作都会使用 GPU
  • 如果目标 API 为 14 及更高级别,则硬件加速默认启用
  • 如果应用仅使用标准视图和 Drawable,则全局启用硬件加速不会造成任何不良绘制效果。
  • 不过,并非所有 2D 绘制操作都支持硬件加速,因此启用硬件加速可能会影响部分自定义视图或绘制调用。
  • Android 允许在多个级别选择是启用还是停用硬件加速。
  • 启用硬件加速需要更多资源,因此应用会占用更多内存

控制硬件加速

App

 <application android:hardwareAccelerated="true" ...>
复制代码

Activity

    <application android:hardwareAccelerated="true">
        <activity ... />
        <activity android:hardwareAccelerated="false" />
    </application>
复制代码

Window

目前在窗口级别无法停用硬件加速

    //开启
	getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
复制代码

View

目前在视图级别无法启用硬件加速

    myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);// 关闭
复制代码

确定是否经过硬件加速

  • 如果 View 已附加到硬件加速窗口,则 **View.isHardwareAccelerated() **会返回 true。
  • 如果 Canvas 经过硬件加速,则**Canvas.isHardwareAccelerated() **会返回 true

不受支持的绘制操作

绘制模型(原理)

  • 软件绘制:使用CPU绘制到Bitmap,然后把Bitmap渲染到屏幕
  • 硬件绘制:CPU把绘制内容转换成GPU操作,由GPU负责真正的绘制

在硬件加速关闭的时候:

Canvas绘制的工作方式是:把要绘制的内容写进一个 Bitmap,在之后的渲染过程中,这个 Bitmap的像素内容被直接渲染到屏幕
这种绘制方式的主要计算工作在于把绘制操作转换为像素的过程,这个过程的计算是由 **CPU **来完成的。大致就像这样:

img

在硬件加速开启时:

Canvas的工作方式改变了:CPU只是把绘制的内容转换为GPU 的操作保存了下来,然后就把它交给 GPU,最终由 GPU 来完成实际的显示工作。大致是这样:

img

硬件加速更快的原因:

  • GPU分摊了工作,并且GPU计算效率更快
  • 绘制机制的改变,导致界面内容改变时的刷新效率极大提高。
    (invalidate()时,其父View到顶层View不再都刷新)

硬件加速缺点:

兼容性问题
由于使用GPU(暂时)无法完成某些绘制,因此对于一些特定的API,需要关闭硬件加速,转回到使用CPU进行绘制

  • 自定义绘制时,参看api兼容性
  • 所有的原生自带控件,都没有用到 API 版本不兼容的绘制操作,可以放心使用

离屏缓冲

从 Android 3.0(API 级别 11)开始,可以通过 View.setLayerType()方法更好地控制如何及何时使用层。视图可以使用以下三种层类型之一:

  • LAYER_TYPE_NONE:视图正常渲染,不受屏幕外缓冲区支持。这是默认行为。
  • LAYER_TYPE_HARDWARE:如果应用经过硬件加速,视图在硬件中渲染为硬件纹理。如果应用未经过硬件加速,此层类型的行为方式与 LAYER_TYPE_SOFTWARE 相同。
  • LAYER_TYPE_SOFTWARE:使用软件来渲染视图,绘制到Bitmap。并顺便关闭硬件加速

所谓 View Layer,又称为离屏缓冲(Off-screen Buffer),它的作用是单独启用一块地方来绘制这个 View ,而不是使用软件绘制的 Bitmap 或者通过硬件加速的 GPU

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

基于这样的原理,在进行移动、旋转等**无需调用 invalidate()**的属性动画的时候,开启 Hardware Layer 将会极大地提升动画的效率,因为在动画过程中 View 本身并没有发生改变,只是它的位置或角度改变了,而这种改变是可以由 GPU 通过简单计算就完成的,并不需要重绘整个 View。所以在这种动画的过程中开启 Hardware Layer,可以让本来就依靠硬件加速而变流畅了的动画变得更加流畅

注意:只有在对 translationX 、translationY、 rotation、 alpha 等无需调用 invalidate() 的属性做动画的时候,这种方法才适用。因为这种方法本身利用的就是当界面不发生时,缓存未更新所带来的时间的节省。所以简单地说 这种方式不适用于基于自定义属性绘制的动画

总结

  • setLayerType(LAYER_TYPE_SOFTWARE), “顺带”关闭硬件加速
  • 关键点:translationX、 translationY 、rotation 、alpha 等无需调用 invalidate() 的属性做动画时,才适用
  • 不适用于基于自定义属性绘制的动画

使用

view.setLayerType
    // 开启离屏缓存(软件渲染位图,并关闭硬件加速)
	myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
	// 开启离屏缓存(硬件渲染位图,此时不一定正在硬件加速奥)
	myView.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();


	// withLayer() 可以自动完成上面这段代码的复杂操作
	view.animate()
        .rotationY(90)
        .withLayer(); 
复制代码
class RoundRectCoverView(context: Context, attrs: AttributeSet) : View(context, attrs) {
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val porterDuffXfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OUT)
  
    init {
        //开启View级别的离屏缓冲,并关闭硬件加速,使用软件绘制
        setLayerType(LAYER_TYPE_SOFTWARE, null)
    }
    override fun onDraw(canvas: Canvas) {
        canvas.drawRoundRect(mPadding, mPadding, width - mPadding, 
                             height - mPadding, mRoundCorner, mRoundCorner, paint)
        paint.color = mCoverColor
        paint.xfermode = porterDuffXfermode
        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
        paint.xfermode = null
    }
复制代码
canvas.saveLayer
    override fun onDraw(canvas: Canvas) {
         //Canvas的离屏缓冲
          val count = canvas.saveLayer(bounds, paint)
          canvas.withSave {
              canvas.drawColor(mCoverColor)
              canvas.clipPath(clipPath)
              canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC)
          }
          //把离屏缓冲的内容,绘制到View上去
          canvas.restoreToCount(count)
    }
复制代码

对比:

  • setLayerType: 把整个View都绘制在离屏缓冲中
  • saveLayer:把部分View内容绘制在离屏缓冲中(优先使用该方式)

硬件加速与离屏渲染的关系

硬件加速和离屏渲染分别是两个概念,只是有关系而已

  • 硬件加速:把 View 中绘制的计算工作交给 GPU 来处理,并显示出来
  • 离屏渲染:有没有开启硬件加速,都会单独启用一块地方来绘制 View
    • LAYER_TYPE_SOFTWARE:使用软件来渲染视图,绘制到Bitmap。并顺便关闭硬件加速
    • LAYER_TYPE_HARDWARE:
      • 如果应用经过硬件加速,视图在硬件中渲染为硬件纹理。
      • 如果应用未经过硬件加速,此层类型的行为方式与 LAYER_TYPE_SOFTWARE 相同。

使用

硬件加速,可充分利用GPU的特性,使得界面渲染更加平滑。
但是硬件加速自身并非完美,会消耗更多内存,而且代码不当,会引发闪屏、花屏、错位等渲染问题

无论是硬件加速还是离屏缓冲,都没有完美的方案使用,只能根据不同的情况去选择使用哪一种方式来提升性能:

  • 硬件加速:支持类型的Api时使用
  • 离屏缓冲:View内容无变化,无需调用invalidate()方法时使用

自定义View

硬件加速默认开启,注意兼容不支持API

不兼容问题:

使用Paint的setPathEffect(PathEffect effect)设置路径效果(API 28开始支持)时,开启硬件加速导致绘制异常。解决办法:关闭硬件加速,或者使用离屏缓冲(SOFTWARE类型)

属性动画

动画开始、结束时控制离屏缓冲的开启与关闭

使用硬件加速,提高Android的动画性能

Webview

WebView视图被整体遮住一块,然后突然恢复时(比如使用SlideMenu将WebView从侧边滑出来时),这个过渡期会出现白块同时界面闪烁

**解决方法:**是在过渡期前将WebView的硬件加速临时关闭,过渡期后再开启

播放视频,有2种方案:

  1. 对播放视频的webview单独开硬件加速

  2. 调用外部原生播放器单独播放视频

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享