Android View绘制5 Draw过程(中)

一 概述

上篇分析了自定义 View 绘制流程及其常用方法:Android View绘制4 Draw过程(上),本篇将以硬件加速绘制与软件绘制入口为切入点,通过本篇文章,你将了解到:

  • 硬件加速
  • 硬件加速绘制与软件绘制分道扬镳的地方
  • 认识 LayerType

二 硬件加速

Android 3.0 之前,绘制操作通过 CPU 完成,而在 Android 3.0(含)之后,Android 2D 渲染管道支持硬件加速,也就是说 Canvas 绘制操作会使用 GPU 渲染。
在这里插入图片描述

2.1 硬件加速的作用

使用软件绘制的时候,绘制操作都是通过 CPU 计算并写入 Bitmap,最终 Bitmap 直接渲染到屏幕上。当某个 View 需要刷新的时候,计算刷新的脏区域,有相交的地方都需要重新绘制,也就是重走 Draw 过程。

使用硬件绘制的时候,绘制操作先将操作记录到 RenderNode 里,当渲染的时候将这些操作集合交给 GPU 处理,GPU 更擅长处理浮点相关的运算。

当某个 View 需要刷新的时候,只需要重新生成与之相关的操作指令集,也就是 Draw 过程。甚至当修改透明度等属性的时候都不需要重走 Draw 过程,大大减少了无效的绘制请求,节约了 CPU 时间,提升程序运行流畅度。

当然,硬件加速需要更多资源,因此应用会占用更多内存。另外有些 Canvas API 并不支持硬件加速,具体请参考官方文档:https://developer.android.google.cn/guide/topics/graphics/hardware-accel

2.2 硬件加速的开启与关闭

2.2.1 硬件加速控制层级

硬件加速分为4个层级来控制,分别为:

  • Application
  • Activity
  • Window
  • View

Application

在 Application 标签下,添加如下字段:

//关闭硬件加速
    <application
        android:hardwareAccelerated="false">
    </application>

//开启应将加速
    <application
        android:hardwareAccelerated="true">
    </application>

Android 4.0(含)之后默认开启硬件加速,也就是:默认 android:hardwareAccelerated=“true”

Activity

在 Activity 标签下,添加如下字段:

//关闭硬件加速
  <activity android:hardwareAccelerated="false">
  </activity>

//开启硬件加速
  <activity android:hardwareAccelerated="true">
  </activity>

和 Application 一样,Activity 也是默认开启硬件加速。

Window

//开启硬件加速
    getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
   

目前来说,无法直接关闭 Window 的硬件加速,后续解释。

View

//禁用硬件加速
    View.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

目前来说,无法直接开启 View 的硬件加速,后续解释。

2.2.2 各个层级的关系

按照控制的范围来说:Application>Activity>Window>View。来看看各个层级开关硬件加速之间的相互影响。

当 Application 开启了硬件加速

  • Activity/Window/View 也开启了硬件加速
  • 当然,Activity/View 可以选择关闭硬件加速

当 Application 关闭了硬件加速

  • Activity/Window 也关闭了硬件加速
  • 当然,Activity/Window 可以单独开启硬件加速

当 Activity 开启了硬件加速

  • Window/View 也开启了硬件加速
  • 当然,View 也可以选择关闭硬件加速

当 Activity 关闭了硬件加速

  • View 也关闭了硬件加速

也许你还是觉得比较疑惑,尤其是针对 Window 和 View,到底怎么控制这两个层级的硬件加速呢?前面说过,硬件加速用在绘制上,而绘制的核心是 Canvas,Canvas 分为支持硬件加速的 Canvas:RecordingCanvas 和普通 Cavas。因此只需要找到什么时候用 RecordingCanvas,就知道是否支持了硬件加速。循着这个点,接下来分析代码里是如何控制硬件绘制与软件绘制的。

三 硬件加速绘制与软件绘制分道扬镳的地方

我们知道当 View 添加到 Window 后,会调用 ViewRootImpl->setView() 方法。

