Android走進Framework之AppCompatActivity.setContentView

CorHurd 7年前發布 | 10K 次閱讀 安卓開發 Android開發 移動開發

今天來研究下我們最熟悉的一行代碼 setContentView() 。網上也有很多關于setContentView的源碼解析,但是都是基于 Activity 源碼,而我們現在都是繼承的 AppCompatActivity ,看源碼發現改動還不少,所以我打算來研究下 AppCompatActivity 里是如何把我們的布局添加進去的。你是否也曾有過同樣的疑惑,為什么創建 Activity 就要在 onCreate() 里面調用 setContentView() ?那就讓我們來RTFSC (Read the fucking source code )。

學前疑惑

  • setContentView 中到底做了什么?為什么我們調用后就可以顯示到我們想到的布局?
  • PhoneWindow 是個什么鬼? Window 和它又有什么關系?
  • DecorView 什么干嘛的?和我們的布局有什么聯系?
  • 在我們調用 requestFeature 的時候為什么要在 setContentView 之前?

接下來,我們就來解決這些疑惑! 以下源碼基于Api24

AppCompatActivity.setContentView

我們先來看下 AppCompatActivity 中 setContentView 中做了什么

AppCompatActivity.java

//這個是我們最常用的
    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

可以看到3個重載的方法都調用 getDelegate() ,而其他的方法也都是調用了 getDelegate() ,很顯然這個是代理模式。那么這個 getDelegate() 返回的是什么呢?

AppCompatActivity.java

/**
     * @return The {@link AppCompatDelegate} being used by this Activity.
     */
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            //第一次為空,創建了AppCompatDelegate
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

我們來看下 AppCompatDelegate 是怎么創建的

AppCompatDelegate.java

private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (BuildCompat.isAtLeastN()) {
            //7.0以及7.0以上創建AppCompatDelegateImplN
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (sdk >= 23) {
            //6.0創建AppCompatDelegateImplV23
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            //...
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            //...
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

哦~原來根據不同的api版本返回不同的Delegate,我們先來看看 AppCompatDelegateImplN ,里面是否有 setContentView

AppCompatDelegateImplN.java

@RequiresApi(24)
@TargetApi(24)
class AppCompatDelegateImplN extends AppCompatDelegateImplV23 {

    AppCompatDelegateImplN(Context context, Window window, AppCompatCallback callback) {
        super(context, window, callback);
    }

    @Override
    Window.Callback wrapWindowCallback(Window.Callback callback) {
        return new AppCompatWindowCallbackN(callback);
    }

    class AppCompatWindowCallbackN extends AppCompatWindowCallbackV23 {
        AppCompatWindowCallbackN(Window.Callback callback) {
            super(callback);
        }

        @Override
        public void onProvideKeyboardShortcuts(
                List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
            final PanelFeatureState panel = getPanelState(Window.FEATURE_OPTIONS_PANEL, true);
            if (panel != null && panel.menu != null) {
                // The menu provided is one created by PhoneWindow which we don't actually use.
                // Instead we'll pass through our own...
                super.onProvideKeyboardShortcuts(data, panel.menu, deviceId);
            } else {
                // If we don't have a menu, jump pass through the original instead
                super.onProvideKeyboardShortcuts(data, menu, deviceId);
            }
        }
    }
}

發現并沒有 setContentView ,那么肯定在父類。誒,它繼承 AppCompatDelegateImplV23 ,而 AppCompatDelegateImplV23 又繼承 AppCompatDelegateImplV14 , AppCompatDelegateImplV14 又繼承 AppCompatDelegateImplV11 , AppCompatDelegateImplV11 又繼承 AppCompatDelegateImplV9 ,好,知道關系后我有點懵逼了,搞什么鬼?客官別急,我們先來畫個圖

ok,最后我在V9里找到 setContentView ,我們來看下

AppCompatDelegateImplV9.java

@Override
    public void setContentView(int resId) {
        //這個很關鍵,稍后會講
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        //把我們的布局放到contentParent里面
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mOriginalWindowCallback.onContentChanged();
    }

這是對應的3個實現的方法,發現都會調用 ensureSubDecor(); 并且都會找到 contentParent ,然后把我們的布局放入進去

ok,到這里我們來捋一捋流程。

private void ensureSubDecor() {
        if (!mSubDecorInstalled) {

            //這個mSubDecor其實就ViewGroup,調用createSubDecor()后,此時存放我們的布局的容器已經準備好了
            mSubDecor = createSubDecor();//核心代碼!

            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                onTitleChanged(title);
            }

            applyFixedSizeWindow();
            //SubDecor 安裝后的回調
            onSubDecorInstalled(mSubDecor);
            //設置標記位
            mSubDecorInstalled = true;

            // Invalidate if the panel menu hasn't been created before this.
            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
            // being called in the middle of onCreate or similar.
            // A pending invalidation will typically be resolved before the posted message
            // would run normally in order to satisfy instance state restoration.
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!isDestroyed() && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }

調用了 createSubDecor() ,看字面意思創建了一個 SubDecor ,看似跟 DecorView 有聯系。我們看下里面做了什么操作

private ViewGroup createSubDecor() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            //還記得我們使用AppCompatActivity如果不設置AppCompat主題報的錯誤嗎?就是在這里拋出來的
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }
        //初始化相關特征標志
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            //一般我們的主題默認都是NoTitle
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
        a.recycle();

        //重點!在這里就創建DecorView,至于DecorView到底是什么以及如何創建的,稍后會講到
        mWindow.getDecorView();

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        //可以看到其實就是個ViewGroup,我們接著往下看,跟DecorView到底有啥關系
        ViewGroup subDecor = null;


        if (!mWindowNoTitle) {
            //上面說了主題默認都是NoTitle,所以不會走里面的方法
            if (mIsFloating) {
                // If we're floating, inflate the dialog title decor
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);

                ...
            } else if (mHasActionBar) {

                TypedValue outValue = new TypedValue();
                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

                ...

                // Now inflate the view using the themed context and set it as the content view
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);

                /**
                 * Propagate features to DecorContentParent
                 */
                if (mOverlayActionBar) {
                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
                }
                if (mFeatureProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
                }
                if (mFeatureIndeterminateProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
                }
            }
        } else {
            //我們進入else
            if (mOverlayActionMode) {
                //調用了requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY)會走進來
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                //ok,所以如果這些我們都沒設置,默認就走到這里來了,在這里映射出了subDecor,稍后我們來看下這個布局是啥
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }

            ...
        }

        if (subDecor == null) {
            throw new IllegalArgumentException(
                    "AppCompat does not support the current theme features: { "
                            + "windowActionBar: " + mHasActionBar
                            + ", windowActionBarOverlay: "+ mOverlayActionBar
                            + ", android:windowIsFloating: " + mIsFloating
                            + ", windowActionModeOverlay: " + mOverlayActionMode
                            + ", windowNoTitle: " + mWindowNoTitle
                            + " }");
        }

        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }

        // Make the decor optionally fit system windows, like the window's decor
        ViewUtils.makeOptionalFitsSystemWindows(subDecor);
        //這個contentView很重要,是我們布局的父容器,你可以把它直接當成FrameLayout
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        //看過相關知識的同學應該知道android.R.id.content這個Id在以前是我們布局的父容器的Id
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // There might be Views already added to the Window's content view so we need to
            // migrate them to our content view
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            //注意!原來windowContentView的Id是android.R.id.content,現在設置成NO_ID
            windowContentView.setId(View.NO_ID);
            //在之前這個id是我們的父容器,現在將contentView設置成android.R.id.content,那么可以初步判定,這個contentView將會是我的父容器
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null);
            }
        }

        // Now set the Window's content view with the decor
        //注意!重要!將subDecor放入到了這個Window里面,這個Window是個抽象類,其實現類是PhoneWindow,稍后會講到
        mWindow.setContentView(subDecor);

        ....

        return subDecor;
    }

