Android View绘制(二)-绘制流程分发

Android View绘制(二)-绘制流程分发

在上一篇文章,我们了解了 Android inflate 的流程,也就是布局文件 inflate 成 View 的简要流程,但是并没有实际看到一些控件怎么布局和绘制的,那么接下来就要进行介绍相关的内容。

从 addView() 说起

在 LayoutInflate.inflate() 中,有这么一行代码:

if (root != null && attachToRoot) {
    root.addView(temp, params);
}

最终调用的是 ViewGroup.addView(View,int,LayoutParams) 方法,代源码如下:

public void addView(View child, int index, LayoutParams params) {
    if (DBG) {
        System.out.println(this + " addView");
    }

    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }

    // addViewInner() will call child.requestLayout() when setting the new LayoutParams
    // therefore, we call requestLayout() on ourselves before, so that the child's request
    // will be blocked at our level
    requestLayout();//请求测量
    invalidate(true);//请求绘制
    addViewInner(child, index, params, false);//实际上这一步才是子 View 添加到父容器中
}

这里关键看最后三行代码,分别调用了 requestLayout(),invalidate(),addViewInner() 方法,其中 requestLayout() 会

View.requestLayout() 方法

根据官方 api 的解释,requestLayout()方法在该 View 的 layout 需要重新测绘是会被调用,这个方法会向子 View 传递要求子 View 也实施 layout 行为。

public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // Only trigger request-during-layout logic if this is the view requesting it,
        // not the views in its parent hierarchy
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }

    mPrivateFlags |= PFLAG_FORCE_LAYOUT;//重写标记位,表示需要重新进行 layout
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {//需要判断是否 request 执行中
        mParent.requestLayout();//调用父容器的 requestLayout()方法
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

第二十行里面,调用了 mParent.requestLayout() 也就是调用了父布局懂得 requestLayout(),就这样,requestLayout 事件不断的从子容器传递到父布局里面,直到 Activity 里面,最顶层的容器,我们都知道 Activity 的顶层容器是 DecorView,然而 DecorView 里面并没有实现 requestLayout() 方法,也就是说,最终还会被传递到 DecorView 的 mParent 的reuqestLayout() 中去。

然而 DecorView 的 mParent 是在这个 View. assignParent() 这个方法被赋值的:

void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent");
    }
}

那么 DrcorView 的 assginParent() 又是什么时候被调用的, 我们可以猜测是 DecorView 被创建的时候。通过打断点的方式,我们找到了 DecorView 被创建的地方,也就是 ActivityThread.handleResumeActvity() 方法,我们暂时理解,这个方法在 Activity.onResume() 阶段会被调用,源码如下

final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    ........
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();//获取 PhoneWindow 对象(attach 方法中被创建)
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();//获取 WindowManagerImpl 对象
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
               //获取 ViewRootImpl 对象
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient && !a.mWindowAdded) {
                a.mWindowAdded = true;//设置标记位,表示 DecorView 被添加过了
                wm.addView(decor, l);//将 DecorView 加入到 Activity 窗口上
            }

        // If the window has already been added, but during resume
        // we started another activity, then don't yet make the
        // window visible.
        } else if (!willBeVisible) {
            if (localLOGV) Slog.v(
                TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }

     .......

}

然后在第8行,调用了 r.window.getDecorView() 方法,接着调用的是 PhoneWindow.getDecorView() 方法,但是我们进入到 getDecorView() 方法,并没有发现什么。继续找下去在 29行,wm.addView() 方法里面,我们找到了关联,源码如下:

// WindowManagerImpl.addView() 方法,这里 mGlobal 是一个 WindowManagerGlobal 对象
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

接着调用 WindowManagerGlobal.addView() 方法:

