View 绘制流程

102 阅读7分钟

View 绘制流程

前言

关于 Android View 绘制流程是个老生常谈的主题了,相关的优秀博客也有很多,本篇博客希望通过一张时序图作为主线,尽量以好记、好看、好讲的方式简要表述 View 绘制流程一些关键节点。

开发环境

Android Studio Arctic Fox|2020.3.1

Android 33

View 绘制流程时序图

Android_View_绘制流程时序图.png fig.1 View 绘制流程时序图

对象(Object)描述

对象描述
ActivityThread负责应用程序的生命周期管理和主线程消息循环。
Activity• 提供用户界面:每个 Activity 都包含一个布局文件(XML)或动态生成的视图(View),用于显示内容。• 处理用户交互:响应用户操作,如点击、滑动等。• 管理生命周期:Activity 有自己的生命周期,系统会在不同状态下回调相应方法(如创建、启动、暂停、销毁等)。• 组件通信:通过 Intent 与其他 Activity、Service 或应用组件通信。
PhoneWindowPhoneWindow 是 Window 类的具体实现,用于表示一个应用程序窗口。它是 Activity 和 Dialog 的窗口实现类,负责管理窗口的装饰(DecorView)和内容视图。
DecorView它是整个应用窗口 View 树,触摸事件和其他输入都会首先到达这里,然后再分发下去。
ViewRootImpl负责 View 的测量、布局、绘制、渲染,事件分发和与 WindowManagerService 的交互。
WindowManagerImplWindowManagerImpl 是 WindowManager 接口的具体实现类,负责窗口的添加、更新和删除等操作。它直接与应用程序交互,处理窗口的布局、绘制和事件分发。每个 Activity 或 Dialog 通常都有一个对应的 WindowManagerImpl 实例,管理其窗口。
WindowManagerGlobalWindowManagerGlobal 是一个全局单例类,负责管理整个应用程序的所有窗口。它协调多个 WindowManagerImpl 实例,确保窗口操作的一致性和同步。作为系统级窗口管理的核心,与 WindowManagerService 交互,执行窗口的添加、更新和删除等操作。

