Android走进Framework之AppCompatActivity.setContentView

CorHurd 7年前
   <p>今天来研究下我们最熟悉的一行代码 setContentView() 。网上也有很多关于setContentView的源码解析,但是都是基于 Activity 源码,而我们现在都是继承的 AppCompatActivity ,看源码发现改动还不少,所以我打算来研究下 AppCompatActivity 里是如何把我们的布局添加进去的。你是否也曾有过同样的疑惑,为什么创建 Activity 就要在 onCreate() 里面调用 setContentView() ?那就让我们来RTFSC (Read the fucking source code )。</p>    <h2>学前疑惑</h2>    <ul>     <li>setContentView 中到底做了什么?为什么我们调用后就可以显示到我们想到的布局?</li>     <li>PhoneWindow 是个什么鬼? Window 和它又有什么关系?</li>     <li>DecorView 什么干嘛的?和我们的布局有什么联系?</li>     <li>在我们调用 requestFeature 的时候为什么要在 setContentView 之前?</li>    </ul>    <p>接下来,我们就来解决这些疑惑! <strong>以下源码基于Api24</strong></p>    <h2>AppCompatActivity.setContentView</h2>    <p>我们先来看下 AppCompatActivity 中 setContentView 中做了什么</p>    <p>AppCompatActivity.java</p>    <pre>  <code class="language-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);      }</code></pre>    <p>可以看到3个重载的方法都调用 getDelegate() ,而其他的方法也都是调用了 getDelegate() ,很显然这个是代理模式。那么这个 getDelegate() 返回的是什么呢?</p>    <p>AppCompatActivity.java</p>    <pre>  <code class="language-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;      }</code></pre>    <p>我们来看下 AppCompatDelegate 是怎么创建的</p>    <p>AppCompatDelegate.java</p>    <pre>  <code class="language-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);          }      }</code></pre>    <p>哦~原来根据不同的api版本返回不同的Delegate,我们先来看看 AppCompatDelegateImplN ,里面是否有 setContentView</p>    <p>AppCompatDelegateImplN.java</p>    <pre>  <code class="language-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);              }          }      }  }</code></pre>    <p>发现并没有 setContentView ,那么肯定在父类。诶,它继承 AppCompatDelegateImplV23 ,而 AppCompatDelegateImplV23 又继承 AppCompatDelegateImplV14 , AppCompatDelegateImplV14 又继承 AppCompatDelegateImplV11 , AppCompatDelegateImplV11 又继承 AppCompatDelegateImplV9 ,好,知道关系后我有点懵逼了,搞什么鬼?客官别急,我们先来画个图</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/aa61c6885501cffb0339526c4d8bc049.png"></p>    <p>ok,最后我在V9里找到 setContentView ,我们来看下</p>    <p>AppCompatDelegateImplV9.java</p>    <pre>  <code class="language-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();      }</code></pre>    <p>这是对应的3个实现的方法,发现都会调用 ensureSubDecor(); 并且都会找到 contentParent ,然后把我们的布局放入进去</p>    <p>ok,到这里我们来捋一捋流程。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3807a2ddb53ac38c6d76c735fa00f4c0.png"></p>    <pre>  <code class="language-java">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);              }          }      }</code></pre>    <p>调用了 createSubDecor() ,看字面意思创建了一个 SubDecor ,看似跟 DecorView 有联系。我们看下里面做了什么操作</p>    <pre>  <code class="language-java">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;      }</code></pre>    <p>看到了requestWindowFeature是不是很熟悉?还记得我们是怎么让Activity全屏的吗?</p>    <pre>  <code class="language-java">@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);      }</code></pre>    <p>而且这两行代码必须在setContentView()之前调用,知道为啥了吧?因为在这里就把Window的相关特征标志给初始化了,在setContentView()之后调用就不起作用了!</p>    <p>在代码里其他比较重要的地方已写了注释,我们来看下这个 abc_screen_simple.xml 的布局到底是什么样子的</p>    <p>abc_screen_simple.xml</p>    <pre>  <code class="language-java"><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></code></pre>    <p>abc_screen_content_include.xml</p>    <pre>  <code class="language-java"><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></code></pre>    <p>原来这个 subDecor 就是 FitWindowsLinearLayout</p>    <p>看到这2个布局,我们先把这2个布局用图画出来。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/36e42c8e9c5b0672c9c235232699ad11.png"></p>    <p>(图不在美,能懂就行~)</p>    <p>从AppCompatActivity到现在布局,在我的脑海里浮现出这样的的画面。。。</p>    <p>那这是不是我们app最终的布局呢?当然不是,因为我们还没讲到非常重要的两行代码</p>    <p>mWindow.getDecorView();<br> mWindow.setContentView(subDecor);</p>    <p>注释中说道Window是个抽象类,其实现类是PhoneWindow。那么我们先来看PhoneWindow的getDecorView做了什么</p>    <p>PhoneWindow.java</p>    <pre>  <code class="language-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);          }        }    }</code></pre>    <p>在generateDecor()做了什么?其实返回了一个DecorView对象。</p>    <pre>  <code class="language-java">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());      }</code></pre>    <p>DecorView是啥呢?</p>    <pre>  <code class="language-java">public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {      ...  }</code></pre>    <p>哦~原来继承FrameLayout,起到了装饰的作用。</p>    <p>我们在来看看 generateLayout() 做了什么。</p>    <pre>  <code class="language-java">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;      }</code></pre>    <p>可以看到根据不同主题属性使用的不同的布局,然后返回了这个布局 contentParent 。</p>    <p>我们来看看这个screen_simple.xml布局是什么样子的</p>    <pre>  <code class="language-java"><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></code></pre>    <p>咦,这个布局结构跟 subDecor 好相似啊。。</p>    <p>好了,到目前为止我们知道了,当我们调用 mWindow.getDecorView(); 的时候里面创建DecorView,然后又根据不同主题属性添加不同布局放到DecorView下,然后找到这个布局的 R.id.content ,也就是 mContentParent 。ok,搞清楚 mWindow.getDecorView(); 之后,我们在来看看 mWindow.setContentView(subDecor); (注意:此时把subDecor传入进去)</p>    <pre>  <code class="language-java">@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;      }</code></pre>    <p>当调用了 mWindow.getDecorView(); 创建了DecorView以及 mContentParent ,并且把 subDecor 放到了 mContentParent 里面。我们再来回头看看 AppCompatDelegateImplV9 ,还记得它吗?当我们在 AppCompatActivity 的 setContentView 的时候会去调用 AppCompatDelegateImplV9 的 setContentView</p>    <p>AppCompatDelegateImplV9.java</p>    <pre>  <code class="language-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();      }</code></pre>    <h2>完整布局</h2>    <p>ok,看到这里,想必大家在脑海里也有个大致布局了吧,我们再来把整个app初始布局画出来</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/64adccf73efcffb27029d030e33a061b.png"></p>    <h2>验证布局</h2>    <p>接下来我们来验证下我们布局结构是否正确</p>    <p>新建一个 Activity</p>    <pre>  <code class="language-java">public class TestAcitivty extends AppCompatActivity {      @Override      protected void onCreate(@Nullable Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_test);      }  }</code></pre>    <p>主题</p>    <pre>  <code class="language-java"><!-- 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></code></pre>    <p>为了演示布局非常简单,就是一个textview</p>    <pre>  <code class="language-java"><?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></code></pre>    <p>运行后,我们在用 hierarchyviewer 查看下</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e2a45385db58bc87c2b68273a829e2b4.png"></p>    <p>看来我们的脑补的布局是对的!</p>    <h2>学后总结</h2>    <p>整个流程就是这样。看到这里我们明白了,当我们调用 setContentView 的时候加载了2次系统布局,在 PhoneWindow 里面创建了 DecorView , DecorView 是我们的最底层的View,并且将我们的布局放入到一个 ContentFrameLayout 里,我们还知道在 setContentView 的时候进行了相关特征标志初始化,所以在它之后调用 requestWindowFeature 就会不起作用然后报错。</p>    <h2>setContentView时序图</h2>    <p>知道这些之后我们不妨用时序图来梳理下整个调用的流程</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/58a0b19f6a042468fb2e2b7aa40e12da.jpg"></p>    <p> </p>    <h2>结语</h2>    <p>当然,在这篇文章中,因为篇幅问题,也有许多没有讲的重要知识点,比如:</p>    <ul>     <li>PhoneWindow 在哪里初始化?它做了哪些事?</li>     <li>view 树是如何被管理的?</li>     <li>findViewById 到底是怎么找到对应的View的?</li>     <li>为什么说 setContentView 在 onResume 在对用户可见?</li>     <li>等等…</li>    </ul>    <p> </p>    <p> </p>    <p>来自:http://weyye.me/detail/framework-appcompatactivity-setcontentview/</p>    <p> </p>