結合Android原生看Flutter渲染流程

寫在前面

作為一名android開發(fā)人員接觸學習flutter已有小半年了缘屹,無論是widget配置構建響應式ui的思想鞍陨,dart vm hotReload的快捷盆赤,還是各種devtools帶來的開發(fā)便利都讓我體會到flutter不同于其他跨平臺框架的強大的魅力蒿囤。深入了解了flutter的渲染流程才大致明白了flutter宣稱的媲美原生的流暢度是如何做到的客们。下面對比著android原生看下flutter framework是如何做ui渲染的。

UI渲染

Android 上無論是原生應用材诽,視頻解碼播放還是 FLutter 渲染都離不開Surface底挫。UI 渲染本質上就是圖像的生產者生產圖像流,圖像的消費方消費圖像流脸侥,由于兩端處于不同進程有不同的運行速度兩者數(shù)據(jù)傳遞就需要用到緩沖區(qū)建邓,在安卓上這個緩沖區(qū)對應的就是BufferQueueSurface是一個接口睁枕,供生產方與使用方交換緩沖區(qū)官边。下圖展示了生產者消費者通過對緩沖區(qū)的操作實現(xiàn)數(shù)據(jù)的傳遞。

圖片

一個 android 應用一個典型的渲染流程就是:當 app 進入前臺時外遇,WindowManager服務會向SurfaceFlinger請求一個繪圖Surface注簿。SurfaceFlinger會創(chuàng)建一個其主要組件為BufferQueue的層,而SurfaceFlinger是其消耗方臀规。生產方端的Binder對象通過WindowManager傳遞到應用滩援,然后應用可以開始直接將幀發(fā)送到SurfaceFlinger。應用通過 Vysnc 觸發(fā)繪制流程塔嬉,經過measure , layout, draw等流程最后通過柵格化把代碼表示的view 結構對應的display list轉化為像素數(shù)據(jù)(支持硬件加速柵格化會通過 opengl es 調用 gpu 執(zhí)行玩徊,不持支的通過 skia 在 cpu 中執(zhí)行,現(xiàn)在的手機都有硬件加速了所以文章后面都是圍繞著有硬件加速展開的),隨后提交到BufferQueue中等待消費谨究。大多數(shù)應用在屏幕上一次顯示三個層:屏幕頂部的狀態(tài)欄恩袱、底部或側面的導航欄以及應用界面,SurfaceFlingers收到所有層后會和Hardware Composer一起完成層的合成工作最后交給顯示屏顯示胶哲。

圖片

Flutter 是使用SurfaceView依附于 android 原生之上畔塔,他的繪制流程不同的地方是在于Vysnc 觸發(fā)后進行自己的animate, build, layout, paint等步驟根據(jù)我們構建的widget tree生成的rendering tree轉化為一系列繪制指令最后根據(jù)指令柵格化為像素數(shù)據(jù)。也就是說更換了生產圖片流的方式鸯屿,視頻播放也是一樣不過生產方是用對應解碼器從視頻文件流中解碼出一幀楨圖片像素數(shù)據(jù)澈吨。

總的來說 android/flutter 渲染就是要把我們代碼里寫的 view/widget 對應的 ui 結構樹轉化為繪制指令集,再經過 gpu 或者 cpu 柵格化轉化為像素數(shù)據(jù)用于顯示寄摆。

開始渲染谅辣!

我所理解的 UI 渲染一般分為EventLayout婶恼,Draw三個階段:

  • Event:用戶做了某些操作或收到系統(tǒng)某些指令觸發(fā)繪制流程桑阶,如我們點擊按鈕柏副,修改控件顯示文本等等。
  • Layout:繪制之前對控件樹進行尺寸測量和位置布局等蚣录。
  • Draw:更新或重制繪制指令集割择,格柵化,顯示等

Android原生

在android中渲染都是由 Vysnc 驅動的萎河。Vysnc 信號由硬件產生經過SurfaceFlinger轉發(fā)到應用線程的Choreographer中荔泳,通過 ui 線程的 handler 切換線程調用doFrame方法開始每一幀的渲染流程:

void doFrame(long frameTimeNanos, int frame) {
    ...
    doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    ...
}

該方法會依次處理4個 callback

  • INPUT:輸入事件
  • ANIMATION:動畫
  • TRAVERSAL:窗口刷新,執(zhí)行 measure/layout/draw 操作
  • COMMIT:遍歷完成的提交操作公壤,用來修正動畫啟動時間