//WindowManagerGlobal.addView() 方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
    ........
    ViewRootImpl root;
    View panelParentView = null;

      .......
        root = new ViewRootImpl(view.getContext(), display);//创建一个 ViewRootImpl 对象

        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

      ......
    // do this last because it fires off messages to start doing things
    try {
        root.setView(view, wparams, panelParentView);//ViewRootImpl.setView() 方法
    } catch (RuntimeException e) {
    .....
        throw e;
    }
}

接着调用 ViewRootImpl.setView() 方法

    //ViewRootImpl.setView() 方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
        if (mView == null) {
            mView = view;
            ......
            mAdded = true;
            int res; /* = WindowManagerImpl.ADD_OKAY; */

            // Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            requestLayout();//请求 requestLayout
            .......
            //view 就是之前的 DecorView,将该 ViewRootImpl 设置为 DecorView 的 mParent 
            view.assignParent(this);
            mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
            mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
        }
    }
}

接着调用 ViewRootImpl.rquestLayout()

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

最终 requestLayout() 的回溯流程已经捋清楚了,简单的画一张流程图看一下。

那么重新回到 ViewRootImpl.requestLayout()方法,继续分析。

ViewRootImpl.requestLayout()

这里先简单介绍下这个 ViewRootImpl 是什么东西,通过上述的分析,我们可以知道 DecorView 的 ViewRootImpl 建立联系的是在 ViewRootImpl.setView()方法里面。

然后调用了这么几行代码,在 ViewRootImpl.setView()方法中:

View mView;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
 .....
mView = view;
......
mAttachInfo.mRootView = view;
......
mAdded = true;
....
view.assignParent(this);
...
}

再从 View 里面的源码分析 mParent 的作用,可以发现 ViewRootImpl 大概是负责通知 DecorView 及其子 View 完成测绘过程的一个代理类。简要的关系图如下:

那么这里对 ViewRootImpl.rquestLayout() 进行分析:

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();//检查是否主线程
        mLayoutRequested = true;//设置标记位
        scheduleTraversals();
    }
}

然后会调用 scheduleTraversals() 方法:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

重点看第5行和第6行,这里post了一个 Runnable,那么我们进这个 Runnable 的 run() 方法看看:

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
}

这里会调用 performTraversals() 方法,但是这个方法居然有七百多行之长,实在令人汗颜,只能捡重点看了:

private void performTraversals() {
    // cache mView since it is used so much below...
    //在 ViewRootImpl.setView() 方法里面知道,mView 就是之前的 DecorView
    final View host = mView;
    .........
    if (host == null || !mAdded)
        return;

    mIsInTraversal = true;
    mWillDrawSoon = true;
    boolean windowSizeMayChange = false;
    boolean newSurface = false;
    boolean surfaceChanged = false;
    WindowManager.LayoutParams lp = mWindowAttributes;

    // DecorView 对应的 Window 的宽高,第一次测量时会去屏幕宽高,之后由上一次的读取 mWinFrame 里保存的宽高
    int desiredWindowWidth;
    int desiredWindowHeight;
    .......
    Rect frame = mWinFrame;
    if (mFirst) {
        mFullRedrawNeeded = true;
        mLayoutRequested = true;

        if (shouldUseDisplaySize(lp)) {
            // NOTE -- system code, won't try to do compat mode.
            Point size = new Point();
            mDisplay.getRealSize(size);
            desiredWindowWidth = size.x;
            desiredWindowHeight = size.y;
        } else {
            Configuration config = mContext.getResources().getConfiguration();
            desiredWindowWidth = dipToPx(config.screenWidthDp);
            desiredWindowHeight = dipToPx(config.screenHeightDp);
        }
        ......

    } else {
        desiredWindowWidth = frame.width();
        desiredWindowHeight = frame.height();
        if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
            if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            windowSizeMayChange = true;
        }
    }
    ......

        if (!mStopped || mReportNextDraw) {//只要 Window 还是 activite 的 mStopped 就为 ture
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                    (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
                    //这里会根据 Window 大小,计算出 DecorView de  MeasureSpec 
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                        + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                        + " mHeight=" + mHeight
                        + " measuredHeight=" + host.getMeasuredHeight()
                        + " coveredInsetsChanged=" + contentInsetsChanged);

                 // Ask host how big it wants to be
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);// Measure 调用在这里

                // Implementation of weights from WindowManager.LayoutParams
                // We just grow the dimensions as needed and re-measure if
                // needs be
                int width = host.getMeasuredWidth();
                int height = host.getMeasuredHeight();
                boolean measureAgain = false;
                //如果存在 Weight 值,则会重新计算 MeasureSpace,然后再次调用 performMeasure() 方法
                if (lp.horizontalWeight > 0.0f) {
                    width += (int) ((mWidth - width) * lp.horizontalWeight);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                if (lp.verticalWeight > 0.0f) {
                    height += (int) ((mHeight - height) * lp.verticalWeight);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }

                if (measureAgain) {
                    if (DEBUG_LAYOUT) Log.v(mTag,
                            "And hey let's measure once more: width=" + width
                            + " height=" + height);
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }

                layoutRequested = true;
            }
        }
    } else {
        maybeHandleWindowMove(frame);
    }

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        performLayout(lp, mWidth, mHeight);//这里调用 performLayout()

        // By this point all views have been sized and positioned
        // We can compute the transparent area

        if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
            // start out transparent
            // TODO: AVOID THAT CALL BY CACHING THE RESULT?
            host.getLocationInWindow(mTmpLocation);
            mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
                    mTmpLocation[0] + host.mRight - host.mLeft,
                    mTmpLocation[1] + host.mBottom - host.mTop);

            host.gatherTransparentRegion(mTransparentRegion);
            if (mTranslator != null) {
                mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
            }

            if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
                mPreviousTransparentRegion.set(mTransparentRegion);
                mFullRedrawNeeded = true;
                // reconfigure window manager
                try {
                    mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
                } catch (RemoteException e) {
                }
            }
        }

    }

  ........
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

    if (!cancelDraw && !newSurface) {
        if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).startChangingAnimations();
            }
            mPendingTransitions.clear();
        }

        performDraw();// draw 调用在这里
    } else {
        if (isViewVisible) {
            // Try again
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }

    mIsInTraversal = false;
}

第67,107,149 行依次调用了 performMeasure() , performLayout(),performDraw() 方法,这里的调用逻辑判断比较复杂,我们关注下,这些方法传递的参数

 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//传递的是 DecorView 的 MeasureSpec
performLayout(lp, mWidth, mHeight);//依次传递的是 Window.LayoutParams,和 ViewRootImpl 的宽高
performDraw();//并没有传递任何参数

这三个函数最终会将 Mesaure,Layout,Draw 事件分发到 mView,也就是 DecorView,最终分别调用 DecorView 的 measure(),layout(),draw() 方法,源码如下:

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);
    }
}

接着是 performLayout() 方法:

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;

    final View host = mView;
    if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
        Log.v(mTag, "Laying out " + host + " to (" +
                host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
    }

    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        mInLayout = false;
        int numViewsRequestingLayout = mLayoutRequesters.size();
        if (numViewsRequestingLayout > 0) {
            // requestLayout() was called during layout.
            // If no layout-request flags are set on the requesting views, there is no problem.
            // If some requests are still pending, then we need to clear those flags and do
            // a full request/measure/layout pass to handle this situation.
            ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                    false);
            if (validLayoutRequesters != null) {
                // Set this flag to indicate that any further requests are happening during
                // the second pass, which may result in posting those requests to the next
                // frame instead
                mHandlingLayoutInLayoutRequest = true;

                // Process fresh layout requests, then measure and layout
                int numValidRequests = validLayoutRequesters.size();
                for (int i = 0; i < numValidRequests; ++i) {
                    final View view = validLayoutRequesters.get(i);
                    Log.w("View", "requestLayout() improperly called by " + view +
                            " during layout: running second layout pass");
                    view.requestLayout();
                }
                measureHierarchy(host, lp, mView.getContext().getResources(),
                        desiredWindowWidth, desiredWindowHeight);
                mInLayout = true;
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                mHandlingLayoutInLayoutRequest = false;

                // Check the valid requests again, this time without checking/clearing the
                // layout flags, since requests happening during the second pass get noop'd
                validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                if (validLayoutRequesters != null) {
                    final ArrayList<View> finalRequesters = validLayoutRequesters;
                    // Post second-pass requests to the next frame
                    getRunQueue().post(new Runnable() {
                        @Override
                        public void run() {
                            int numValidRequests = finalRequesters.size();
                            for (int i = 0; i < numValidRequests; ++i) {
                                final View view = finalRequesters.get(i);
                                Log.w("View", "requestLayout() improperly called by " + view +
                                        " during second layout pass: posting in next frame");
                                view.requestLayout();
                            }
                        }
                    });
                }
            }

        }
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

