Android 圖形渲染應用層簡述

一. 圖形渲染方式

Android 圖像渲染有兩種方式一是 CPU 渲染, 另外一種是 GPU 渲染android

一) CPU 渲染

CPU 渲染稱之爲軟件繪製, Android CPU 渲染引擎框架爲 Skia, 它是一款在底端設備上呈現高質量的 2D 跨平臺圖形框架, Google 的 Chrome、Flutter 內部都有使用這個圖形渲染框架canvas

二) GPU 渲染

GPU 渲染稱之爲硬件繪製(即開啓硬件加速)緩存

1. OpenGL

市面上最經常使用於圖形渲染的引擎莫過於 OpenGL 了, Android 系統架構中的外部連接庫中有 OpenGL ES 的依賴, 而且提供了應用層的 API, 用於作高性能的 2D/3D 圖形渲染, Android 中對 OpenGL 的支持以下bash

OpenGL 版本支持

Android 版本 OpenGL ES 支持
Android 1.0 OpenGL ES 1.0、1.1
Android 2.2 OpenGL ES 2.0
Android 4.3 OpenGL ES 3.0
Android 5.0 OpenGL ES 3.1
Android 7.0 OpenGL ES 3.2

OpenGL API 支持

OpenGL API 支持

2. Vulkan

Android 7.0 以後除了添加 OpenGL ES3.2 的支持, 同時添加了 Vulkan 圖像引擎, Vulkan 是用於高性能 3D 圖形的低開銷、跨平臺 API, 它與 OpenGL 不一樣, 它被添加到 Android 運行時庫中, 目前支持面稍窄架構

二. 圖性渲染組件

Android 的圖形渲染是一個生產者消費者模型, Android 圖形渲染的組件結構圖以下app

圖形渲染模型

圖像生產者

生產者爲 Media Player 視頻解碼器, OpenGL ES 等產生的圖像緩存數據, 他們經過 BufferData 的形式傳遞到緩衝隊列中框架

圖像消耗者

圖像數據的消耗者主要是 SurfaceFlinger, 該系統服務會消耗 Surface 提供的數據緩衝流, 而且使用窗口管理器中提供的數據, 把他們合併輸出繪製到屏幕上ide

窗口管理器

控制窗口的 Android 系統服務,它是視圖容器。窗口老是由 Surface 提供支持。該服務會監督生命週期、輸入和聚焦事件、屏幕方向、轉換、動畫、位置、變形、Z-Order 以及窗口的其餘許多方面。窗口管理器會將全部窗口元數據發送到 SurfaceFlinger,以便 SurfaceFlinger 可使用該數據在顯示部分合成 Surface。性能

各個組件之間的映射關係

  • 畫筆: Skia 和 OpenGL, 咱們經過 Canvas API 進行繪製最終都會調用到外部連接庫的 Skia 和 OpenGL動畫

    • Skia: 2D 圖像繪製, 關閉硬件加速時使用該引擎
    • OpenGL: 2D/3D 圖像繪製, 開啓硬件加速時使用該引擎
  • 畫紙: Surface, Android 中全部的元素都在 Surface 這張畫紙上進行繪製

    • Window 是 View 的容器, 每一個 Window 會關聯一個 Surface
    • WindowManager 用於管理全部的 Window
      • 它將 Window 的 Surface 信息傳遞給 Graphic Buffer
      • 將 Window 其餘數據信息發送給 SurfaceFlinger
  • 畫板: Graphic Buffer, 它用於圖像數據的緩衝, 將數據發送給 SurfaceFlinger 進行繪製

    • 4.1 以前使用雙緩衝機制, 4.1 以後使用三緩衝機制
  • 顯示: SurfaceFlinger, 它將 WindowManager 提供的全部的 Surface, 經過硬件合成輸出到屏幕上

三. 圖像渲染流程

熟悉 View 繪製的三大流程可知, View 的繪製發起在 ViewRootImpl 的 performDraw 中, 咱們直接從這裏分析

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
            
    private void performTraversals(){
        if (!cancelDraw && !newSurface) {
            ......
            // 調用了 performDraw
            performDraw();
        } else {
            ......
        }
    }
    
    private void performDraw() {
          try {
            // 執行繪製
            boolean canUseAsync = draw(fullRedrawNeeded);
            ......
        } finally {
            ......
        }
    }
    
    
    private boolean draw(boolean fullRedrawNeeded) {
        ......
        final Rect dirty = mDirty;
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            // 若開啓了硬件加速, 則使用 OpenGL 的 ThreadedRenderer 進行繪製
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                ......
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
            } else {
                // 若咱們沒有開啓硬件加速, 則調用 drawSoftware
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }
        .......
    }
}
複製代碼

