【Android原始碼】View的繪製流程分析

指間沙似流年發表於2017-12-23

Activity的resume

Activity的啟動流程

上次在Activity的啟動流程中,我們知道了Activity的生命週期都是通過Handler傳送訊息來執行的,而View的繪製就是在onResume之後:

// H();
 case RESUME_ACTIVITY:
     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
     SomeArgs args = (SomeArgs) msg.obj;
     handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
             args.argi3, "RESUME_ACTIVITY");
     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     break;
     
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    // 呼叫onResume方法
    r = performResumeActivity(token, clearHide, reason);
    // 將decorView新增到螢幕中
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    a.mDecor = decor;
    wm.addView(decor, l);
}

// WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
   applyDefaultToken(params);
   mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
複製程式碼
  1. handleResumeActivity方法首先呼叫performResumeActivity也就是我們Activity中的onResume方法。
  2. 在通過獲取到decorView,我們的佈局其實已經載入到decorView中了,在通過wm的addView方法將decorView傳遞出去。
  3. wm其實就是WindowManagerImpl,而在WindowManagerImpl中又呼叫了WindowManagerGlobaladdView方法。
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    ViewRootImpl root;
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    
    root.setView(view, wparams, panelParentView);
}  

// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    requestLayout();
}

@Override
public void requestLayout() {
   if (!mHandlingLayoutInLayoutRequest) {
       checkThread();
       mLayoutRequested = true;
       scheduleTraversals();
   }
}

void scheduleTraversals() {
       mChoreographer.postCallback(
               Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

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

複製程式碼

在addView方法中首先構建了ViewRootImpl這個非常關鍵的類,這個類就是操作View的繪製流程的類,在通過setView方法設定View。 而在setView方法中我們又看到了非常熟悉的一個方法requestLayout,我們在自定義ViewGroup的時候,如果要更新佈局就會呼叫這個方法重新整理介面,其實就是呼叫了View的重新繪製流程。 經過一系列的呼叫之後,最終呼叫了doTraversal方法。

measure

void doTraversal() {
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
   Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
   try {
       mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
   } finally {
       Trace.traceEnd(Trace.TRACE_TAG_VIEW);
   }
}

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    onMeasure(widthMeasureSpec, heightMeasureSpec);
}
複製程式碼

doTraversal中呼叫了performMeasure,而performMeasure又呼叫了View的onMeasure方法,這個才開始真正的測量。

我們以LinearLayout的VERTICAL來分析:

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
             measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);
         }
            if (useLargestChild &&
           (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {
       mTotalLength = 0;

       for (int i = 0; i < count; ++i) {
           final View child = getVirtualChildAt(i);
           if (child == null) {
               mTotalLength += measureNullChild(i);
               continue;
           }

           if (child.getVisibility() == GONE) {
               i += getChildrenSkipCount(child, i);
               continue;
           }

           final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
                   child.getLayoutParams();
           // Account for negative margins
           final int totalLength = mTotalLength;
           mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +
                   lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));
       }
   }

   // Add in our padding
   mTotalLength += mPaddingTop + mPaddingBottom;

   int heightSize = mTotalLength;

       int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
       setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
}

void measureChildBeforeLayout(View child, int childIndex,
       int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
       int totalHeight) {
   measureChildWithMargins(child, widthMeasureSpec, totalWidth,
           heightMeasureSpec, totalHeight);
}

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);
}
複製程式碼

首先遍歷所有的子View,然後通過子View的measure方法來繼續測量,如果子View是ViewGroup則繼續遍歷,如果是View則直接可以測量子View的寬高。而在View的measure中呼叫了onMeasure方法,這個方法就是我們平時寫的自定義View的時候覆蓋的方法,來指定我們自定義View的寬高。

通過深入優先遍歷的演算法,從最裡層的View開始測量,最終測量到最外層的ViewGroup。 當測量到最外層的時候,會再次遍歷自己的子View將子View的寬高計算。 最終呼叫setMeasuredDimension將寬高設定好。