消息(Message)说明

  • ① handleLaunchActivity()

    // ActivityThread.java
    public final class ActivityThread extends ClientTransactionHandler
                    implements ActivityThreadInternal {
            ...
            // ApplicationThread 主要负责与 ActivityManagerService 进行 Binder 通信。
            final ApplicationThread mAppThread = new ApplicationThread();
            // 获取的是当前线程上的消息循环队列
            final Looper mLooper = Looper.myLooper();
            // H 继承自 Handler,负责消息处理
            final H mH = new H();
    
            ...
            private class ApplicationThread extends IApplicationThread.Stub {
                    private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";
    
                    public final void scheduleReceiver(Intent intent, ActivityInfo info,
                                    CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
                                    boolean sync, int sendingUser, int processState) {
                            updateProcessState(processState, false);
                            ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
                                            sync, false, mAppThread.asBinder(), sendingUser);
                            r.info = info;
                            r.compatInfo = compatInfo;
                            // 接收到的 AMS 消息,通过 mH 发送消息
                            sendMessage(H.RECEIVER, r);
                    }
            }
    }
    

    Binder + Handler 消息机制,ApplicationThread 接收到跨进程的消息,通过 Handler (H) 将其切换到 ui 线程(ActivityThread),第一个消息是 LaunchActivityItem(LAUNCH_ACTIVITY),它调用 handleLaunchActivity(),在这个方法里完成了 Activity 的创建和启动等。

  • ② performLaunchActivity()

    // ActivityThread.java
    public final class ActivityThread extends ClientTransactionHandler
                    implements ActivityThreadInternal {
            ...
            private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
                    ...
                    ContextImpl appContext = createBaseContextForActivity(r);
                    ...
                    // 通过类加载创建 Activity
                    activity = mInstrumentation.newActivity(
                                    cl, component.getClassName(), r.intent);
                    ...
                    Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);
                    ...
    
                    activity.attach(appContext, this, getInstrumentation(), r.token,
                                    r.ident, app, r.intent, r.activityInfo, title, r.parent,
                                    r.embeddedID, r.lastNonConfigurationInstances, config,
                                    r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                                    r.assistToken, r.shareableActivityToken);
                    ...
                    // 调用到 Activity 的 onCreate()
                    if (r.isPersistable()) {
                            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                    } else {
                            mInstrumentation.callActivityOnCreate(activity, r.state);
                    }
            }
    
    }
    
  • ⑧ setContentView()

    // AppCompatDelegateImpl.java 
    class AppCompatDelegateImpl extends AppCompatDelegate
            implements MenuBuilder.Callback, LayoutInflater.Factory2 {
        ...
        @Override
        public void setContentView(int resId) {
            ensureSubDecor();
            ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            // 用户设置的布局添加到 ⑨ 创建的 DecorView android.R.id.content 节点下
            LayoutInflater.from(mContext).inflate(resId, contentParent);
            mAppCompatWindowCallback.bypassOnContentChanged(mWindow.getCallback());
        }
        ...
    }
    

    把用户设置的 setContentView(resId) 布局添加到 DecorView 布局下

  • ⑨ installDecor()

    // PhoneWindow.java
    public class PhoneWindow extends Window implements MenuBuilder.Callback {
        ...
        // 创建 DecorView
        private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                mDecor = generateDecor(-1);
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
                mContentParent = generateLayout(mDecor);
    
            }
        }
    
        ...
        // 基于 theme、features 设置布局,默认布局 R.layout.screen_simple
        protected ViewGroup generateLayout(DecorView decor) {
            // Apply data from current theme.
            ...
        }
        ...
    }
    
  • ⑪ handleResumeActivity()

    Binder + Handler 消息机制,触发 ActivityThread 的 handleResumeActivity()

  • ⑭ new ViewRootImpl()

    // ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
                    View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
                    AttachedSurfaceControl {
    
            ...
            public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
                            boolean useSfChoreographer) {
                    ...
                    // 创建 ViewRootImpl 所在的线程,默认 mThread 是主线程(ActivityThread)
                    // 部分刷新 UI 操作需要 checkThread() 进行校验
                    mThread = Thread.currentThread();
                    ...
                    // View.post 里面判断的 mAttachInfo
                    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                                    context);
                    ...
                    mChoreographer = useSfChoreographer
                                    ? Choreographer.getSfInstance()
                                    : Choreographer.getInstance();
                    ...
            }
    
            ...
            void checkThread() {
                    if (mThread != Thread.currentThread()) {
                            throw new CalledFromWrongThreadException(
                                            "Only the original thread that created a view hierarchy can touch its views.");
                    }
            }
    
    }
    
  • ⑮ root.setView()

    // ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
                    View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
                    AttachedSurfaceControl {
    
            ...
            public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
                            int userId) {
                    synchronized (this) {
                            if (mView == null) {
                                    mView = view;
    
                                    ...
    
                                    // 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();
                                    InputChannel inputChannel = null;
                                    if ((mWindowAttributes.inputFeatures
                                                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                                            inputChannel = new InputChannel();
                                    }
                                    mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                                                    & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
    
                                    if (mView instanceof RootViewSurfaceTaker) {
                                            PendingInsetsController pendingInsetsController = ((RootViewSurfaceTaker) mView)
                                                            .providePendingInsetsController();
                                            if (pendingInsetsController != null) {
                                                    pendingInsetsController.replayAndAttach(mInsetsController);
                                            }
                                    }
    
                                    try {
                                            ...
                                            // 添加到 window 显示
                                            res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                                                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                                                            mInsetsController.getRequestedVisibilities(), inputChannel,
                                                            mTempInsets,
                                                            mTempControls);
                                          
                                    } 
                                    
                                    ...
                                    setFrame(mTmpFrames.frame);
                                    ...
                            }
                    }
            }
    
    }
    
  • ⑰ scheduleTraversals()

    // ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
                    View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
                    AttachedSurfaceControl {
    
            ...
            public void requestLayout() {
                    if (!mHandlingLayoutInLayoutRequest) {
                            checkThread();
                            mLayoutRequested = true;
                            scheduleTraversals();
                    }
            }
    
            ...
            void scheduleTraversals() {
                    if (!mTraversalScheduled) {
                            mTraversalScheduled = true;
                            // 同步屏障消息
                            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                            // 发布要在下一帧上运行的回调,回调运行一次,然后自动删除
                            mChoreographer.postCallback(
                                            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                            notifyRendererOfFramePending();
                            pokeDrawLockIfNeeded();
                    }
            }
    
    }
    
  • ㉑ performTraversals()

    屏幕的下一个 Vsync 信号到来,⑰ 设置的下一帧回调触发,执行到 performTraversals()

    // ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
                    View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
                    AttachedSurfaceControl {
    
            ...
            private void performTraversals() {
                    ...
                    Rect frame = mWinFrame;
                    if (mFirst) {
                            ...
                            // 分发给 View 设置 mAttachInfo,这里会调用 View 的 onAttachedToWindow 方法,把 View.post 的任务添加给
                            // mHandler
                            host.dispatchAttachedToWindow(mAttachInfo, 0);
                            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
                            dispatchApplyInsets(host);
                    }
                    ...
                    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
                    if (layoutRequested) {
                            if (!mFirst) {
                                    if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                                                    || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                                            windowSizeMayChange = 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 {
                                                    final Rect bounds = getWindowBoundsInsetSystemBars();
                                                    desiredWindowWidth = bounds.width();
                                                    desiredWindowHeight = bounds.height();
                                            }
                                    }
                            }
    
                            // 确定 window 的大小,measureHierarchy() -> performMeasure()
                            // Ask host how big it wants to be
                            windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
                                            desiredWindowWidth, desiredWindowHeight);
                    }
                    ...
                    if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
                                    || mForceNextWindowRelayout) {
                            ...
                            if (!mStopped || mReportNextDraw) {
                                    if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()
                                                    || dispatchApplyInsets || updatedConfiguration) {
                                            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
                                                            lp.privateFlags);
                                            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
                                                            lp.privateFlags);
    
                                            if (DEBUG_LAYOUT)
                                                    Log.v(mTag, "Ooops, something changed!  mWidth="
                                                                    + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                                                                    + " mHeight=" + mHeight
                                                                    + " measuredHeight=" + host.getMeasuredHeight()
                                                                    + " dispatchApplyInsets=" + dispatchApplyInsets);
                                            // 调用 DecorVier 的 measure 方法
                                            // Ask host how big it wants to be
                                            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
                                            // 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;
    
                                            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;
                                    }
                            }
                    }
    
                    ...
                    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
                    boolean triggerGlobalLayoutListener = didLayout
                                    || mAttachInfo.mRecomputeGlobalAttributes;
                    if (didLayout) {
                            // 调用 DecorVier 的 layout 方法
                            performLayout(lp, mWidth, mHeight);
                            ...
                    }
    
                    ...
                    if (!isViewVisible) {
                            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                                            mPendingTransitions.get(i).endChangingAnimations();
                                    }
                                    mPendingTransitions.clear();
                            }
    
                            if (mSyncBufferCallback != null) {
                                    mSyncBufferCallback.onBufferReady(null);
                            }
                    } else if (cancelAndRedraw) {
                            // Try again
                            scheduleTraversals();
                    } else {
                            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                                            mPendingTransitions.get(i).startChangingAnimations();
                                    }
                                    mPendingTransitions.clear();
                            }
                            // 调用 DecorVier 的 draw 方法
                            if (!performDraw() && mSyncBufferCallback != null) {
                                    mSyncBufferCallback.onBufferReady(null);
                            }
                    }
            }
    
            ...
            private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
                            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
                    int childWidthMeasureSpec;
                    int childHeightMeasureSpec;
                    boolean windowSizeMayChange = false;
    
                    if (DEBUG_ORIENTATION || DEBUG_LAYOUT)
                            Log.v(mTag,
                                            "Measuring " + host + " in display " + desiredWindowWidth
                                                            + "x" + desiredWindowHeight + "...");
    
                    boolean goodMeasure = false;
                    if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
                            // On large screens, we don't want to allow dialogs to just
                            // stretch to fill the entire width of the screen to display
                            // one line of text. First try doing the layout at a smaller
                            // size to see if it will fit.
                            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
                            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
                            int baseSize = 0;
                            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                                    baseSize = (int) mTmpValue.getDimension(packageMetrics);
                            }
                            if (DEBUG_DIALOG)
                                    Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                                                    + ", desiredWindowWidth=" + desiredWindowWidth);
                            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
                                    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
                                                    lp.privateFlags);
                                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                                    if (DEBUG_DIALOG)
                                            Log.v(mTag, "Window " + mView + ": measured ("
                                                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
                                                            + ") from width spec: "
                                                            + MeasureSpec.toString(childWidthMeasureSpec)
                                                            + " and height spec: "
                                                            + MeasureSpec.toString(childHeightMeasureSpec));
                                    if ((host.getMeasuredWidthAndState() & View.MEASURED_STATE_TOO_SMALL) == 0) {
                                            goodMeasure = true;
                                    } else {
                                            // Didn't fit in that size... try expanding a bit.
                                            baseSize = (baseSize + desiredWindowWidth) / 2;
                                            if (DEBUG_DIALOG)
                                                    Log.v(mTag, "Window " + mView + ": next baseSize="
                                                                    + baseSize);
                                            childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags);
                                            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                                            if (DEBUG_DIALOG)
                                                    Log.v(mTag, "Window " + mView + ": measured ("
                                                                    + host.getMeasuredWidth() + ","
                                                                    + host.getMeasuredHeight() + ")");
                                            if ((host.getMeasuredWidthAndState() & View.MEASURED_STATE_TOO_SMALL) == 0) {
                                                    if (DEBUG_DIALOG)
                                                            Log.v(mTag, "Good!");
                                                    goodMeasure = true;
                                            }
                                    }
                            }
                    }
    
                    if (!goodMeasure) {
                            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width,
                                            lp.privateFlags);
                            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
                                            lp.privateFlags);
                            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                                    windowSizeMayChange = true;
                            }
                    }
    
                    if (DBG) {
                            System.out.println("======================================");
                            System.out.println("performTraversals -- after measure");
                            host.debug();
                    }
    
                    return windowSizeMayChange;
            }
    
            ...
            // view.post 任务传递给 mHandler 处理
            final ViewRootHandler mHandler = new ViewRootHandler();
            ...
    }
    

