Android View添加到Window的过程

Android 界面显示的过程可以分为两个步骤
1.是将我们要显示的布局添加到window上
2.在进行测量、布局、绘制
通过这两步我们想看到的View就显示在Window上了

今天说下View是怎么添加到Window上的

首先要从Activity的setContentView开始

public void setContentView(View view) {
    getWindow().setContentView(view);
    initWindowDecorActionBar();
}

看一下getWindow(),也是activity中的方法

public Window getWindow() {
    return mWindow;
}

那么看下mWindow这个成员变量是什么

private Window mWindow;

再看下mWindow在哪里初始化的

final void attach(Context context, ActivityThread aThread,
        ...

    mWindow = new PhoneWindow(this, window);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    ...
}

原来mWindow 是 PhoneWindow,那么我们再找到setContentView

@Override
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
...
}

首先判断mContentParent是否为null,如果为null执行方法installDecor();
那么mContentParent是什么?
看下他的注释吧

 // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;

意思大概是:这是放置视图窗口的内容,内容是mDecor本身,或mDecor的一个孩子
通过这句话大概能猜想到,但是我们继续往下看
我们看下mContentParent的实例化,在方法installDecor()中实例化的,我们看下installDecor()方法做了什么

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

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();
            ...

看到 mContentParent = generateLayout(mDecor);然后我们再追踪下方法generateLayout(mDecor),里面有这样一行代码

 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

再看下 ID_ANDROID_CONTENT是什么?

/**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

原来这就是为什么叫setContentView的原因,因为他的id就是content。
我们在回到installDecor()方法接着看 ,
installDecor()方法中还实例化了DecorView,而DecorView是ViewTree的最顶层的View,代表了整个应用界面,DecorView包括标题view和内容View,而内容View就是我们上面说的mContentParent,其实就是将我们想要显示的View添加到DecorView的内容部分,
我们再回到setContentView()方法,有这样一行代码

mLayoutInflater.inflate(layoutResID, mContentParent);

通过这行代码就知道mContentParent是我们要显示的View的父控件

到现在为止将我们要显示的布局添加到DecorView上了,那么DecorView又如何添加到Window上

将DecorView添加到Window上

首先要知道在Window上添加view是通过WindowManager,addView()方法,然后我们要定位到Activity的创建过程,
在ActivityThread中,当Activity被创建完毕后,会将DecorView添加到Window中,ActivityThread中的方法handleResumeActivity();

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ...
          if (r != null) {
            final Activity a = r.activity;

            if (localLOGV) Slog.v(
                TAG, "Resume " + r + " started activity: " +
                a.mStartedActivity + ", hideForNow: " + r.hideForNow
                + ", finished: " + a.mFinished);

            ...

           if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //通过activity获取WindowManager的实现类
                ViewManager wm = a.getWindowManager();
                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 impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//将decor添加到window上
                }
                ...

在handleResumeActivity方法中
获取WindowManager的实现类 ViewManager wm = a.getWindowManager();
wm.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);

            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);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

在addView的过程中实例化了ViewRootImpl类,然后调用ViewRootImpl的setView方法,并把DecorView作为参数传递进去
这就是将DecorView添加到Window的过程

到现在为止通过setContentView方法,创建了DecorView和加载了我们提供的布局,又将DecorView 添加到Window上,但是现在还是不可见的,因为还没用进行测量,布局和绘制
从ViewRootImpl中的performTraversals()方法开始View的测量、布局、绘制流程

关系总结:
PhoneWindow 是 Window的子类
DecorView 是Window的最顶层View
ViewRoot是建立DecorView与WindowsManger通讯的桥梁

发布了33 篇原创文章 · 获赞 5 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/hjiangshujing/article/details/70292015