Bootstrap

View的绘制流程一 DecorView是什么时候添加到Window上的

setContentView做了什么?

setContentView只是创建了一个Decorview,把我们的xml添加到Decorview里面。但是DecorView并没有添加到PhoneView里面。

比如果在onCreate,onResume去拿控件的高度是拿不到的。

Activity的onResume方法在什么时候执行呢?

ActivityThread的handleResumeActivity方法里面触发performResumeActivity方法,performResumeActivity方法里面会执行r.activity.performResume,接着走到mInstrumentation.callActivityOnResume,这个方法里面触发activity.onResume()。

handleResumeActivity(ActivityThread.java) 
--> performResumeActivity(ActivityThread.java) 
	--> r.activity.performResume(Activity.java) 
		--> mInstrumentation.callActivityOnResume(Activity.java) 
           -->activity.onResume(Instrumentation.java)
 ActivityThread.java

@Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        // TODO Push resumeArgs into the activity for consideration
        // skip below steps for double-resume and r.mFinish = true case.
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
          ........
}

public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            String reason) {
       ......
        try {
            r.activity.onStateNotSaved();
            r.activity.mFragments.noteStateNotSaved();
            checkAndBlockForNetworkAccess();
            if (r.pendingIntents != null) {
                deliverNewIntents(r, r.pendingIntents);
                r.pendingIntents = null;
            }
            if (r.pendingResults != null) {
                deliverResults(r, r.pendingResults, reason);
                r.pendingResults = null;
            }
            r.activity.performResume(r.startsNotResumed, reason);

            r.state = null;
            r.persistentState = null;
            r.setState(ON_RESUME);

            reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");
        } ......
    }



Activity.java

    final void performResume(boolean followedByPause, String reason) {
       ......
        // mResumed is set by the instrumentation
        mInstrumentation.callActivityOnResume(this);
        ......
    }



Instrumentation.java

 public void callActivityOnResume(Activity activity) {
        activity.mResumed = true;
        activity.onResume();
        
       ......
    }

DecorView是什么时候添加到Window上的?

ActivityThread的handleResumeActivity方法:

 @Override
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
        ........

        final Activity a = r.activity;

        ........

        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            //拿到DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            ......
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //把DecorView与Phone关联起来
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } 
         ........
    }

在handleResumeActivity方法里面,先拿到DecorView,然后调用WindowManager的addView方法,把DecorView与PhoneWindow关联起来。

WindowManager是啥?

1.WindowManager在什么时候创建?

在ActivityThread的performLaunchActivity里面执行了activity的attach方法:

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
                ........
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }

                // Activity resources must be initialized with the same loaders as the
                // application context.
                appContext.getResources().addLoaders(
                        app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

                appContext.setOuterContext(activity);
                //会在这个方法中创建Activity的PhoneWindow,并绑定对应的WindowManager。
                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);

                 ........
    }

 再看看Activty的attach方法:

  @UnsupportedAppUsage
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
     
        //  PhoneWindow 
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        // PhoneWindow 的 callback 设置为activity
        mWindow.setCallback(this);
        ........

        //设置 PhoneWindow的WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        // 获取 WindowManagerImpl 作为windowManager
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;
        ........
    }

 WindowManager的创建时机在执行ActivityThread的performLaunchActivity方法里面,因为调用了Activity的attach而创建出来。

2.WindowManger与WindowManagerImpl,WindowManagerGlobal关系

再来看到setWindowManager方法:

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

我们发现wm原来是WindowManagerImpl,那么在handResumeActivity方法里面的wm.addview会执行WindowManagerImpl里面的addView方法。

    @UnsupportedAppUsage
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();



   @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

 WindowManagerImpl的addView最终又是调用WindowManagerGlobal的addView方法。那么WindowManger,WindowManagerImpl,WindowManagerGlobal关系是怎么样的呢?

WindowManagerImpl:确定View属于哪个屏幕,哪个父窗口

WindowManagerGlobal:管理整个进程,所有的窗口信息

ViewRootImpl:WindowManagerGlobal 实际操作者,操作自己的窗口

WindowManager是一个接口类,继承自接口ViewManager,负责窗口的管理(增、删、改)。它的实现类是WindowManagerlmpl,而具体操作实际上又会交给WindowManagerGlobal来处理,它是个单例,进程唯一。WindowManagerGlobal执行addView的方法中会传入DecorView,还会初始化一个ViewRootlmpl。WindowManagerGlobal因为是单例的,它内部会有两个List来分别保存这两个对象,来统一管理。

3.ViewRootImpl是什么