好的能夠看到, View 的繪製, 可能有兩種實現方式

  • 調用 ThreadedRenderer.draw() 進行 GPU 繪製
  • 調用 ViewRootImpl.drawSoftware() 進行 CPU 繪製

GPU 繪製又稱之爲硬件加速繪製, 在 Android 4.0 以後是系統默認開啓的, 咱們先分析硬件繪製原理

一) 硬件繪製

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
            
    private boolean draw(boolean fullRedrawNeeded) {
        ......
        final Rect dirty = mDirty;
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                ......
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
            } 
        }
        .......
    }
}
複製代碼

從 ViewRootImpl.draw 的代碼中, 咱們知道硬件繪製只有在 mAttachInfo 的 mThreadedRenderer 有效的狀況下才會執行

所以在想了解硬件繪製流程以前, 須要搞清楚 mThreadedRenderer 它是如何初始化而且賦值的, 也就是說硬件繪製是如何開啓的?

1. 硬件繪製的開啓

這須要從 ViewRootImpl 的建立提及, ViewRootImpl 是用於管理 View 樹與其依附 Window 的一個媒介, 當咱們調用 WindowManager.addView 時便會建立一個 ViewRootImpl 來管理即將添加到 Window 中的 View

public final class WindowManagerGlobal {

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ViewRootImpl root;
        synchronized (mLock) {
            // 建立了 ViewRootImpl 的實例
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            // 添加到緩存中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            try {
                // 將要添加的視圖添加到 ViewRootImpl 中
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                ......
            }
        }
    }
    
}

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
        
    final View.AttachInfo mAttachInfo;
    
    public ViewRootImpl(Context context, Display display) {
        ......
        // 構建了一個 View.AttachInfo 用於描述這個 View 樹與其 Window 的依附關係
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);
        ......
    }
    
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
         if (mSurfaceHolder == null) {
            // 根據 attrs 判讀是否開啓硬件加速
            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 (fakeHwAccelerated) {
                ......
            } else if (!ThreadedRenderer.sRendererDisabled
                    || (ThreadedRenderer.sSystemRendererDisabled && forceHwAccelerated)) {
                ......
                // 建立硬件加速的渲染器
                mAttachInfo.mThreadedRenderer = ThreadedRenderer.create(mContext, translucent,
                        attrs.getTitle().toString());
                mAttachInfo.mThreadedRenderer.setWideGamut(wideGamut);
                if (mAttachInfo.mThreadedRenderer != null) {
                    mAttachInfo.mHardwareAccelerated =
                            mAttachInfo.mHardwareAccelerationRequested = true;
                }
            }
        }
    }
}
複製代碼

好的, 這個硬件加速渲染器是經過 WindowManager.LayoutParams 來決定的, 他會給 mAttachInfo 的 mThreadedRenderer 屬性建立一個渲染器線程的描述

好的, 有了 mThreadedRenderer 這個對象, 接下來就能夠探索 mThreadedRenderer.draw 是如何進行硬件繪製的了

2. 硬件繪製流程

硬件渲染開啓以後, 在 ViewRootImpl.draw 中就會執行 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback); 進行 View 的繪製

public final class ThreadedRenderer {
    
    void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks,
            FrameDrawingCallback frameDrawingCallback) {
        ......    
        // 1. 構建根視圖的渲染數據
        updateRootDisplayList(view, callbacks);
        ......
        // 2. 通知 RenderThread 線程繪製
        int syncResult = nSyncAndDrawFrame(mNativeProxy, frameInfo, frameInfo.length);
        ......
    }
    
    private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
        // 1.1 構建 View 樹的渲染數據
        updateViewTreeDisplayList(view);
        // 1.2 視圖須要更新 || 當前的根渲染器中的數據已經無效了
        if (mRootNodeNeedsUpdate || !mRootNode.isValid()) {
            // 構建當前 Window 對應的 Surface 的畫筆
            DisplayListCanvas canvas = mRootNode.start(mSurfaceWidth, mSurfaceHeight);
            try {
                ......
                // 讓 Surface 畫筆的數據指向根視圖 DecorView 中的數據
                canvas.drawRenderNode(view.updateDisplayListIfDirty());
                ......
                // 表示當前 Window 的視圖數據更新完畢, 不須要更新了
                mRootNodeNeedsUpdate = false;
            } finally {
                // 將 Canvas 畫筆中的數據, 保存到渲染器中
                mRootNode.end(canvas);
            }
        }
    }

    private void updateViewTreeDisplayList(View view) {
        // 在根 View 的 Flag 中添加一個 Drawn 指令
        view.mPrivateFlags |= View.PFLAG_DRAWN; 
        // 若根視圖被設置了 INVALIDATE, 則說明須要從新構建顯示列表
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        // 若是 View 的顯示區域已經無效了, 則更新 View 的顯示列表
        view.updateDisplayListIfDirty();
        ......
    }
}
複製代碼

