Android 自定義View之Draw過程(中)

前言

Draw 過程系列文章

上篇分析了自定義View繪制流程及其常用方法:
Android 自定義View之Draw過程(上)
本篇將以硬件加速繪制與軟件繪制入口為切入點(diǎn),通過本篇文章,你將了解到:

1、什么是硬件加速
2、硬件加速的開啟與關(guān)閉
3、硬件加速繪制與軟件繪制分道揚(yáng)鑣的地方
4、初步認(rèn)識LayerType

什么是硬件加速

Android 3.0 之前,繪制操作通過CPU完成的,而在Android 3.0(含)之后,Android 2D 渲染管道支持硬件加速,也就是說Canvas繪制操作會使用GPU渲染。


image.png

硬件加速的作用

使用軟件繪制的時(shí)候,繪制操作都是通過CPU計(jì)算并寫入Bitmap,最終Bitmap直接渲染到屏幕上。當(dāng)某個(gè)View需要需要刷新的時(shí)候,計(jì)算刷新的臟區(qū)域,有相交的地方都需要重新繪制,也就是重走Draw過程。

使用硬件繪制的時(shí)候,繪制操作先將操作記錄到RenderNode里,當(dāng)渲染的時(shí)候?qū)⑦@些操作集合交給GPU處理,GPU更擅長處理浮點(diǎn)相關(guān)的運(yùn)算。
當(dāng)某個(gè)View需要刷新的時(shí)候,只需要重新生成與之相關(guān)的操作指令集,也就是Draw過程。甚至當(dāng)修改透明度等屬性的時(shí)候都不需要重走Draw過程,大大減少了無效的繪制請求,節(jié)約了CPU時(shí)間,提升程序運(yùn)行流暢度。

當(dāng)然,硬件加速需要更多資源,因此應(yīng)用會占用更多內(nèi)存。
另外有些Canvas API并不支持硬件加速,具體請參考官方文檔:https://developer.android.google.cn/guide/topics/graphics/hardware-accel

硬件加速的開啟與關(guān)閉

硬件加速控制層級

硬件加速分為4個(gè)層級來控制,分別為:

1、Application
2、Activity
3、Window
4、View

Application

在Application標(biāo)簽下,添加如下字段:

//關(guān)閉硬件加速
    <application
        android:hardwareAccelerated="false">
    </application>
或
//開啟應(yīng)將加速
    <application
        android:hardwareAccelerated="true">
    </application>

Android 4.0(含)之后默認(rèn)開啟硬件加速,也就是:
默認(rèn) android:hardwareAccelerated="true"

Activity

在Activity 標(biāo)簽下,添加如下字段:

//關(guān)閉硬件加速
  <activity android:hardwareAccelerated="false">
  </activity>
或
//開啟硬件加速
  <activity android:hardwareAccelerated="true">
  </activity>

和Application一樣,Activity也是默認(rèn)開啟硬件加速。

Window

//開啟硬件加速
    getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
   

目前來說,無法直接關(guān)閉Window的硬件加速。(1)后續(xù)解釋。

View

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

目前來說,無法直接開啟View的硬件加速。(2)后續(xù)解釋。

各個(gè)層級關(guān)系

按照控制的范圍來說:
Application>Activity>Window>View
來看看各個(gè)層級開關(guān)硬件加速之間的相互影響。

1、當(dāng)Application 開啟了硬件加速
Activity/Window/View 也開啟了硬件加速
當(dāng)然,Activity/View 可以選擇關(guān)閉硬件加速

2、當(dāng)Application 關(guān)閉了硬件加速
Activity/Window 也關(guān)閉了硬件加速
當(dāng)然,Activity/Window 可以單獨(dú)開啟硬件加速

3、當(dāng)Activity 開啟了硬件加速
Window/View 也開啟了硬件加速
當(dāng)然,View 也可以選擇關(guān)閉硬件加速

4、當(dāng)Activity 關(guān)閉了硬件加速
View 也關(guān)閉了硬件加速

也許你還是覺得比較疑惑,尤其是針對Window和View,到底怎么控制這兩個(gè)層級的硬件加速呢?
前面說過,硬件加速用在繪制上,而繪制的核心是Canvas,Canvas分為支持硬件加速的Canvas:RecordingCanvas和普通Cavas。因此只需要找到什么時(shí)候用
RecordingCanvas,就知道是否支持了硬件加速。
循著這個(gè)點(diǎn),接下來分析代碼里是如何控制硬件繪制與軟件繪制的。

硬件加速繪制與軟件繪制分道揚(yáng)鑣的地方

當(dāng)View 添加到Window后,會調(diào)用ViewRootImpl->setView(xx)方法。
關(guān)于過程細(xì)節(jié)請移步:Window/WindowManager 不可不知之事