这里能看到,第15 行,请求了 mView 也就是 DecorView.layout() 方法。从 50 行到 66 行,会重新将那些请求 requestLayout 的布局加入到队列中,然后尝试在下一帧去调用这些 View 的 requestLayout() 方法。

tip:如果多个子View 同时调用 requestLayout() 方法,是否会 layout 多次?需要去验证。

最后是 performDraw() 方法:

private void performDraw() {
    if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
        return;
    }

    final boolean fullRedrawNeeded = mFullRedrawNeeded;
    mFullRedrawNeeded = false;

    mIsDrawing = true;
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
    try {
        draw(fullRedrawNeeded);//调用 draw() 方法
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ......
}

这里又会调用 ViewRootImpl.draw() 方法:

private void draw(boolean fullRedrawNeeded) {

    Surface surface = mSurface;
    if (!surface.isValid()) {
        return;
    }

    if (DEBUG_FPS) {
        trackFPS();
    }

    if (!sFirstDrawComplete) {
        synchronized (sFirstDrawHandlers) {
            sFirstDrawComplete = true;
            final int count = sFirstDrawHandlers.size();
            for (int i = 0; i< count; i++) {
                mHandler.post(sFirstDrawHandlers.get(i));
            }
        }
    }

    scrollToRectOrFocus(null, false);

    if (mAttachInfo.mViewScrollChanged) {
        mAttachInfo.mViewScrollChanged = false;
        mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
    }

    boolean animating = mScroller != null && mScroller.computeScrollOffset();
    final int curScrollY;
    if (animating) {
        curScrollY = mScroller.getCurrY();
    } else {
        curScrollY = mScrollY;
    }
    if (mCurScrollY != curScrollY) {
        mCurScrollY = curScrollY;
        fullRedrawNeeded = true;
        if (mView instanceof RootViewSurfaceTaker) {
            ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
        }
    }

    final float appScale = mAttachInfo.mApplicationScale;
    final boolean scalingRequired = mAttachInfo.mScalingRequired;

    int resizeAlpha = 0;

    final Rect dirty = mDirty;
    if (mSurfaceHolder != null) {
        // The app owns the surface, we won't draw.
        dirty.setEmpty();
        if (animating && mScroller != null) {
            mScroller.abortAnimation();
        }
        return;
    }

    if (fullRedrawNeeded) {
        mAttachInfo.mIgnoreDirtyState = true;
        dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
    }

    if (DEBUG_ORIENTATION || DEBUG_DRAW) {
        Log.v(mTag, "Draw " + mView + "/"
                + mWindowAttributes.getTitle()
                + ": dirty={" + dirty.left + "," + dirty.top
                + "," + dirty.right + "," + dirty.bottom + "} surface="
                + surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
                appScale + ", width=" + mWidth + ", height=" + mHeight);
    }

    mAttachInfo.mTreeObserver.dispatchOnDraw();

    int xOffset = -mCanvasOffsetX;
    int yOffset = -mCanvasOffsetY + curScrollY;
    final WindowManager.LayoutParams params = mWindowAttributes;
    final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
    if (surfaceInsets != null) {
        xOffset -= surfaceInsets.left;
        yOffset -= surfaceInsets.top;

        // Offset dirty rect for surface insets.
        dirty.offset(surfaceInsets.left, surfaceInsets.right);
    }

    boolean accessibilityFocusDirty = false;
    final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
    if (drawable != null) {
        final Rect bounds = mAttachInfo.mTmpInvalRect;
        final boolean hasFocus = getAccessibilityFocusedRect(bounds);
        if (!hasFocus) {
            bounds.setEmpty();
        }
        if (!bounds.equals(drawable.getBounds())) {
            accessibilityFocusDirty = true;
        }
    }

    mAttachInfo.mDrawingTime =
            mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;

    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
         // 开启硬件绘制
        if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
            // If accessibility focus moved, always invalidate the root.
            boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
            mInvalidateRootRequested = false;

            // Draw with hardware renderer.
            mIsAnimating = false;

            if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
                mHardwareYOffset = yOffset;
                mHardwareXOffset = xOffset;
                invalidateRoot = true;
            }

            if (invalidateRoot) {
                mAttachInfo.mHardwareRenderer.invalidateRoot();
            }

            dirty.setEmpty();

            // Stage the content drawn size now. It will be transferred to the renderer
            // shortly before the draw commands get send to the renderer.
            final boolean updated = updateContentDrawBounds();

            if (mReportNextDraw) {
                // report next draw overrides setStopped()
                // This value is re-sync'd to the value of mStopped
                // in the handling of mReportNextDraw post-draw.
                mAttachInfo.mHardwareRenderer.setStopped(false);
            }

            if (updated) {
                requestDrawWindow();
            }

            mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
        } else {
            // If we get here with a disabled & requested hardware renderer, something went
            // wrong (an invalidate posted right before we destroyed the hardware surface
            // for instance) so we should just bail out. Locking the surface with software
            // rendering at this point would lock it forever and prevent hardware renderer
            // from doing its job when it comes back.
            // Before we request a new frame we must however attempt to reinitiliaze the
            // hardware renderer if it's in requested state. This would happen after an
            // eglTerminate() for instance.
            if (mAttachInfo.mHardwareRenderer != null &&
                    !mAttachInfo.mHardwareRenderer.isEnabled() &&
                    mAttachInfo.mHardwareRenderer.isRequested()) {

                try {
                    mAttachInfo.mHardwareRenderer.initializeIfNeeded(
                            mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
                } catch (OutOfResourcesException e) {
                    handleOutOfResourcesException(e);
                    return;
                }

                mFullRedrawNeeded = true;
                scheduleTraversals();
                return;
            }
            // 默认的绘制流程走这里
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                return;
            }
        }
    }

    if (animating) {
        mFullRedrawNeeded = true;
        scheduleTraversals();
    }
}

然后这里主要进行的工作是策略绘制区域大小,也就是计算Rect 类型变量 dirty 的大小,然后调用 ViewTreeObserver.dispathOnDraw() 方法,接着根据绘制策略,查看是支持硬件加速,然后执行不同的方法,如果是不支持硬件加速,则走 ViewRootImpl.drawSoftware() 方法,支持硬件加速则会走 ThreadRender.draw(),在单独的线程完成绘制。但是两者最终都会回调 DecorView.draw(Canvas) 方法。

这里就暂时不对这两个方法进行分析了,因为比较复杂,硬件加速涉及的知识点更是非常难啃的。

总结-android View 绘制流程

到这里,应该已经清楚, 各个事件是怎么分发,怎么到 子 View的,下面结合一张流程图,进行总结一下:

所以,如果自定义 View,需要实现 onMeasure(),onLayout(),onDraw() 便可以实现对自定义 View 的控制了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值