Activity、View、Window之間關係的分析

weixin_34198453發表於2018-05-11
1460594-625f48fc9fdbd74b.jpg
看大家都放圖,我也來一張

通常我們所看到的Activity和View最直觀的關係是在onCreate()方法中設定setContentView(LayoutId),為activity設定佈局檔案,這樣view就在介面上顯示出來了。這個方法做的操作如下:

 /**
 * Set the activity content from a layout resource.  The resource will be
 * inflated, adding all top-level views to the activity.
 *
 * @param layoutResID Resource ID to be inflated.
 *
 * @see #setContentView(android.view.View)
 * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
 */
public void setContentView(@LayoutRes int layoutResID) {
    //其實是呼叫了window(PhoneWindow)的setContentView方法
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
 }

 public Window getWindow() {
    return mWindow;
 }

activity的setContentView最終呼叫的是mWindow的setContentView方法。mWindow的初始化是在activity的attach()方法中做的。

 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) {
          。。。
    //這裡進行了初始化,mWindow的物件其實是Window的子類PhoneWindow
    mWindow = new PhoneWindow(this);
    //此處省略n行程式碼
    。。。。
}

到這裡說明了一點,activity的setContentView()是呼叫了PhoneWindow的setContentView().
接著上PhoneWindow的程式碼:

 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) {
      //1、初始化DecorView,生成mContentParent
        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 {
    //2、載入activity自己的佈局
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
//省略n行程式碼
    。。。。。
}

phoneWindow中的setContentView()方法一共可以分為兩步。第一步:初始化話DectorView 第二步:載入我們自己設定的佈局。到這裡載入activity自己的view就結束了(ps:至於涉及到FrameWork層的東西這裡就不作描述了)。主要來看一下installDecor()的操作。

private void installDecor() {
    if (mDecor == null) {
      //  生成DecorView,一個繼承了FrameLayout的view
      //private final class DecorView extends FrameLayout implements   RootViewSurfaceTaker {
        mDecor = generateDecor();
        //省略若干程式碼
    }
    if (mContentParent == null) {
        //為mContentParent 賦值
        mContentParent = generateLayout(mDecor);
    }
    //省略若干程式碼
}
//  生成DecorView
protected DecorView generateDecor() {
    return new DecorView(getContext(), -1);
}

這個方法裡面首先生成了一個DecorView(繼承了FrameLayout),然後呼叫了generateLayout(mDecor)為mContentParent 賦值。mContentParent 是做什麼用的呢?還記得當年的夏雨荷嗎?哦 不 是記得phoneWindow中的setContentView()中執行的mLayoutInflater.inflate(layoutResID, mContentParent)程式碼
嗎。沒錯,這個mContentParent就是作為了activity自己的佈局(也就是我們自己寫的佈局)的一個父佈局而存在。

接下來就看一下這個mContentParent到底是誰吧。

 protected ViewGroup generateLayout(DecorView decor) {
    //依照慣例 依然是省略n行程式碼
    。。。。
    // Inflate the window decor.

    int layoutResource;
    int features = getLocalFeatures();
    //  根據不同的features來載入不同的佈局
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
    } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
       。。。。。。
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();
    //這裡 根據layoutId(上面根據不同的features 賦值的layoutResource )載入佈局
    View in = mLayoutInflater.inflate(layoutResource, null);
    //把載入的佈局放入到decorview中
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;

  //public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
  //查詢id為ID_ANDROID_CONTENT的控制元件
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }
。。。。
//  返回id為ID_ANDROID_CONTENT的控制元件
 return contentParent;
}

上面的程式碼主要就是完成了載入系統的佈局、把佈局放入到dectorview中、查詢出com.android.internal.R.id.content的控制元件。這個控制元件就是我們自己寫的佈局的父view。
到這裡window、activity、view之間的關係就清楚了。
1、activity的attach方法中執行了window的初始化,window的例項為PhoneWindow。
2、activity的setContentView(ID)方法最終是呼叫的PhoneWindow的setContentView()方法。
3、PhoneWindow在執行setContentView()的過程中生成了一個frameLayout的子類DecorView.並且根據feature的型別載入了一個對應的系統佈局,放入了decorview中。系統佈局中有一個id為com.android.internal.R.id.content的framelayout,這個frameLayout作為一個父佈局載入我們應用中自己定義的xml檔案。

也就是說,我們所有看到的頁面其實都是在window裡面的,activity其實並不直接掌控view而是藉助於window展現的view。下面是時候放出圖了(這張圖就清楚的展現了activity、view、window之間的關係)


1460594-c45a8a390072413d.jpg
1677180-10136019b1f4c254.jpg

相關文章