常见问题

  • 为什么我在 onCreate() 中调用 View.post() 方法可以得到 View 的宽高呢?

        // View.java
        /**
         * <p>Causes the Runnable to be added to the message queue.
         * The runnable will be run on the user interface thread.</p>
         *
         * @param action The Runnable that will be executed.
         *
         * @return Returns true if the Runnable was successfully placed in to the
         *         message queue.  Returns false on failure, usually because the
         *         looper processing the message queue is exiting.
         *
         * @see #postDelayed
         * @see #removeCallbacks
         */
        public boolean post(Runnable action) {
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                return attachInfo.mHandler.post(action);
            }
    
            // Postpone the runnable until we know on which thread it needs to run.
            // Assume that the runnable will be successfully placed after attach.
            getRunQueue().post(action);
            return true;
        }
    

    onCreate() 时 mAttachInfo 为 null,任务放到了消息队列中,不是立刻执行,等到 Activity::onResume(),dispatchAttachedToWindow() 方法中把 View 的 post 任务添加到 ViewRootImpl handler 中,在 DocerView 经过 measure、loayout、draw 后,任务才执行。

  • MeasureSpec 的理解

    Android MeasureSpec 简介

  • invaliate() 和 requestlayout() 方法的区别?

    ViewRootImpl 持有 DecorView 管理 View 的绘制。所以简单来说,requestlayout 和 invaliate 最终都会向上回溯调用到 ViewRootImpl 的 postTranversals 方法来绘制 View。 不同的是 requestlayout 会绘制 View 的 measure,layout 和 draw 过程。invaliate 因为只添加了绘制 draw 的标志位,只会绘制 draw 过程。

  • Activity 首次启动 onMeasure() 执行两次?

    ㉑ performTraversals() 方法内执行两次 onMeasure()

    1. 首次测量
      • 这个阶段的主要目的是为了确定 RootView 的尺寸,进而决定 Window 尺寸。
    2. 二次测量
      • 在这个阶段,由于已经知道了 Window 的大小,系统会对整个视图树中的各个组件进行精确地重新测量。
  • 子线程刷新 UI 的几种方式?

    1. 避开 ViewRootImpl::checkThread()检测
      • 利⽤硬件加速机制一些场景可以绕开 requestLayout()
      • 主线程刚刚更新某个 View 后子线程立刻更新
    2. 尝试子线程中创建 ViewRootImpl
    3. GlSurfaceView 也算是在子线程中刷新 UI

参考文档

探索 Android View 绘制流程

Android MeasureSpec 简介

后记:View 绘制流程的代码主要在 Java 层,调试起来非常方便。然而,如果在梳理过程中试图涵盖每段代码的细节,就像测量海岸线的长度一样,会陷入无止境的分形中。普通的开发者总是渴望洞悉所有细节,但优秀的开发者懂得何时忽略不必要的细节。