渲染主要看的是TRAVERSAL毒涧,這個 callback 是由ViewRootImpl添加的翔试,對應的是ViewRootImpl中的TraversalRunnable

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

doTraversal最終會調用performTraversals方法

private void performTraversals() {
  ...
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
  performLayout(lp, mWidth, mHeight);
  ...
  performDraw();
}

該方法中會依次調用performMeasure/Layout/Draw方法開始繪制流程。

Event 階段

Chorepgrapher并不會一直接收到Vsync信號進行重繪施流,只有一定的事件發(fā)生才會去接收信號這樣避免了無用的渲染慨飘。在Event階段要做的就是告訴系統(tǒng)我需要在下一幀重繪确憨,下一個Vsync信號到來時才會觸發(fā)ChoreographerdoFrame方法。

看一個簡單的例子

圖片

最外層是DecorView,他是所有View流程的起點瓤的。navigationbarBackgorundstatusBarBackground就像前面所說的一樣不屬于app層休弃,是由 systemui 渲染后 和 app 的層合并顯示的。

當我們調用setText重新設置TextView顯示文字

圖片

根據(jù)調用椚Ω啵可以發(fā)現(xiàn)settext后會分別調用requestLayoutinvalidateInternal方法

public void requestLayout() {
    ...
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ...
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {
    ...
    if (...) {
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
        }
        mPrivateFlags |= PFLAG_DIRTY;
        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }
        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }
        ...
    }
}

這2個方法基本都是對 view 的mPrivateFlags標志位進行標記塔猾,會標記這個view及其父類,子類是否需要重新布局和重新繪制稽坤。

圖片

繼續(xù)往里走發(fā)現(xiàn)調用了DisplayEventReceivernativeScheduleVsync方法丈甸,往下就是C++層的代碼,后面的具體作用就是通過binder和SurfaceFlinger通信尿褪,告訴 SurfaceFlinger 我需要在下一幀重繪睦擂,當硬件發(fā)送Vsync消息給SurfaceFlinger后SurfaceFlinger會通知應用,最后就到了ChorepgrapherdoFrame方法開始繪制流程杖玲。完成了Event階段到Layout階段的切換顿仇。

圖片

Layout 階段

Android原生上這個階段對應的就是 View 的 Measure 和 Layout 階段。

  • measure 確定 view 的測量寬/高(mMeasureWidth/mMeasureHeight)
  • layout 確定 view 的最終寬/高和四個頂點位置(mLeft,mTop,mRight.mBottom)

Measure

measure要分view和viewgroup兩種情況摆马,view通過measure完成自己的測量臼闻,viewgroup除了完成自己的測量外還要遍歷調用所有子view的measure方法,各個child view/viewgroup 遞歸去執(zhí)行這個流程囤采。

View measure

先看下View的measure方法:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    ...
    final boolean needsLayout = specChanged
            && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
    ...
    if (forceLayout || needsLayout) {
        ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        ...
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
    ...
}
  • 參數(shù)widthMeasureSpec述呐,heightMeasureSpec,是 parent view 根據(jù) child view 的LayoutParam 和自身邏輯對 view 的尺寸要求斑唬,如要求指定尺寸或者最大尺寸等等
  • forceLayout:這里可以看到是對mPrivateFlags標志位PFLAG_FORCE_LAYOUT的判斷市埋,其實這個PFLAG_FORCE_LAYOUT就是Event階段我們在requestLayout方法時候設置的黎泣,
  • onMeasure方法:forcelayout為true或者滿足一些條件觸發(fā),如parent view傳的measureSpec有變換等等
  • 設置PFLAG_LAYOUT_REQUIRED標志位用于后面的layout方法

onMeasure方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

onMeasure方法大部分子類都會重寫缤谎,靠自己的規(guī)則測量出view的大惺阋小(如TextView會根據(jù)文字的排版確定尺寸),如果沒有重寫就比較簡單根據(jù)背景圖寬高和設置的最小寬高屬性和measureSpec確定最終的測量大小并給mMeasuredWidth/mMeasuredHeight(這里只是測量尺寸坷澡,具體的尺寸還要layout后才能確定托呕,但是絕大分測量尺寸就是view的最終尺寸,getMeasureWidth/getWidth)賦值频敛,單個view的measure流程就完成了项郊。

ViewGroup measure

再看看擁有子view的ViewGroup的measure流程,由于measure方法是final的子view并不能重寫斟赚,所以measure方法都是一樣的着降,所以直接從onMeasure開始看,這里以FramLayout為例:

圖片