ViewRootImpl.java

    public void setView(View view, WindowManager.LayoutParams attrs,
        View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                ......
                //mSurfaceHolder 初始为空
                if (mSurfaceHolder == null) {
                    //使能硬件加速
                    enableHardwareAcceleration(attrs);
                }
                ......
            }
        }
    }

    private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
        //初始标记
        mAttachInfo.mHardwareAccelerated = false;
        mAttachInfo.mHardwareAccelerationRequested = false;
        
        //判断是否开启硬件加速
        final boolean hardwareAccelerated = (attrs.flags &
            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;

        if (hardwareAccelerated) {
            //判断渲染是否可用
            if (!ThreadedRenderer.isAvailable()) {
                return;
            }

            //默认没有设置该标记
        final boolean fakeHwAccelerated = (attrs.privateFlags &
        WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
        final boolean forceHwAccelerated = (attrs.privateFlags &
        WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;

            if (fakeHwAccelerated) {
                mAttachInfo.mHardwareAccelerationRequested = true;
            } else if (!ThreadedRenderer.sRendererDisabled
                    || (ThreadedRenderer.sSystemRendererDisabled &&
                    forceHwAccelerated)) {
                if (mAttachInfo.mThreadedRenderer != null) {
                    mAttachInfo.mThreadedRenderer.destroy();
                }                
                //创建渲染线程 ThreadedRenderer
                //并赋值给mAttachInfo->mThreadedRenderer
                mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext,
                translucent, attrs.getTitle().toString());

                if (mAttachInfo.mThreadedRenderer != null) {
                    //将mAttachInfo->mHardwareAccelerated 标记位true
                    mAttachInfo.mHardwareAccelerated =
                            mAttachInfo.mHardwareAccelerationRequested = true;
                }
            }
        }
    }

以上代码重点关注两个点:

  • 硬件加速标记存放在 WindowManager.LayoutParams 的 flags 参数里
  • 如果支持硬件加速,则将标记与渲染线程对象记录到 View.AttachInfo 里

我们知道 View 展示的三大流程是在 performTraversals() 里调用的,在该方法里,依次进行 Measure、Layout、Draw 过程,来看看 Draw 过程的开启:

ViewRootImpl.java

    private void performTraversals() {
        //若有必要初始化渲染线程
        hwInitialized = mAttachInfo.mThreadedRenderer.initialize(
                mSurface);
        //1、Measure
        ......
        //2、Layout
        ......
        //3、Draw
        performDraw();
    }

    private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        ......
        if (mAttachInfo.mThreadedRenderer != null &&
            mAttachInfo.mThreadedRenderer.isEnabled()) {
            //mAttachInfo.mThreadedRenderer.isEnabled()->true 前边初始化过了
            //硬件加速绘制入口
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
        } else {
            //软件绘制入口
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                    scalingRequired, dirty, surfaceInsets)) {
                return false;
            }
        }

        return useAsyncReport;
    }

以上代码重点关注两个点:

  • 在 ViewRootImpl->draw() 方法里开始了绘制的分歧
  • 分歧的依据是 mAttachInfo.mThreadedRenderer != null

因此现在的问题简化为:WindowManager.LayoutParams 里的 flags 硬件加速标记决定绘制是否走硬件加速流程。WindowManager.LayoutParams 从哪来?回顾一下 View 是如何添加到 Window 的:

  • 获取 WindowManager 对象
  • 设置 LayoutParams 属性
  • 将 View 添加到 Window 里

当调用:

wm.addView(textView, layoutParams);

后续调用如下:
在这里插入图片描述
可以看出,layoutParams 经过层层传递最后到达 ViewRootImpl 里的 setView(),也即是上边分析的方法。

通过分析代码,发现只有 WindowManagerGlobal addView() 会给 layoutParams.flags 赋值:

WindowManagerGlobal.java

    public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {
        ......
        final WindowManager.LayoutParams wparams = 
            (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            //该分支是Activity、Dialog addView() 会走
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            //该分支是直接WindowManager.addView()
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                    & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                //(context.getApplicationInfo().flags 是在Application 层级设置的标记
                //如果设置了该标记,那么将该标记存储在wparams
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        ......
        synchronized (mLock) {
            try {
                ...
                //此时,wparams 记录了是否有硬件加速
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
            }
        }
    }

继续来看 adjustLayoutParamsForSubWindow:

    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        ......
        //1、mHardwareAccelerated 表示该 Activity 是否支持硬件加速
        // 该值是由 Activity 在 xml 里配置的硬件加速标记决定的
        //2、mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED)
        // 表示之前是否给 Window 设置了硬件加速标记
        if (mHardwareAccelerated ||
                (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
            //如果满足,记录在WindowManager.LayoutParams 里
            wp.flags |= FLAG_HARDWARE_ACCELERATED;
        }
    }

从以上两段代码可以看出:

  • Window 与 Activity 关联,如果该 Activity 开启了硬件加速,那么该 Window 也开启了硬件加速。
  • Window 不与 Activity 关联,如果 Application 开启了硬件加速,那么该 Window 也开启了硬件加速。

要关闭 Window 层级的硬件加速,只需要 Activity/Application 禁用硬件加速即可。

实际上,不管 Activity/Application/Window 如何设置硬件加速标记,都是反馈到 WindowManager.LayoutParams 上,进而反馈到 View.AttachInfo,最终反馈到 Canvas,用图表示其中的关系:

在这里插入图片描述

四 初步认识LayerType

在所有 Android 版本中,视图能够通过以下两种方式渲染到屏幕外缓冲区(离屏缓存):

  • Canvas.saveLayer()
  • 使用视图的绘制缓存 。

屏幕外缓冲区或层具有多种用途。在为复杂的视图添加动画效果或应用合成效果时,我们可以使用离屏缓存获得更好的效果。

此处介绍第二种:View 是通过 Canvas 绘制的,而 Canvas 既可以硬件加速绘制也可以软件绘制,于是 View 同样就拥有了这两种选择。

通过前面的分析可以看出,只要 Application/Activity/Window 开启了硬件加速,那么View 也就开启了硬件加速,那么如何关闭 View 的硬件加速呢?

View.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

该方法简化如下:

View.java

    int mLayerType = LAYER_TYPE_NONE;
    public void setLayerType(@LayerType int layerType,
        @android.annotation.Nullable Paint paint) {
        ......
        mLayerType = layerType;
        ......
    }

    public int getLayerType() {
        return mLayerType;
    }

View 里用 Int 表示 LayerType,可以理解为绘制离屏缓存。

  • LAYER_TYPE_NONE–> 不使用绘制缓存
  • LAYER_TYPE_SOFTWARE–> 使用软件绘制缓存
  • LAYER_TYPE_HARDWARE–> 使用硬件绘制缓存

当使用 LAYER_TYPE_SOFTWARE 时,就"顺道"禁用了硬件加速,因此View.setLayerType(View.LAYER_TYPE_SOFTWARE, null) 产生了两个作用:

  • 启用软件绘制缓存
  • 禁用硬件加速

软件绘制、硬件加速绘制、启用离屏缓存三者对于 Draw 流程的影响将会在下篇分析,敬请关注。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android的视图绘制Android应用程序的重要部分,它涉及到将用户界面元素绘制到屏幕上。以下是Android视图绘制的基本流程: 1. 触发绘制:当应用程序启动、布局发生变化或者手动调用 `invalidate()` 方法时,会触发视图绘制。 2. 测量布局:在绘制之前,Android会测量每个视图的大小。这个过程称为“测量布局”。测量布局是为了确定每个视图在屏幕上的位置和大小。 3. 布局:一旦测量完成,Android会根据视图的测量结果进行布局,确定每个视图在屏幕上的位置。 4. 绘制:布局完成后,Android会调用每个视图的 `draw()` 方法进行绘制。在 `draw()` 方法,视图会绘制自己的内容,包括背景、文字、图片等。 5. 绘制层次:视图的绘制按照层次结构进行,即从父视图到子视图的顺序。父视图会先绘制自己,然后再绘制子视图。 6. 递归绘制:当父视图绘制完成后,它会递归地调用子视图的 `draw()` 方法,依次完成整个视图树的绘制过程。 7. 绘制缓存:为了提高绘制性能,Android使用了绘制缓存。绘制缓存可以将视图的绘制结果保存起来,在下次绘制时直接使用缓存,而不需要重新执行绘制操作。 总结来说,Android的视图绘制过程包括测量布局、布局、绘制绘制缓存。通过这个过程Android应用程序可以将用户界面元素绘制到屏幕上,实现丰富多样的交互效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值