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通訊的橋樑

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章