接下来我们来看WindowManagerGlobal的addView方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (display == null) {
            throw new IllegalArgumentException("display must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        /* 如果当前窗口需要被添加为另一个窗口的附属窗口(子窗口),则需要让父窗口视自己的情况,
        对当前窗口的布局参数(LayoutParams)进行一些修改 */
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };
                SystemProperties.addChangeCallback(mSystemPropertyUpdater);
            }

            // WindowManager不允许同一个View被添加两次
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // If this is a panel window, then find the window it is being
            // attached to for future reference.
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }

           // 创建一个ViewRootImpl对象,ViewRootImpl实现了一个控件树的根
           // 它负责与WMS进行直接的通讯,负责管理Surface,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站。
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            // 将下面三个参数保存到对应集合中,以供之后的查询,控件、布局参数、ViewRootImpl三者共同组成了客户端的一个窗口
            // 将 DecorView 添加到 mViews 中
            mViews.add(view);
            // 将 ViewRootImpl 添加到 mRoots 中
            mRoots.add(root);
            // 将 DecorView 的 LayoutParams参数 添加到 mParams 中
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                /* 将作为窗口的控件设置给ViewRootImpl。这个动作将导致ViewRootImpl向WMS
                添加新的窗口、申请Surface以及托管控件在Surface上的重绘动作。这才是真正意义上
                完成了窗口的添加操作*/
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

这个方法里面主要是创建了ViewRootImpl,把WindowManager.LayoutParams设置给DecorView了。并且设置了三个list,分别把ViewRootImpl,DecorView和WindowManager.LayoutParams给保存起来,调用ViewRootImpl的setView方法,把ViewRootImpl设置为DecorView的父布局,开启view绘制流程:

 // 创建一个ViewRootImpl对象,ViewRootImpl实现了一个控件树的根
           // 它负责与WMS进行直接的通讯,负责管理Surface,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站。
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            // 将下面三个参数保存到对应集合中,以供之后的查询,控件、布局参数、ViewRootImpl三者共同组成了客户端的一个窗口
            // 将 DecorView 添加到 mViews 中
            mViews.add(view);
            // 将 ViewRootImpl 添加到 mRoots 中
            mRoots.add(root);
            // 将 DecorView 的 LayoutParams参数 添加到 mParams 中
            mParams.add(wparams);
// do this last because it fires off messages to start doing things
            try {
                /* 将作为窗口的控件设置给ViewRootImpl。这个动作将导致ViewRootImpl向WMS
                添加新的窗口、申请Surface以及托管控件在Surface上的重绘动作。这才是真正意义上
                完成了窗口的添加操作*/
                root.setView(view, wparams, panelParentView, userId);
            }
 ViewRootImpl构造方法

我们进入ViewRootImpl的构造方法一探究竟:

 public ViewRootImpl(Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
        ......
		/* 保存当前线程到mThread。这个赋值操作体现了创建ViewRootImpl的线程如何成为UI主线程
		 在ViewRootImpl处理来自控件树的请求时(如请求重新布局,请求重绘,改变焦点等),会检
		 查发起请求的thread与这个mThread是否相同。倘若不同则会拒绝这个请求并抛出一个异常 */
		mThread = Thread.currentThread();
		......
		/* mDirty用于收集窗口中的无效区域。所谓无效区域是指由于数据或状态发生改变时而需要进行重绘
		的区域。举例说明,当应用程序修改了一个TextView的文字时,TextView会将自己的区域标记为无效
		区域,并通过invalidate()方法将这块区域收集到这里的mDirty中。当下次绘制时,TextView便
		可以将新的文字绘制在这块区域上 */
		mDirty = new Rect();
		mTempRect = new Rect();
		mVisRect = new Rect();
		/* mWinFrame,描述了当前窗口的位置和尺寸。与WMS中WindowState.mFrame保持着一致 */
		mWinFrame = new Rect();
		/* 创建一个W类型的实例,W是IWindow.Stub的子类。即它将在WMS中作为新窗口的ID,以及接 
		收来自WMS的回调 */
		mWindow = new W(this);
		......
		/* 创建mAttachInfo。mAttachInfo是控件系统中很重要的对象。它存储了此当前控件树所贴附 
		的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的
		mAttachInfo变量中。mAttachInfo中所保存的信息有WindowSession,窗口的实例(即mWindow)
	      ViewRootImpl实例,窗口所属的Display,窗口的Surface以及窗口在屏幕上的位置等等。所以,当
	      需要在一个View中查询与当前窗口相关的信息时,非常值得在mAttachInfo中搜索一下 */
	      mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,    
	      context);
		......
    }

ViewRootImpl创建的时候,有三个地方需要注意:

1.保存当前线程到mThread

这个赋值操作体现了创建ViewRootImpl的线程如何成为UI主线程。在ViewRootImpl处理来自控件树的请求时(如请求重新布局,请求重绘,改变焦点等),会检查发起请求的thread与这个mThread是否相同,倘若不同则会拒绝这个请求并抛出一个异常。