layout

void doTraversal() {
    performLayout(lp, mWidth, mHeight);
}

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
       int desiredWindowHeight) {
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

public void layout(int l, int t, int r, int b) {
    onLayout(changed, l, t, r, b);
}
複製程式碼

當performMeasure方法執行完之後,doTraversal又呼叫了performLayout來進行確定子View的位置。而performLayout則繼續呼叫View的layout方法,在這裡我們又見到了熟悉的onLayout

同樣以LinearLayout的VERTICAL來分析

void layoutVertical(int left, int top, int right, int bottom) {
    for (int i = 0; i < count; i++) {
           setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                   childWidth, childHeight);
           childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

           i += getChildrenSkipCount(child, i);
       }
   }
}

private void setChildFrame(View child, int left, int top, int width, int height) {        
   child.layout(left, top, left + width, top + height);
}
複製程式碼

layoutVertical會遍歷所有的子View並呼叫setChildFrame來為子View指定對應的位置。 這個時候layout和measure的計算方式又有所區別,measure是通過深度優先遍歷的方式,首先獲取子View的寬高,而layout指定View的位置的時候,是從外向內,依次遍歷指定View的位置。

draw

void doTraversal() {
    performDraw();
}

private void performDraw() {
   try {
         // 呼叫繪製函式
       draw(fullRedrawNeeded);
   } finally {
       mIsDrawing = false;
       Trace.traceEnd(Trace.TRACE_TAG_VIEW);
   }
}

private void draw(boolean fullRedrawNeeded) {
    // 獲取surface
   Surface surface = mSurface;
   if (!surface.isValid()) {
       return;
   }
    // 繪製需要更新
   if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
           // 使用硬體加速
       if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
                // 使用硬體渲染繪製
           mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
       } else {
               // 使用cpu繪製
           if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
               return;
           }
       }
   }

   if (animating) {
       mFullRedrawNeeded = true;
       scheduleTraversals();
   }
}
複製程式碼

當performMeasure方法執行完之後,doTraversal又呼叫了performDraw開始繪製檢視。 在draw函式中獲取需要繪製的區域,再判斷是否使用硬體加速。 通常情況下都是使用cpu繪製,也就是直接呼叫drawSoftware:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
       boolean scalingRequired, Rect dirty) {

   // Draw with software renderer.
   final Canvas canvas;
   try {
       final int left = dirty.left;
       final int top = dirty.top;
       final int right = dirty.right;
       final int bottom = dirty.bottom;

            // 獲取canvas物件,用於framework層繪製
       canvas = mSurface.lockCanvas(dirty);
   } catch (Surface.OutOfResourcesException e) {
           return false;
   }

   try {
           // 開始繪製,從decorView開始繪製
         mView.draw(canvas);
   } finally {
           // 釋放canvas鎖,並通知SurfaceFlinger更新
           // private static native void nHwuiDraw(long renderer); 呼叫native方法
           surface.unlockCanvasAndPost(canvas);
   }
   return true;
}
複製程式碼

從上面的函式可以看出繪製的過程:

  1. 判斷使用gpu還是cpu渲染
  2. 獲取繪製的surface物件
  3. 通過surface獲取並鎖住canvas物件
  4. 從DecorView開始發起整個view樹的繪製
  5. 解鎖canvas物件,並通知surfaceFlinger物件更新檢視,呼叫native方法。

我們來看View的draw函式:

public void draw(Canvas canvas) {
    drawBackground(canvas);
    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);
        
        // we're done...
        return;
    }
}
複製程式碼

View的draw方法會呼叫dispatchDraw(canvas)來傳遞繪製過程,dispatchDraw會遍歷呼叫所有子元素的draw方法,這樣draw就會一層一層的傳遞下去,知道最後一個View繪製完成。

這個時候View的整個繪製流程就完成了。

相關文章