onMeasure主要做2件事

  • 傳遞尺寸要求調用所有child viewmeasure方法確定所有view的大修志(每個child view又會調用自己的onMeasure方法測量自己的)
  • 根據(jù)子view的最大寬高來調用setMeaureDimension來確定FrameLayout的寬高

onMeasure會遍歷所有child view并把child view 作為參數(shù)調用到ViewGroup 的 measureChildWithMargins方法:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

可以看到根據(jù)getChildMeasureSpec方法生成childWidthMeasureSpec/childHeightMeasureSpec作為參數(shù)調用child view的measure方法開始遞歸完成所有view樹所有view的measure方法任洞。
(getChildMeasureSpec方法參數(shù)傳遞了LayoutParam的width/height,也就是上文說說的 parent view 根據(jù) child view 的LayoutParam 和自身邏輯生成對 view 的尺寸要求 measureSpec)

所有measure的起點則是上文所說的doFrame后調用的ViewRootImpl的performMeasure方法

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
  ...
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
}

mView其實就是DecorView发侵,DecorView就是一個FramLayout,就是從它開始整個measure流程交掏。

Layout

Layout的作用是確定view的位置,通過調用view的layout方法來確定該view的位置刃鳄,layout會調用onLayout盅弛,view通過重寫onLayout方法就可以響應view位置的變化,如果是ViewGroup還要負責在onLayout中遍歷所有的子元素并調用其layout方法種當ViewGroup的位置被確定后叔锐,它 onLayout 中會遍歷所有的子元素并調用其layout方法挪鹏。完成所有view 的layout

先看下layout方法

public void layout(int l, int t, int r, int b) {
  ...
  boolean changed = isLayoutModeOptical(mParent) ?
          setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  ...
  if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
      onLayout(changed, l, t, r, b);
  ...
  mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
  }
  ...
  mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
  ...
}
  • l,t,r,b 就是parent view對這個view確定的位置
  • 調用setFrame方法對比定點位置和原來的位置有沒有變化
  • 如果頂點位置有變化或者設置PFLAG_LAYOUT_REQUIRED標志位(measure方法里設置的)觸發(fā)onLayout方法
  • mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED, mPrivateFlags &= ~PFLAG_FORCE_LAYOUT掌腰,應為到這layout方法已經走完了所以得還原以該view前設置的標記位

繼續(xù)看onLayout方法狰住,onLayout要區(qū)分view和viewgroup,view的onLayout是根據(jù)確定的大小做些內部邏輯的調整齿梁,viewgroup還得在onLayout中調用child view 的layout方法催植,以FrameLayout的onLayout方法為例:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();
                int childLeft;
                int childTop;
                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                ...
                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }
                ...
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

在layoutChildren方法中我們可以看到FrameLayout根據(jù)child view 在measure的時候確定的測量大小和gravity確定child view的4個頂點,然后調用child 的 layout方法開始child view 的layout流程勺择,因為view的實際大小其實是由4個頂點決定的所以說view的實際大小不一定等于測量尺寸mMeasureWidth/Height创南。
layout的起點則是后調用的ViewRootImpl的performLayout方法

 private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    ...
    final View host = mView;
    if (host == null) {
      return;
    }
    ...
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
  }

用0,0點DecoerView的測量寬高作為參數(shù)調用DecoerView的layout方法開始整個view 的layout流程省核。
經過上訴2個過程就確定了每個view的大小和位置就可以開始繪制流程了稿辙,通過ViewRootImpl的perfornDraw進入到Draw階段

Draw 階段

圖片

從上面的調用棧可以看到有涉及到ThreadedRenderer,RenderNode,****RecordingCanvas,****DisplayList 等偏底層類气忠,這幾個類的大致作用如下:

  • ThreadedRenderer是連接java/c++層邻储,ui線程和渲染線程的代理類
  • RecordingCanvas記錄繪制過程最后生成DisplayList
  • DisplayList就是實際存放繪制指令的類
  • RenderNode則是連接View和對應的DisplayList赋咽,每個View都有自己對應的RenderNode

觸發(fā)了ViewRootImpl的draw后會調用到ThreadedRendererdraw方法,然后調用updateRootDisplayList把DecorView作為參數(shù)傳遞到updateViewTreeDisplayList開始view的重繪過程吨娜。

private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
            == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}

設置一些flag然后調用view的updateDisplayListIfDirty