2.创建mDirty区域

mDirty用于收集窗口中的无效区域。所谓无效区域是指由于数据或状态发生改变时而需要进行重绘
的区域。举例说明,当应用程序修改了一个TextView的文字时,TextView会将自己的区域标记为无效区域,并通过invalidate()方法将这块区域收集到这里的mDirty中。当下次绘制时,TextView便可以将新的文字绘制在这块区域上。

3.创建mAttachInfo

mAttachInfo是控件系统中很重要的对象。它存储了此当前控件树所贴附的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的mAttachInfo变量中。mAttachInfo中所保存的信息有WindowSession,窗口的实例(即mWindow),ViewRootImpl实例,窗口所属的Display,窗口的Surface以及窗口在屏幕上的位置等等。所以,当需要在一个View中查询与当前窗口相关的信息时,非常值得在mAttachInfo中搜索一下。

真正把DecorView添加到PhoneWindow的地方

接着来看ViewRootImpl.setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                // mView 保存了控件树的根
                mView = view;

               ......
               //在添加到窗口管理器之前安排第一个布局,以确保在从系统接收任何其他事件之前进行重新布局?               /* 在添加窗口之前,先通过requestLayout()方法在主线程上安排一次“遍历”。//
               所谓“遍历”是指ViewRootImpl中的核心方法performTraversals()。    
               这个方法实现了对控件树进行测量、布局、向WMS申请修改窗口属性以及重绘的所有工作。
               由于此“遍历”操作对于初次遍历做了一些特殊处理,而来自WMS通过mWindow发生的回调会导致一些属性发生变化?
               如窗口的尺寸、Insets以及窗口焦点等,从而有可能使得初次“遍历”的现场遭到破坏。
               因此,需要在添加窗口之前,先发送一个“遍历”消息到主线程
               在主线程中向主线程的Handler发送消息,如果使用得当,可以产生很精妙的效果。例如本例中
               可以实现如下的执行顺序:添加窗口->初次遍历->处理来自WMS的回调 */ 
                //TODO
				requestLayout();
			    InputChannel inputChannel = null; 
				/* 初始化mInputChannel。InputChannel是窗口接受来自InputDispatcher的输入事件的管道。
				注意,仅当窗口的属性inputFeatures不含有INPUT_FEATURE_NO_INPUT_CHANNEL时才会
				创建InputChannel,否则mInputChannel为空,从而导致此窗口无法接受任何输入事件 */
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    adjustLayoutParamsForCompatibility(mWindowAttributes);
                    /* 将窗口添加到WMS中。完成这个操作之后,mWindow已经被添加到指定的Display中?                    而且mInputChannel(如果不为空)已经准备好接收事件了。只是由于这个窗口没有进行
                    过relayout(),因此它还没有有效的Surface可以进行绘制 */
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                    setFrame(mTmpFrame);
                } 
                ......
                /* 如果mInputChannel不为空,则创建mInputEventReceiver,用于接收输入事件。
                注意第二个参数传递的是Looper.myLooper(),即mInputEventReceiver将在主线程上
                触发输入事件的读取与onInputEvent()。这是应用程序可以在onTouch()等事件响应中                直接进行UI操作等根本原因。*/
                if (inputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());
                }

                /* ViewRootImpl将作为参数view的parent。所以,ViewRootImpl可以从控件树中任何一个
                控件开始,通过回溯getParent()的方法得到 */
                view.assignParent(this);
                ......
    }

ViewRootImpl.setView方法主要是执行这几个重要步骤:

1.requestLayout()---》请求遍历控件树

2.res = mWindowSession.addToDisplayAsUser ---》将窗口添加到WMS上面

3.mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper())---》接收输入事件

4.view.assignParent(this) --》ViewRootImpl将作为DecorView的parent。所以,ViewRootImpl可以从控件树中任何一个控件开始,通过回溯getParent()的方法得到。

mWindowSession.addToDisplayAsUser ()这个方法最后会调用WindowManagerService中的addWindow()方法,这样一来,DecorView就被添加到Window上了。

回顾一下整个添加的流程

ActivityThread#handleResumeActivity —>WindowManagerImpl#addView
—>WindowManagerGlobal#addView —> ViewRootImpl#setView(调用mWindowSession.addToDisplayAsUser)

ActivityThread.handleResumeActivity 
--> wm.addView(decor, l); (WindowManagerImpl.java)
 --> WindowManagerGlobal.addView
 	--> root = new ViewRootImpl(view.getContext(), display);
 	--> mViews.add(view); // DecorView
        mRoots.add(root); // ViewRootImpl
        mParams.add(wparams); // WindowManager.LayoutParams
    --> root.setView(view, wparams, panelParentView, userId);
          -->res = mWindowSession.addToDisplayAsUser

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;