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

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