public RenderNode updateDisplayListIfDirty() {
    final RenderNode renderNode = mRenderNode;
    ...
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
            || !renderNode.hasDisplayList()
            || (mRecreateDisplayList)) {
        if (renderNode.hasDisplayList()
                && !mRecreateDisplayList) {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();
            return renderNode; 
        }
        mRecreateDisplayList = true;
        ...
        final RecordingCanvas canvas = renderNode.beginRecording(width, height);
        try {
        ...
                computeScroll();

                canvas.translate(-mScrollX, -mScrollY);
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                    dispatchDraw(canvas);
                    drawAutofilledHighlight(canvas);
                    if (mOverlay != null && !mOverlay.isEmpty()) {
                        mOverlay.getOverlayView().draw(canvas);
                    }
                    if (debugDraw()) {
                        debugDrawFocus(canvas);
                    }
                } else {
                    draw(canvas);
                }
        ...
        } finally {
            renderNode.endRecording();
            setDisplayListProperties(renderNode);
        }
    } else {
        mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
    }
    return renderNode;
}
  • 根據(jù)view的RenderNode是否已經持有DisplayListmRecreateDisplayList(是否需要重建DisplayList標記) 判斷是否需要觸發(fā)重建DiplayList
  • beginRecording新建用于記錄繪制的RecordingCanvas
  • RecordingCanvas作為參數(shù)調用view.draw(canvas)
  • endRecording結束繪制記錄

draw方法是被beginRecordingendRecording包圍脓匿,就有點像由系統(tǒng)beginRecording給view一張紙(RecordingCanvas),view通過draw方法在上面寫如需要在什么位置需要繪制某個圖形的指令等等,最后endRecording時后系統(tǒng)回收紙生成對應的DisplayList宦赠。

再回頭看下draw方法陪毡,和measure方法類似,子view的draw都是由parent view調用的勾扭,parent view需要通過dispatchDraw調用所有child view的draw方法毡琉,

view的draw方法是有兩種

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime){
 ...
if (hardwareAcceleratedCanvas) {
    mRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;
    mPrivateFlags &= ~PFLAG_INVALIDATED;
}
...
if (drawingWithRenderNode) {
    renderNode = updateDisplayListIfDirty();
...
}
...
}
public void draw(Canvas canvas){
  ...
  drawBackground(canvas);
  ...
  onDraw(canvas);
  ...
  dispatchDraw(canvas);
  ...
}
  • 第一個是由ViewGroup.drawChild()調用,主要處理和RenderNode等渲染有關的東西,并調用updateDisplayListIfDirty方法妙色∥ψ蹋可以看到在updateDisplayListIfDirty中觸發(fā)重建displayList的mRecreateDisplayList是由 PFLAG_INVALIDATED決定的,這個標志位正好就是Event 階段調用requestLayout或者in****validate設置的
  • 第二個是view在updateDisplayListIfDirty中被調用的身辨。我們熟知的onDraw也是在這里被調用的為了防止歧義下文中暫且命名這個方法為draw_inner

用下圖表述下2個draw之間的關系:
圖片

ViewGroup的draw被parent view調用后根據(jù)mPrivateFlags等決定是否要重建DisplayList虱歪,如果需要就會新建RecordingCanvas作為參數(shù)調用draw_inner,然后調用onDraw栅表,平常做自定義控件會重寫onDraw方法時調用canvas.drawXXX等等api,這個canvas就是RecordingCanvas师枣,它的左右就是記錄繪制的流程怪瓶,onDraw結束后會調用dispatchDraw遍歷child view調用每個view的draw方法,完成所有view的繪制践美,整個流程的起點還是DecoverView,在上文中updateViewTreeDisplayList方法中開始的洗贰。

當所有view 完成繪制了后會調用endRecording方法開始生成對應的DisplayList

RenderNode.java

public void endRecording() {
    RecordingCanvas canvas = mCurrentRecordingCanvas;
    mCurrentRecordingCanvas = null;
    long displayList = canvas.finishRecording();
    nSetDisplayList(mNativeRenderNode, displayList);
    canvas.recycle();
}

RecordingCanvas.java

long finishRecording() {
    return nFinishRecording(mNativeCanvasWrapper);
}

結束繪制返回生成的DisplayList

RecordingCanvas.cpp

DisplayList* RecordingCanvas::finishRecording() {
    restoreToCount(1);
    mPaintMap.clear();
    mRegionMap.clear();
    mPathMap.clear();
    DisplayList* displayList = mDisplayList;
    mDisplayList = nullptr;
    mSkiaCanvasProxy.reset(nullptr);
    return displayList;
}