看到了requestWindowFeature是不是很熟悉?還記得我們是怎么讓Activity全屏的嗎?

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FILL_PARENT
        ,WindowManager.LayoutParams.FILL_PARENT);
        setContentView(R.layout.activity_test);
    }

而且這兩行代碼必須在setContentView()之前調用,知道為啥了吧?因為在這里就把Window的相關特征標志給初始化了,在setContentView()之后調用就不起作用了!

在代碼里其他比較重要的地方已寫了注釋,我們來看下這個 abc_screen_simple.xml 的布局到底是什么樣子的

abc_screen_simple.xml

<android.support.v7.internal.widget.FitWindowsLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/action_bar_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:fitsSystemWindows="true">

    <android.support.v7.internal.widget.ViewStubCompat
        android:id="@+id/action_mode_bar_stub"
        android:inflatedId="@+id/action_mode_bar"
        android:layout="@layout/abc_action_mode_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <include layout="@layout/abc_screen_content_include" />

</android.support.v7.internal.widget.FitWindowsLinearLayout>

abc_screen_content_include.xml

<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.v7.internal.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>

原來這個 subDecor 就是 FitWindowsLinearLayout

看到這2個布局,我們先把這2個布局用圖畫出來。

(圖不在美,能懂就行~)

從AppCompatActivity到現在布局,在我的腦海里浮現出這樣的的畫面。。。

那這是不是我們app最終的布局呢?當然不是,因為我們還沒講到非常重要的兩行代碼

mWindow.getDecorView();
mWindow.setContentView(subDecor);

注釋中說道Window是個抽象類,其實現類是PhoneWindow。那么我們先來看PhoneWindow的getDecorView做了什么

PhoneWindow.java

public class PhoneWindow extends Window implements MenuBuilder.Callback {
    @Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }
    private void installDecor() {
        //mDecor是DecorView,第一次mDecor=null,所以調用generateDecor
        if (mDecor == null) {
            mDecor = generateDecor();
               ...
         }
         //第一次mContentParent也等于null
        if (mContentParent == null) {
            //可以看到把DecorView傳入進去了
            mContentParent = generateLayout(mDecor);
        }

    }

}

在generateDecor()做了什么?其實返回了一個DecorView對象。

protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }

DecorView是啥呢?

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    ...
}

哦~原來繼承FrameLayout,起到了裝飾的作用。

我們在來看看 generateLayout() 做了什么。

protected ViewGroup generateLayout(DecorView decor) {
        TypedArray a = getWindowStyle();
        //設置一堆標志位...
        ...
        if (!mForcedStatusBarColor) {
            //獲取主題狀態欄的顏色
            mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
        }
        if (!mForcedNavigationBarColor) {
            //獲取底部NavigationBar顏色
            mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
        }

        //獲取主題一些資源
       ...

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            ...我們設置不同的主題以及樣式,會采用不同的布局文件...
        } else {
            //記住這個布局,之后我們會來驗證下布局的結構
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        //要開始更改mDecor啦~
        mDecor.startChanging();
        //注意,此時把screen_simple放到了DecorView里面
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //這里的ID_ANDROID_CONTENT就是R.id.content;
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        ...


        //這里的getContainer()返回的是個Window類,也就是父Window,一般為空
        if (getContainer() == null) {
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            }
            //設置背景
            mDecor.setWindowBackground(background);

            final Drawable frame;
            if (mFrameResource != 0) {
                frame = getContext().getDrawable(mFrameResource);
            } else {
                frame = null;
            }
            mDecor.setWindowFrame(frame);

            mDecor.setElevation(mElevation);
            mDecor.setClipToOutline(mClipToOutline);

            if (mTitle != null) {
                setTitle(mTitle);
            }

            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            setTitleColor(mTitleColor);
        }

        mDecor.finishChanging();

        return contentParent;
    }

可以看到根據不同主題屬性使用的不同的布局,然后返回了這個布局 contentParent 。

我們來看看這個screen_simple.xml布局是什么樣子的

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

咦,這個布局結構跟 subDecor 好相似啊。。

好了,到目前為止我們知道了,當我們調用 mWindow.getDecorView(); 的時候里面創建DecorView,然后又根據不同主題屬性添加不同布局放到DecorView下,然后找到這個布局的 R.id.content ,也就是 mContentParent 。ok,搞清楚 mWindow.getDecorView(); 之后,我們在來看看 mWindow.setContentView(subDecor); (注意:此時把subDecor傳入進去)

@Override
    public void setContentView(View view) {
        //調用下面的重載方法
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        //在mWindow.getDecorView()已經創建了mContentParent
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        //是否有transitions動畫。沒有,進入else
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            //重要!!將這個subDecor也就是FitWindowsLinearLayout添加到這個mContentParent里面了
            //mContentParent是FrameLayout,在之前設置的View.NO_ID
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

當調用了 mWindow.getDecorView(); 創建了DecorView以及 mContentParent ,并且把 subDecor 放到了 mContentParent 里面。我們再來回頭看看 AppCompatDelegateImplV9 ,還記得它嗎?當我們在 AppCompatActivity 的 setContentView 的時候會去調用 AppCompatDelegateImplV9 的 setContentView

AppCompatDelegateImplV9.java

@Override
    public void setContentView(View v) {
        //此時DecorView和subDecor都創建好了
        ensureSubDecor();
        //還記得調用createSubDecor的時候把原本是R.id.content的windowContentView設置成了NO_ID
        //并且將contentView也就是ContentFrameLayout設置成了R.id.content嗎?
        //也就是說此時的contentParent就是ContentFrameLayout
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        //將我的布局放到contentParent里面
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        //將我們的布局id映射成View并且放到contentParent下
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mOriginalWindowCallback.onContentChanged();
    }

完整布局

ok,看到這里,想必大家在腦海里也有個大致布局了吧,我們再來把整個app初始布局畫出來

驗證布局

接下來我們來驗證下我們布局結構是否正確

新建一個 Activity

public class TestAcitivty extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
    }
}

主題

<!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:listDivider">@color/divider_dddddd</item>
    </style>

為了演示布局非常簡單,就是一個textview

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/textView"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:orientation="vertical">

</TextView>

運行后,我們在用 hierarchyviewer 查看下

看來我們的腦補的布局是對的!

學后總結

整個流程就是這樣。看到這里我們明白了,當我們調用 setContentView 的時候加載了2次系統布局,在 PhoneWindow 里面創建了 DecorView , DecorView 是我們的最底層的View,并且將我們的布局放入到一個 ContentFrameLayout 里,我們還知道在 setContentView 的時候進行了相關特征標志初始化,所以在它之后調用 requestWindowFeature 就會不起作用然后報錯。

setContentView時序圖

知道這些之后我們不妨用時序圖來梳理下整個調用的流程

 

結語

當然,在這篇文章中,因為篇幅問題,也有許多沒有講的重要知識點,比如:

  • PhoneWindow 在哪里初始化?它做了哪些事?
  • view 樹是如何被管理的?
  • findViewById 到底是怎么找到對應的View的?
  • 為什么說 setContentView 在 onResume 在對用戶可見?
  • 等等…

 

 

來自:http://weyye.me/detail/framework-appcompatactivity-setcontentview/

 

 本文由用戶 CorHurd 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!