好的, 能夠看到 ThreadedRenderer 主要進行了兩個操做

  • 調用 updateRootDisplayList 構建根視圖的渲染數據
    • 調用 View.updateViewTreeDisplayList(), 更新 View 樹的渲染器數據
    • 將 View 樹的渲染器數據保存到當前 Window 的畫布 Surface 渲染器中
  • 將 Surface 渲染器中的數據發送到 RenderThread 進行真正的渲染

好的, 能夠看到這裏調用了 View.updateViewTreeDisplayList() 對 View 樹的渲染數據的進行構建, 接下來咱們就看看他是如何操做的

View 的渲染數據的構建

public class View {
    
    final RenderNode mRenderNode;
    
    public View(Context context) {
        ......
        // 可見在硬件繪製中, 每個 View 對應着一個渲染器中的結點
        mRenderNode = RenderNode.create(getClass().getName(), this);
        ......    
    }
    
     public RenderNode updateDisplayListIfDirty() {
        final RenderNode renderNode = mRenderNode;
        .....
        
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                || !renderNode.isValid() || (mRecreateDisplayList)) {
            // 1. 當前 View 渲染器中數據依舊是有效的 && 沒有要求重繪
            if (renderNode.isValid() && !mRecreateDisplayList) {
                // 1.2 將更新 Render 的操做分發給子 View, 該方法會在 ViewGroup 中重寫
                dispatchGetDisplayList();
                // 1.3 直接跳過使用 Canvas 繪製的操做
                return renderNode;
            }

            // 2. 走到這裏說明當前 View 的渲染數據已經失效了, 須要從新構建渲染數據
            mRecreateDisplayList = true;

            int width = mRight - mLeft;
            int height = mBottom - mTop;
            int layerType = getLayerType();
            
            // 2.1 經過渲染器獲取一個 Canvas 畫筆, 畫筆的可做用的區域爲 width, height
            final DisplayListCanvas canvas = renderNode.start(width, height);
            try {
                if (layerType == LAYER_TYPE_SOFTWARE) {
                    ......
                } else {
                    ......
                    // 2.2 當前 View 的 Draw 可跳過, 直接分發去構建子 View 渲染數據
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);
                    } else {
                        // 2.3 繪製自身
                        draw(canvas);
                    }
                }
            } finally {
                // 2.4 表示當前 View 的渲染數據已經保存在 Canvas 中了
                // 將它的數據傳遞到渲染器
                renderNode.end(canvas);
            }
        } else {
            ......
        }
        return renderNode;
    }
    
}
複製代碼

好的, View.updateDisplayListIfDirty 方法從名字上來理解是 若 View 展現列表無效了則更新它, 事實上它作的也是如此, 只不過這個 DisplayList 稱之爲渲染數據更爲合適, 它主要作了以下操做

  • 當前 View 渲染數據依舊是有效的 而且沒有要求重繪
    • 將更新渲染數據的操做分發給子 View
    • 遍歷結束以後直接返回 當前現有的渲染器結點對象
  • 當前 View 渲染數據無效了
    • 經過渲染器構建可渲染區域的畫筆 Canvas
    • 調用 view.draw 進行渲染數據的構建
      • 這個方法留在軟件渲染的時候再分析
    • 當前 View 的渲染數據從新構建好了, 則將它保存在渲染器 renderNode 中

好的, DecorView 的 updateDisplayListIfDirty 操做完成以後, 當前 Window 的 Surface 中全部的渲染數據就更新了, 以後再調用 ThreadedRenderer.nSyncAndDrawFrame 就能夠將數據發送到 SurfaceFlinger 提供的 Graphic Buffer 中等待其展現到屏幕上了

3.硬件繪製流程圖

硬件繪製流程圖

  • 當調用 View.draw 時首先獲取當前 Window 的畫布 Surface
  • 獲取當前畫布 Surface 的渲染器 RenderNode, 經過他來構建一個畫筆 Canvas
  • View.updateDisplayListIfDirty 完成後, 畫筆 Canvas 便會將數據輸出到其對應的渲染器中
    • 最終 Window 的 Surface 畫布中便會保存 DecorView 的渲染數據
  • GPU 從 SurfaceFlinger 託管的 BufferQueue 中獲取一個 Graphic Buffer
  • Surface 將渲染器中的數據發送到 GPU, GPU 將渲染數據柵格化到 Graphic Buffer 中
  • GPU 將 Graphic Buffer 從新發送給 SurfaceFlinger 的 BufferQueue 中
  • SurfaceFlinger 將柵格化的數據發送給屏幕呈現出來