#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) {
        //初始標(biāo)記
        mAttachInfo.mHardwareAccelerated = false;
        mAttachInfo.mHardwareAccelerationRequested = false;
        
        //判斷是否開啟硬件加速
        final boolean hardwareAccelerated =
                (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;

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

            //默認(rèn)沒有設(shè)置該標(biāo)記
            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();
                }
                
                //創(chuàng)建渲染線程 ThreadedRenderer
                //并賦值給mAttachInfo->mThreadedRenderer
                mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
                        attrs.getTitle().toString());

                if (mAttachInfo.mThreadedRenderer != null) {
                    //將mAttachInfo->mHardwareAccelerated 標(biāo)記位true
                    mAttachInfo.mHardwareAccelerated =
                            mAttachInfo.mHardwareAccelerationRequested = true;
                }
            }
        }
    }

以上代碼重點(diǎn)關(guān)注兩個(gè)點(diǎn):

1、硬件加速標(biāo)記存放在WindowManager.LayoutParams 的flags參數(shù)里
2、如果支持硬件加速,則將標(biāo)記與渲染線程對象記錄到View.AttachInfo里

我們知道View展示三大流程是在 performTraversals(xx)里,在該方法里,依次進(jìn)行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;
    }

以上代碼重點(diǎn)關(guān)注兩個(gè)點(diǎn):

1、在ViewRootImpl->draw(xx)方法里開始了繪制的分歧
2、分歧的依據(jù)是View.AttachInfo != null

因此現(xiàn)在的問題簡化為:

WindowManager.LayoutParams里的flags 硬件加速標(biāo)記決定繪制是否走硬件加速流程

WindowManager.LayoutParams 從哪來

回顧一下View是如何添加到Window的:

1、獲取WindowManager對象
2、設(shè)置LayoutParams屬性
3、將View添加到Window里

當(dāng)調(diào)用:

wm.addView(textView, layoutParams);

后續(xù)調(diào)用如下:


image.png

可以看出,layoutParams 經(jīng)過層層傳遞最后到達(dá)ViewRootImpl里的setView(xx),也即是上邊分析的方法。
因此,我們有理由相信,layoutParams.flags 有關(guān)硬件加速的標(biāo)記一定在某個(gè)步驟被賦值了。
首先第一個(gè)可能賦值的地方:

layoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
wm.addView(textView, layoutParams);

此過程即為為Window 開啟硬件加速。

再來看看剩下的步驟,發(fā)現(xiàn)只有WindowManagerGlobal addView(xx)會給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(xx) 會走
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            //該分支是直接WindowManager.addView(xx)
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                    & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                //(context.getApplicationInfo().flags 是在Application 層級設(shè)置的標(biāo)記
                //如果設(shè)置了該標(biāo)記,那么將該標(biāo)記存儲在wparams
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
        ...
        synchronized (mLock) {
            try {
                ...
                //此時(shí),wparams 記錄了是否有硬件加速
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
            }
        }
    }

繼續(xù)來看看adjustLayoutParamsForSubWindow:

    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        ...
        //1、mHardwareAccelerated 表示該Window是否支持硬件加速,該值是由Activity 在xml里配置的硬件加速標(biāo)記決定的
        //2、mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) 表示之前是否給Window 設(shè)置了硬件加速標(biāo)記
        if (mHardwareAccelerated ||
                (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
            //如果滿足,記錄在WindowManager.LayoutParams 里
            wp.flags |= FLAG_HARDWARE_ACCELERATED;
        }
    }

從以上兩段代碼可以看出:

1、Window與Activity關(guān)聯(lián),如果該Activity開啟了硬件加速,那么該Window也開啟了硬件加速。
2、Window不與Activity 關(guān)聯(lián),如果Application 開啟了硬件加速,那么該Window也開啟了硬件加速。

要關(guān)閉Window層級的硬件加速,只需要Activity/Application 禁用硬件加速即可。

實(shí)際上,不管Activity/Application/Window 如何設(shè)置硬件加速標(biāo)記,都是反饋到WindowManager.LayoutParams 上,進(jìn)而反饋到View.AttachInfo,最終反饋到Canvas
用圖表示其中的關(guān)系:

image.png

初步認(rèn)識LayerType

在所有 Android 版本中,視圖能夠通過以下兩種方式渲染到屏幕外緩沖區(qū)(離屏緩存):

1、Canvas.saveLayer()
2、使用視圖的繪制緩存 。

屏幕外緩沖區(qū)或?qū)泳哂卸喾N用途。在為復(fù)雜的視圖添加動畫效果或應(yīng)用合成效果時(shí),我們可以使用離屏緩存獲得更好的效果。
此處介紹第二種:
View是通過Canvas繪制的,而Canvas既可以硬件加速繪制也可以軟件繪制,于是View同樣就擁有了這兩種選擇。
通過前面的分析可以看出,只要Application/Activity/Window 開啟了硬件加速,那么View 也就開啟了硬件加速,那么如何關(guān)閉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-->使用硬件繪制緩存

當(dāng)使用LAYER_TYPE_SOFTWARE時(shí),就"順道"禁用了硬件加速,因此View.setLayerType(View.LAYER_TYPE_SOFTWARE, null) 產(chǎn)生了兩個(gè)作用:

1、啟用軟件繪制緩存
2、禁用硬件加速

軟件繪制、硬件加速繪制、啟用離屏緩存 三者對于Draw流程的影響將會在下篇分析,敬請關(guān)注。
本文基于 Android 10.0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容