finishRecording后調用nSetDisplayList方法關聯(lián)RenderNode(Java RenderNode 對應的C++類)和對應的DisplayList(C++)

nSetDisplayList方法

android_view_RenderNode.cpp

static void android_view_RenderNode_setDisplayList(JNIEnv* env,
        jobject clazz, jlong renderNodePtr, jlong displayListPtr) {
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
     DisplayList* newData = reinterpret_cast<DisplayList*>(displayListPtr);
     renderNode->setStagingDisplayList(newData);
 }

記錄完繪制指令后會調用syncAndDrawFrame開始格柵化和交換buffer與SurfaceFlinger通信階段

圖片
public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
    return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
}

然后會依次調用如下c++類的方法

void CanvasContext::draw()
{
    ...
    bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue,
                                        mContentDrawBound, mOpaque, mLightInfo, mRenderNodes, &(profiler()));
    ...
    bool didSwap = mRenderPipeline->swapBuffers(frame, drew, windowDirty, mCurrentFrameInfo,
                                                    &requireSwap);
    ...
}

mRenderPipelinedrawswapBuffers方法就是對應的格柵化和交換 buffer了。到此 Android 原生端的繪制流程就算結束了陨倡。

圖片

Flutter

FrameWork構架

Flutter擁有自己的開發(fā)工具敛滋,開發(fā)語言、虛擬機兴革,編譯機制绎晃,線程模型和渲染管線,和Android相比杂曲,它也可以看做一個小型的OS了庶艾。

先看下flutter framework的構架圖

圖片

從下到上依次:

Embedder

不同的操作系統(tǒng)有不同的embedder,它負責為fluuter提供運行的入口擎勘,提供像rendering surface,輸入系統(tǒng)咱揍,管理message event loop等服務,甚至可以根據(jù)embedder api移植到不同的系統(tǒng)上棚饵,如非官方支持desktop的https://github.com/go-flutter-desktop/go-flutter

Flutter engine

作為flutter app的基石煤裙,提供dart runtime 掩完,文字圖像繪制,柵格化硼砰,文件且蓬,網絡等功能,通過dart:ui像framework層提供一些底層的api

Framework

我們開發(fā)過程中接觸最多的就是framework層夺刑,給我們提供了不同風格的控件缅疟,動畫。手勢遍愿,繪制等支持,管理我們配置的widget tree存淫,處理一部分渲染流程,本文主要也是在這層展開沼填。

未完待續(xù)桅咆。。坞笙。

參考:

一顆像素的誕生:

https://mp.weixin.qq.com/s/QoFrdmxdRJG5ETQp5Ua3-A

Flutter architectural-overview:

https://flutter.cn/docs/resources/architectural-overview#widgets

Choreographer原理

http://gityuan.com/2017/02/25/choreographer/

Android N中UI硬件渲染(hwui)的HWUI_NEW_OPS(基于Android 7.1)

https://blog.csdn.net/jinzhuojun/article/details/54234354

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末岩饼,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子薛夜,更是在濱河造成了極大的恐慌籍茧,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梯澜,死亡現(xiàn)場離奇詭異寞冯,居然都是意外死亡,警方通過查閱死者的電腦和手機晚伙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進店門吮龄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咆疗,你說我怎么就攤上這事漓帚。” “怎么了午磁?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵尝抖,是天一觀的道長。 經常有香客問我漓踢,道長牵署,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任喧半,我火速辦了婚禮奴迅,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己取具,他們只是感情好脖隶,可當我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著暇检,像睡著了一般产阱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上块仆,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天构蹬,我揣著相機與錄音,去河邊找鬼悔据。 笑死庄敛,一個胖子當著我的面吹牛,可吹牛的內容都是我干的科汗。 我是一名探鬼主播藻烤,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼头滔!你這毒婦竟也來了怖亭?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤坤检,失蹤者是張志新(化名)和其女友劉穎兴猩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體早歇,經...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡峭跳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了缺前。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡悬襟,死狀恐怖衅码,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情脊岳,我是刑警寧澤逝段,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站割捅,受9級特大地震影響奶躯,放射性物質發(fā)生泄漏。R本人自食惡果不足惜亿驾,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一嘹黔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧莫瞬,春花似錦儡蔓、人聲如沸郭蕉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽召锈。三九已至,卻和暖如春获询,著一層夾襖步出監(jiān)牢的瞬間涨岁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工吉嚣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梢薪,地道東北人。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓瓦戚,卻偏偏與公主長得像沮尿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子较解,可洞房花燭夜當晚...
    茶點故事閱讀 44,652評論 2 354