二) 軟件渲染

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
            
    private boolean draw(boolean fullRedrawNeeded) {
        ......
        final Rect dirty = mDirty;
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                ......
            } else {
                // 若咱們沒有開啓硬件加速, 則調用 drawSoftware
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }
        .......
    }
}
複製代碼

能夠看到軟件渲染調用了 drawSoftware 方法, 接下來咱們繼續探究軟件渲染是如何執行的

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
        
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
        final Canvas canvas;
        try {
            ......
            // 1. 經過 Surface 的 lock 操做, 獲取一個畫筆, 這個畫筆是 Skia 的上層封裝
            canvas = mSurface.lockCanvas(dirty);
            ......
        }
        ......

        try {
            ......
            try {
                ......
                // 2. 調用了 DecorView 的 draw 方法
                mView.draw(canvas);
                ......
            } finally {
               .......
            }
        } finally {
            // 3. 解鎖畫筆, 將數據發送給 SurfaceFlinger
            surface.unlockCanvasAndPost(canvas);
            ......
        }
        return true;
    }
    
}
複製代碼

好的, 能夠看到軟件繪製主要有三部

  • 首先是經過 Surface 的 lockCanvas 獲取一個畫筆 Canvas, 它是 Android 2D 圖像庫 Skia 的一個上層封裝
  • 而後調用了 View 的 draw 方法
  • 最後調用了 unlockCanvasAndPost 解鎖畫筆, 將數據同步給 SurfaceFinger 緩衝區, 進行渲染
public class View {

    public void draw(Canvas canvas) {    
        ......
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
        // Step 1, draw the background, if needed
        int saveCount;
        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) {
                onDraw(canvas);
            }
            // Step 4, draw the children
            dispatchDraw(canvas);
            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }
            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);
            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);
            return;
        }
        ......
    }
    
}

複製代碼

好的, 很簡單這就是 View 的繪製流程的分發, 這裏再也不贅述了

軟件繪製流程圖

軟件繪製流程圖

  • View 想要繪製的時候, 首先要獲取 Window 對應的 Surface 畫紙
  • 經過 Surface.lock 操做獲取一個 Canvas 畫筆, 這個 Canvas 是 Skia 的上層封裝
  • 使用 Skia 畫筆在 Surface 畫紙上進行繪製
  • SurfaceFlinger 會託管一個 BufferQueue, 咱們從 BufferQueue 中獲取到 Graphic Buffer 畫板
  • 將咱們使用 Skia 畫筆在 Surface 畫紙上繪製的內容柵格化到 Graphic Buffer 畫板上
  • 將填充了數據畫板發送給 SurfaceFlinger 進行繪製
  • SurfaceFlinger 將畫板內容渲染到手機屏幕上

四. 硬件繪製與軟件繪製差別

從渲染機制

  • 硬件繪製使用的是 OpenGL/ Vulkan, 支持 3D 高性能圖形繪製
  • 軟件繪製使用的是 Skia, 僅支持 2D 圖形繪製

渲染效率上

  • 硬件繪製

    • 在 Android 5.0 以後引入了 RendererThread, 它將 OpenGL 圖形柵格化的操做所有投遞到了這個線程
    • 硬件繪製會跳過渲染數據無變動的 View, 直接分發給子視圖
  • 軟件繪製

    • 在將數據投入 SurfaceFlinger 以前, 全部的操做均在主線程執行
    • 不會跳過無變化的 View

所以硬件繪製較之軟件繪製會更加流暢

從內測消耗上

硬件繪製消耗的內存要高於軟件繪製, 但在當下大內存手機時代, 用空間去換時間仍是很是值得的

從兼容性上

  • 硬件繪製的 OpenGL 在各個 Android 版本的支持上會有一些不一樣, 常有由於兼容性出現的系統 bug
  • 軟件繪製的 Skia 庫從 Android 1.0 便屹立不倒, 所以它的兼容性要好於硬件繪製

五. 總結

至此, 對 Andorid 圖形渲染的操做, 整體上有了一些輪廓, 上述代碼都在應用框架層, 對於 SurfaceFlinger 與當前進程的通訊以及渲染原理能夠參考老羅的文章, 筆者從中受益良多

參考文獻: https://source.android.com/devices/graphics https://blog.csdn.net/qian520ao/article/details/81144167

相關文章
相關標籤/搜索