setContentView我们在Activity中经常见到,它的作用就是把我们的布局文件放在Activity中显示,下面我们根据源码分析setContentView是如何做到的
注意Activity的setContentView和AppCompatActivity的setContentView是有一些区别的,所以我们要分析两钟setContentView,下面先分析Activity的
- public void setContentView(@LayoutRes int layoutResID) {
- getWindow().setContentView(layoutResID);
- initWindowDecorActionBar();
- }
可以看到第一句getWindow().setContentView(layoutResID),这个getWindow是获取当前Activity的Window,在Android中Window的实现类是phoneWindow,所以我们要看phoneWindow的setContentView
顺便提一下Activity的window的创建时机是在Activity的attach方法:

- 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();
- if (cb != null && !isDestroyed()) {
- cb.onContentChanged();
- }
- mContentParentExplicitlySet = true; //⭐这个flag记一下
- }
上面代码中我标记了三处重点,我们下面继续分析这三个重点都干了什么,先分析第一个installDecor()

installDecor主要是我用红框标记出来的是重点,我们先分析generateDecor(-1)这个方法:
- 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, this);
- if (mTheme != -1) {
- context.setTheme(mTheme);
- }
- }
- } else {
- context = getContext();
- }
- return new DecorView(context, featureId, this, getAttributes());//⭐重点
- }
创建了一个DecorView并且返回之后赋值给了mDecor,我们先看一下这个DecorVIew是什么:

很明显是一个FrameLayout,这下我们知道了创建了一个FrameLayout类型的DecorView然后赋值给了mDecor变量,下面继续分析installDecor的第二个重点:generateLayout(mDecor)
- protected ViewGroup generateLayout(DecorView decor) {
- ...
- else {
- // Embedded, so no decoration is needed.
- layoutResource = R.layout.screen_simple;
- // System.out.println("Simple!");
- }
-
- mDecor.startChanging();
- mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //⭐重点下面的方法
-
- ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
- if (contentParent == null) {
- throw new RuntimeException("Window couldn't find content container view");
- }
- ...
- return contentParent;
- }
-
- //⭐DecorView的onResourcesLoaded方法
- void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
- if (mBackdropFrameRenderer != null) {
- loadBackgroundDrawablesIfNeeded();
- mBackdropFrameRenderer.onResourcesLoaded(
- this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
- mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
- getCurrentColor(mNavigationColorViewState));
- }
-
- mDecorCaptionView = createDecorCaptionView(inflater);
- final View root = inflater.inflate(layoutResource, null);//⭐重点主线流程
- if (mDecorCaptionView != null) {
- if (mDecorCaptionView.getParent() == null) {
- addView(mDecorCaptionView,
- new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- }
- mDecorCaptionView.addView(root,
- new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
- } else {
-
- // Put it below the color views. ⭐重点主线流程
- addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- }
- mContentRoot = (ViewGroup) root;
- initializeElevation();
- }
这个方法的作用就是,通过我们设置的style或者requestWindowFuture等来选出一个系统自带的布局文件,默认的是R.layout.screen_simple,选出布局文件后,通过调用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);方法inflate出来后,add到DecorView上,我们详细看一下R.layout.screen_simple这个布局文件:
- <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"
- android:theme="?attr/actionBarTheme" />
- <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>
就是这个布局文件,被inflate到DecorView上,然后通过
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
这一句获取到这个是content的FrameLayout,然后返回了这个contentParent
到这里installDecor这个方法分析完了,总结一下,installDecor方法主要就是创建DecorView,然后把选出的布局文件add到DecorView上,然后再通过findViewbyId找到类型是FrameLayout的content赋值给contentParent 返回,其实还有一步是DecorView和phoneWindow结合,这里不细说,以后有FrameWorker源码解析再说。
mLayoutInflater.inflate(layoutResID, mContentParent);
这句很明显,layoutResID是我们的activity_main.layout这种自己写的布局文件,把它inflate到mContentParent中,通过图片让大家有一个更清晰的感官:

mContentParentExplicitlySet = true;
这个flag的作用首先我们先看一段代码:
- public class MainActivity extends AppCompatActivity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- }
- }
这段代码运行会报错:
requestFeature() must be called before adding content
为什么会报这个错误呢,从代码上来找:
- public final boolean requestWindowFeature(int featureId) {
- return getWindow().requestFeature(featureId);
- }
-
- //我们已经知道,getWindow其实获取的是PhoneWindow所以调用的是PhoneWindow的requestFeature
- @Override
- public boolean requestFeature(int featureId) {
- if (mContentParentExplicitlySet) { //⭐这就是报错的根源
- throw new AndroidRuntimeException("requestFeature() must be called before adding content");
- }
- final int features = getFeatures();
- final int newFeatures = features | (1 << featureId);
- if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
- (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
- // Another feature is enabled and the user is trying to enable the custom title feature
- // or custom title feature is enabled and the user is trying to enable another feature
- throw new AndroidRuntimeException(
- "You cannot combine custom titles with other title features");
- }
- if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
- return false; // Ignore. No title dominates.
- }
- if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
- // Remove the action bar feature if we have no title. No title dominates.
- removeFeature(FEATURE_ACTION_BAR);
- }
-
- if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
- getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
- throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
- }
- return super.requestFeature(featureId);
- }
看到了报错的根源,其实就是mContentParentExplicitlySet这个flag,在setContentView执行完就设置成了true,所以调用requestWindowFeature(Window.FEATURE_NO_TITLE);方法必须在setContentView之前,否则就会抛出异常,最后从设计的角度分析,为什么要设计这么一个flag呢,或者说为什么非要在setContentView之前执行requestFeature,因为在setContentView中需要通过设置的这些requestWindowFeature的flag去选择一个布局文件然后add到DecorView上,如果在setContentView后面设置就起不到作用,所以有了这个设计。
- @Override
- public void setContentView(@LayoutRes int layoutResID) {
- getDelegate().setContentView(layoutResID);
- }
- @NonNull
- public AppCompatDelegate getDelegate() {
- if (mDelegate == null) {
- mDelegate = AppCompatDelegate.create(this, this);
- }
- return mDelegate;
- }
实际上实现类是AppCompatDelegate,getDelegate().setContentView(layoutResID);的setContentView实际上是AppCompatDelegate的setContentView方法
- @Override
- public void setContentView(int resId) {
- ensureSubDecor(); //⭐重点主线
- ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //⭐重点主线
- contentParent.removeAllViews();
- LayoutInflater.from(mContext).inflate(resId, contentParent);//⭐重点主线
- mAppCompatWindowCallback.getWrapped().onContentChanged();
- }
- private void ensureSubDecor() {
- if (!mSubDecorInstalled) {
- mSubDecor = createSubDecor(); // ⭐重点主线流程
-
- // If a title was set before we installed the decor, propagate it now
- CharSequence title = getTitle();
- if (!TextUtils.isEmpty(title)) {
- if (mDecorContentParent != null) {
- mDecorContentParent.setWindowTitle(title);
- } else if (peekSupportActionBar() != null) {
- peekSupportActionBar().setWindowTitle(title);
- } else if (mTitleView != null) {
- mTitleView.setText(title);
- }
- }
-
- applyFixedSizeWindow();
-
- onSubDecorInstalled(mSubDecor);
-
- mSubDecorInstalled = true;//⭐这个flag参数
-
- // 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 (!mIsDestroyed && (st == null || st.menu == null)) {
- invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
- }
- }
- }
主要的第一句:
mSubDecor = createSubDecor();
mSubDecor是一个ViewGroup类型的对象,下面我们分析createSubDecor()
- private ViewGroup createSubDecor() {
- TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
-
- //⭐这个错误是不是曾经见到过,如果用的Theme不是AppCompatTheme的就会报错
- if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
- a.recycle();
- throw new IllegalStateException(
- "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
- }
- ......
-
- // Now let's make sure that the Window has installed its decor by retrieving it
- ensureWindow();
- mWindow.getDecorView();
-
-
- final LayoutInflater inflater = LayoutInflater.from(mContext);
- ViewGroup subDecor = null;
-
-
- if (!mWindowNoTitle) {
- ......
- } else {
- ......
- subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
- ......
- }
- ......
-
- final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
- R.id.action_bar_activity_content);
-
- 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);
- }
-
- // Change our content FrameLayout to use the android.R.id.content id.
- // Useful for fragments.
- windowContentView.setId(View.NO_ID);
- 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
- mWindow.setContentView(subDecor);
- ......
-
- return subDecor;
- }
上面显示出来的基本上都很重要,我们一句一句分析:
- private void ensureWindow() {
- // We lazily fetch the Window for Activities, to allow DayNight to apply in
- // attachBaseContext
- if (mWindow == null && mHost instanceof Activity) {
- attachToWindow(((Activity) mHost).getWindow());
- }
- if (mWindow == null) {
- throw new IllegalStateException("We have not been given a Window");
- }
- }
首先我们要明确AppCompatActivity是继承自Activity的,所以window也是在attach方法中创建的,在AppCompatDelegateImpl中也维护了一个Window类型的变量是mWindow,就是通过这个ensureWindow方法经过检查后赋值过来的。说白了ensureWindow方法就是把AppCompatActivity中的Window对象赋值到AppCompatDelegateImpl对象中,当然对window设置的callBack啥的也换成AppCompatDelegateImpl中的。
Window的实现类,所以我们要看PhoneWindow的getDecorView()方法:
- @Override
- public final @NonNull View getDecorView() {
- if (mDecor == null || mForceDecorInstall) {
- installDecor();
- }
- return mDecor;
- }
可以看到是调用了installDecor,和Activity有了相同的部分,我们简单回忆一下installDecor干了啥,首先创建了DecorView,然后通过解析我们style的设置选出合适的系统自带布局文件,把它add到DecorView上,并且返回了一个id是com.android.internal.R.id.content的FrameLayout(将来我们要把我们的activity_mai的layout文件add到这个content上面)。
- ....
- subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
- ....
- final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
- R.id.action_bar_activity_content);
-
- 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);
- }
-
- // Change our content FrameLayout to use the android.R.id.content id.
- // Useful for fragments.
- windowContentView.setId(View.NO_ID);
- contentView.setId(android.R.id.content);
-
- ......
- }
-
- // Now set the Window's content view with the decor
- mWindow.setContentView(subDecor);
- ......
-
- return subDecor;
- }
通过我们设置的style选出一个布局文件,这一步好像在installDecor中已经做过了,这样好像重复了,为什么有这样的一个重复?有这样一个重复是为了不影响原来的代码的同时,把一部分对style处理的逻辑转移到AppCompatDelegateImpl中,例如对windowTitle的隐藏与显示,这里可看出来设计师的设计,通过下面的学习慢慢体会,先看一下这个布局文件:
- <androidx.appcompat.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">
-
- <androidx.appcompat.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" />
-
- androidx.appcompat.widget.FitWindowsLinearLayout>
-
-
- abc_screen_content_include的布局文件:
- <merge xmlns:android="http://schemas.android.com/apk/res/android">
-
- <androidx.appcompat.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>
把这个布局文件inflate成view赋值给subDecor,subDecor是一个ViewGroup:
下面重点来了:
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
这一句是获取subDecor中的id是 action_bar_activity_content的ContentFrameLayout类型的View赋值给contentView
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
windowContentView是获取installDecor中选择的那个布局的id是android.R.id.content的那个FrameLayout
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
如果windowContentView中有子View,那就全部转移到contentView上
windowContentView.setId(View.NO_ID); contentView.setId(android.R.id.content);
然后把windowContentView的id设置成NO_ID,把contentView的id设置成android.R.id.content,这就是想把AppCompatDelegateImple中选的这个系统自带布局文件的content替换掉之前的installDecor方法中布局文件的content,以后我们的activity_main的layout布局文件就加载在替换之后的content上
mWindow.setContentView(subDecor);
这句调用的是phoneWindow的setContentView方法
- @Override
- public void setContentView(View view) {
- setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
- }
-
- @Override
- public void setContentView(View view, ViewGroup.LayoutParams params) {
- // 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)) {
- view.setLayoutParams(params);
- final Scene newScene = new Scene(mContentParent, view);
- transitionTo(newScene);
- } else {
- mContentParent.addView(view, params); //⭐把contentView添加到mContentParent上
- }
- mContentParent.requestApplyInsets();
- final Callback cb = getCallback();
- if (cb != null && !isDestroyed()) {
- cb.onContentChanged();
- }
- mContentParentExplicitlySet = true;
- }
我们看一下mContentParent是什么:
mContentParent = generateLayout(mDecor);
是我们取消ID的那个之前的content,也就是说把AppCompatDelegateImpl的选择的系统自带布局文件(subDecor),添加到之前的content中,最后返回subDecor注意这个subDecor是系统自带布局inflate出来的,接下来我们通过一张图加深理解:

这个mSubDecorInstalled和Activity中的mContentParentExplicitlySet一样,作用也是防止在setContentView之后调用
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
,我们发现和Activity的不一样了,我们思考一下,为什么调用supportRequestWindowFeature而不是调用requestWindowFeature?
Activity中处理这些style是在PhoneWIndow的installDecor中,而AppCompatActivity在处理一些style时是在AppCompatDelegateImpl中,requestWindowFeature是影响的installDecor,supportRequestWindowFeature是影响的AppCompatDelegateImpl,拿window的Title来举例,看上面放过的两张图,Activity的title是要在installDecor方法中决定的显示与隐藏的,而AppCompatActivity的title是放在AppCompatDelegateImpl中决定显示与隐藏的,我们用AppCompatActivity肯定是要在AppCompatDelegateImpl进行一些操作,而不是对installDecor中进行操作。
下面看一下supportRequestWindowFeature调用逻辑:
- public boolean supportRequestWindowFeature(int featureId) {
- return getDelegate().requestWindowFeature(featureId);
- }
-
- @Override
- public boolean requestWindowFeature(int featureId) {
- featureId = sanitizeWindowFeatureId(featureId);
-
- if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) {
- return false; // Ignore. No title dominates.
- }
- if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) {
- // Remove the action bar feature if we have no title. No title dominates.
- mHasActionBar = false;
- }
-
- switch (featureId) {
- case FEATURE_SUPPORT_ACTION_BAR:
- throwFeatureRequestIfSubDecorInstalled();
- mHasActionBar = true;
- return true;
- case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
- throwFeatureRequestIfSubDecorInstalled();
- mOverlayActionBar = true;
- return true;
- case FEATURE_ACTION_MODE_OVERLAY:
- throwFeatureRequestIfSubDecorInstalled();
- mOverlayActionMode = true;
- return true;
- case Window.FEATURE_PROGRESS:
- throwFeatureRequestIfSubDecorInstalled();
- mFeatureProgress = true;
- return true;
- case Window.FEATURE_INDETERMINATE_PROGRESS:
- throwFeatureRequestIfSubDecorInstalled();
- mFeatureIndeterminateProgress = true;
- return true;
- case Window.FEATURE_NO_TITLE:
- throwFeatureRequestIfSubDecorInstalled();
- mWindowNoTitle = true;
- return true;
- }
-
- return mWindow.requestFeature(featureId);
- }
-
- //⭐mSubDecorInstalled 这参数眼熟吧
- private void throwFeatureRequestIfSubDecorInstalled() {
- if (mSubDecorInstalled) {
- throw new AndroidRuntimeException(
- "Window feature must be requested before adding content");
- }
- }
- ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //⭐重点主线
- contentParent.removeAllViews();
- LayoutInflater.from(mContext).inflate(resId, contentParent);//⭐重点主线
获取contentview,注意这个contentView和Activity的contentView的区别,看上面那张图,把所有的view清除掉,然后把我们的activity_main的layout这种我们自己的布局加载上去
或者说这三种写法有什么区别:

我们先看源码,看完之后总结:
- public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
- final Resources res = getContext().getResources();
- if (DEBUG) {
- Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
- + Integer.toHexString(resource) + ")");
- }
-
- View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
- if (view != null) {
- return view;
- }
- XmlResourceParser parser = res.getLayout(resource); //⭐把布局文件用xml解析器解析
- try {
- return inflate(parser, root, attachToRoot); //调用inflate重载的方法
- } finally {
- parser.close();
- }
- }
继续往下看inflate的重载方法,这个方法中就有这道面试题的答案:
- public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
- synchronized (mConstructorArgs) {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
-
- final Context inflaterContext = mContext;
- final AttributeSet attrs = Xml.asAttributeSet(parser);
- Context lastContext = (Context) mConstructorArgs[0];
- mConstructorArgs[0] = inflaterContext;
- View result = root; //⭐重点
-
- try {
- advanceToRootNode(parser); //⭐确保下面的代码是首先解析的根布局标签
- final String name = parser.getName();
-
- if (DEBUG) {
- System.out.println("**************************");
- System.out.println("Creating root view: "
- + name);
- System.out.println("**************************");
- }
-
- if (TAG_MERGE.equals(name)) {
- if (root == null || !attachToRoot) {
- throw new InflateException("
can be used only with a valid " - + "ViewGroup root and attachToRoot=true");
- }
-
- rInflate(parser, root, inflaterContext, attrs, false);
- } else { //⭐重点 else单独分析
- // Temp is the root view that was found in the xml
- final View temp = createViewFromTag(root, name, inflaterContext, attrs);
-
- ViewGroup.LayoutParams params = null;
-
- if (root != null) {
- if (DEBUG) {
- System.out.println("Creating params from root: " +
- root);
- }
- // Create layout params that match root, if supplied
- params = root.generateLayoutParams(attrs);
- if (!attachToRoot) {
- // Set the layout params for temp if we are not
- // attaching. (If we are, we use addView, below)
- temp.setLayoutParams(params);
- }
- }
-
- if (DEBUG) {
- System.out.println("-----> start inflating children");
- }
-
- // Inflate all children under temp against its context.
- rInflateChildren(parser, temp, attrs, true);
-
- if (DEBUG) {
- System.out.println("-----> done inflating children");
- }
-
- // We are supposed to attach all the views we found (int temp)
- // to root. Do that now.
- if (root != null && attachToRoot) {
- root.addView(temp, params);
- }
-
- // Decide whether to return the root that was passed in or the
- // top view found in xml.
- if (root == null || !attachToRoot) {
- result = temp;
- }
- }
-
- } catch (XmlPullParserException e) {
- final InflateException ie = new InflateException(e.getMessage(), e);
- ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
- } catch (Exception e) {
- final InflateException ie = new InflateException(
- getParserStateDescription(inflaterContext, attrs)
- + ": " + e.getMessage(), e);
- ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
- } finally {
- // Don't retain static reference on context.
- mConstructorArgs[0] = lastContext;
- mConstructorArgs[1] = null;
-
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
-
- return result;
- }
- }
上面的代码太多我把关于这道题的主要代码逻辑拿出来:
- private void advanceToRootNode(XmlPullParser parser)
- throws InflateException, IOException, XmlPullParserException {
- // Look for the root node.
- int type;
- while ((type = parser.next()) != XmlPullParser.START_TAG &&
- type != XmlPullParser.END_DOCUMENT) {
- // Empty
- }
-
- if (type != XmlPullParser.START_TAG) {
- throw new InflateException(parser.getPositionDescription()
- + ": No start tag found!");
- }
- }
- else {
- // Temp is the root view that was found in the xml
- final View temp = createViewFromTag(root, name, inflaterContext, attrs);
-
- ViewGroup.LayoutParams params = null;
-
- if (root != null) {
- if (DEBUG) {
- System.out.println("Creating params from root: " +
- root);
- }
- // Create layout params that match root, if supplied
- params = root.generateLayoutParams(attrs);
- if (!attachToRoot) {
- // Set the layout params for temp if we are not
- // attaching. (If we are, we use addView, below)
- temp.setLayoutParams(params);
- }
- }
-
- if (DEBUG) {
- System.out.println("-----> start inflating children");
- }
-
- // Inflate all children under temp against its context.
- rInflateChildren(parser, temp, attrs, true);
-
- if (DEBUG) {
- System.out.println("-----> done inflating children");
- }
-
- // We are supposed to attach all the views we found (int temp)
- // to root. Do that now.
- if (root != null && attachToRoot) {
- root.addView(temp, params);
- }
-
- // Decide whether to return the root that was passed in or the
- // top view found in xml.
- if (root == null || !attachToRoot) {
- result = temp;
- }
- }
下面逐句分析:
(1.2.1)
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
这一句就是通过name,来反射创建view,下面会详细分析如何创建的view,这个temp就是布局文件的根view
(1.2.2)
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
这就是如果inflate方法的第二个参数root不是null,那就执行这段代码:
params = root.generateLayoutParams(attrs);
这段代码的意思是:我们把新视图的根布局参数传递给root。让root进行转换成适合它自身布局的布局参数(因为不同的布局有不同的特性,例如LinearLayout和FrameLayout,我们要把布局文件的根temp这个View放到root中,就要让temp原来的布局参数转换成适合root这个ViewGroup布局的参数),如果inflate第三个参数attachToRoot是false就把布局文件的根view设置成转换的params
(1.2.3)
rInflateChildren(parser, temp, attrs, true);
之后就继续解析布局文件,通过反射创建View,具体如何解析和创建之后详细分析
(1.2.4)
if (root != null && attachToRoot) {
root.addView(temp, params);
}
如果root不是null并且attachToRoot是true,把布局文件根生成的temp这个view添加到root上面,注意这个params还是上面转换的那个。
(1.2.5)
if (root == null || !attachToRoot) {
result = temp;
}
如果root是null或者attachToRoot是false,方法最后的返回值就是这个布局文件生成的View
(1.2.6)
到这里我们就知道了:
1.当root不为null时,attachTORoot是true的时候就直接把我们布局生成的View添加到root(这个root是inflate方法参数的第二个参数)上面,并且方法最后的返回结果是root,如果attachTORoot是false,直接返回我们布局文件生成的View注意这个生成View的layoutParams已经set了,所以可以说:
LayoutInflater.from(this).inflate(R.layout.activity_main,root,true);
等价于
View view = LayoutInflater.from(this).inflate(R.layout.activity_main,root,false); root.addView(view);
2.当root为null时,直接返回我们布局文件生成的view,注意这个生成的View没有layoutParams
,而且一旦root为null,后面的attachToRoot这个参数就失效了。
(2.1).在inflate布局文件的根标签时要注意merge标签
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
}
当一个布局文件的根标签是merge时,如果root是null或者attachToRoot是false就报错,说明布局文件跟标签是merge时不能直接生成一个View,必须依附于其他View上。
(2.2).解析不是根标签的布局
- void rInflate(XmlPullParser parser, View parent, Context context,
- AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
-
- final int depth = parser.getDepth();
- int type;
- boolean pendingRequestFocus = false;
-
- while (((type = parser.next()) != XmlPullParser.END_TAG ||
- parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-
- if (type != XmlPullParser.START_TAG) {
- continue;
- }
-
- final String name = parser.getName();
-
- if (TAG_REQUEST_FOCUS.equals(name)) {
- pendingRequestFocus = true;
- consumeChildElements(parser);
- } else if (TAG_TAG.equals(name)) {
- parseViewTag(parser, parent, attrs);
- } else if (TAG_INCLUDE.equals(name)) {
- if (parser.getDepth() == 0) {
- throw new InflateException("
cannot be the root element"); - }
- parseInclude(parser, context, parent, attrs);
- } else if (TAG_MERGE.equals(name)) {
- throw new InflateException("
must be the root element"); - } else {
- final View view = createViewFromTag(parent, name, context, attrs);
- final ViewGroup viewGroup = (ViewGroup) parent;
- final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
- rInflateChildren(parser, view, attrs, true);
- viewGroup.addView(view, params);
- }
- }
-
- if (pendingRequestFocus) {
- parent.restoreDefaultFocus();
- }
-
- if (finishInflate) {
- parent.onFinishInflate();
- }
- }
(2.2.1).解析不是根布局时,注意对merge、include标签的处理
对merge标签时直接报错,因为merge标签只能用于根标签,对include标签的处理,判断如果include是跟标签则报错,因为include标签不能用作根标签。
(2.2.2).其他的view标签会走创建view,然后通过父布局生成对应的布局参数LayoutParams,然后添加在副布局上
else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
这一段中,createViewFromTag这个方法最重要,后面都是生成LayoutParams和添加到父布局上的逻辑,最后我们分析createViewFromTag这个方法
(2.2.3).createViewFromTag创建view,注意这个Activity和AppCompatActivity有差别
a.先分析Activity的createViewFromTag创建view的流程
- private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
- return createViewFromTag(parent, name, context, attrs, false);
- }
-
- @UnsupportedAppUsage
- View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
- boolean ignoreThemeAttr) {
- if (name.equals("view")) {
- name = attrs.getAttributeValue(null, "class");
- }
-
- // Apply a theme wrapper, if allowed and one is specified.
- if (!ignoreThemeAttr) {
- final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
- final int themeResId = ta.getResourceId(0, 0);
- if (themeResId != 0) {
- context = new ContextThemeWrapper(context, themeResId);
- }
- ta.recycle();
- }
-
- try {
- View view = tryCreateView(parent, name, context, attrs);
-
- if (view == null) {
- final Object lastContext = mConstructorArgs[0];
- mConstructorArgs[0] = context;
- try {
- if (-1 == name.indexOf('.')) { //⭐重点
- view = onCreateView(context, parent, name, attrs); //⭐重点
- } else {
- view = createView(context, name, null, attrs); //⭐重点
- }
- } finally {
- mConstructorArgs[0] = lastContext;
- }
- }
-
- return view;
- } catch (InflateException e) {
- throw e;
-
- } catch (ClassNotFoundException e) {
- final InflateException ie = new InflateException(
- getParserStateDescription(context, attrs)
- + ": Error inflating class " + name, e);
- ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
-
- } catch (Exception e) {
- final InflateException ie = new InflateException(
- getParserStateDescription(context, attrs)
- + ": Error inflating class " + name, e);
- ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
- }
- }
我们分析重点:
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
if (-1 == name.indexOf('.'))这个判断是标签是不是自定义View,带"."的是自定义View,如果不是自定义View,通过onCreateView创建View,如果是自定义View,通过createView创建View
所以我们比较一下为啥不是自定义View要通过onCreateView创建而是自定义VIew的用createView创建
先说一个知识点,LayoutInflater的实现类是PhoneLayoutInflater,onCreateView方法也被重写了:
- private static final String[] sClassPrefixList = {
- "android.widget.",
- "android.webkit.",
- "android.app."
- };
-
- @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
- for (String prefix : sClassPrefixList) {
- try {
- View view = createView(name, prefix, attrs);
- if (view != null) {
- return view;
- }
- } catch (ClassNotFoundException e) {
- // In this case we want to let the base class take a crack
- // at it.
- }
- }
-
- return super.onCreateView(name, attrs);
- }
-
- 最后还调用了super:
- protected View onCreateView(String name, AttributeSet attrs)
- throws ClassNotFoundException {
- return createView(name, "android.view.", attrs);
- }
可以看到onCreateView最后还是调用了createVIew,所以onCreateView和createView主要的差别就是这个prefix的前缀,不是自定义View需要有前缀,想想LinearLayout这个类的全类名是"android.widget.LinearLayout",这下知道了这些SDK自带的不是自定义View标签都会在这里补全全类名,最后看一下createView
(2.2.4).createView方法
- static final Class>[] mConstructorSignature = new Class[] {
- Context.class, AttributeSet.class}; //View的两个参数
-
- public final View createView(@NonNull Context viewContext, @NonNull String name,
- @Nullable String prefix, @Nullable AttributeSet attrs)
- throws ClassNotFoundException, InflateException {
- Objects.requireNonNull(viewContext);
- Objects.requireNonNull(name);
- Constructor extends View> constructor = sConstructorMap.get(name);
- if (constructor != null && !verifyClassLoader(constructor)) {
- constructor = null;
- sConstructorMap.remove(name);
- }
- Class extends View> clazz = null;
-
- try {
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
-
- if (constructor == null) {//⭐主要就是通过反射去创建view
- // Class not found in the cache, see if it's real, and try to add it
- clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
- mContext.getClassLoader()).asSubclass(View.class);
-
- if (mFilter != null && clazz != null) {
- boolean allowed = mFilter.onLoadClass(clazz);
- if (!allowed) {
- failNotAllowed(name, prefix, viewContext, attrs);
- }
- }
- constructor = clazz.getConstructor(mConstructorSignature);//通过反射获取两个参数的构造方法,两个参数分别是context与AttributeSet(XML的参数集合)
- constructor.setAccessible(true);
- sConstructorMap.put(name, constructor); //用map缓存起来
- } else { //constructor不为null说明map里面有直接用map里面的
- // If we have a filter, apply it to cached constructor
- if (mFilter != null) {
- // Have we seen this name before?
- Boolean allowedState = mFilterMap.get(name);
- if (allowedState == null) {
- // New class -- remember whether it is allowed
- clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
- mContext.getClassLoader()).asSubclass(View.class);
-
- boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
- mFilterMap.put(name, allowed);
- if (!allowed) {
- failNotAllowed(name, prefix, viewContext, attrs);
- }
- } else if (allowedState.equals(Boolean.FALSE)) {
- failNotAllowed(name, prefix, viewContext, attrs);
- }
- }
- }
-
- Object lastContext = mConstructorArgs[0];
- mConstructorArgs[0] = viewContext;
- Object[] args = mConstructorArgs;
- args[1] = attrs;
-
- try {
- final View view = constructor.newInstance(args);
- if (view instanceof ViewStub) {
- // Use the same context when inflating ViewStub later.
- final ViewStub viewStub = (ViewStub) view;
- viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
- }
- return view;
- } finally {
- mConstructorArgs[0] = lastContext;
- }
- } catch (NoSuchMethodException e) {
- final InflateException ie = new InflateException(
- getParserStateDescription(viewContext, attrs)
- + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
- ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
-
- } catch (ClassCastException e) {
- // If loaded class is not a View subclass
- final InflateException ie = new InflateException(
- getParserStateDescription(viewContext, attrs)
- + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
- ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
- } catch (ClassNotFoundException e) {
- // If loadClass fails, we should propagate the exception.
- throw e;
- } catch (Exception e) {
- final InflateException ie = new InflateException(
- getParserStateDescription(viewContext, attrs) + ": Error inflating class "
- + (clazz == null ? "
" : clazz.getName()), e); - ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
- } finally {
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- }
- }
这么一大堆代码其实就是通过反射去创建View,注意里面的注释
b.再分析AppCompatActivity的createViewFromTag创建view的流程
- private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
- return createViewFromTag(parent, name, context, attrs, false);
- }
-
- @UnsupportedAppUsage
- View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
- boolean ignoreThemeAttr) {
- if (name.equals("view")) {
- name = attrs.getAttributeValue(null, "class");
- }
-
- // Apply a theme wrapper, if allowed and one is specified.
- if (!ignoreThemeAttr) {
- final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
- final int themeResId = ta.getResourceId(0, 0);
- if (themeResId != 0) {
- context = new ContextThemeWrapper(context, themeResId);
- }
- ta.recycle();
- }
-
- try {
- View view = tryCreateView(parent, name, context, attrs);//⭐重点
-
- if (view == null) {
- final Object lastContext = mConstructorArgs[0];
- mConstructorArgs[0] = context;
- try {
- if (-1 == name.indexOf('.')) {
- view = onCreateView(context, parent, name, attrs);
- } else {
- view = createView(context, name, null, attrs);
- }
- } finally {
- mConstructorArgs[0] = lastContext;
- }
- }
-
- return view;
- } catch (InflateException e) {
- throw e;
-
- } catch (ClassNotFoundException e) {
- final InflateException ie = new InflateException(
- getParserStateDescription(context, attrs)
- + ": Error inflating class " + name, e);
- ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
-
- } catch (Exception e) {
- final InflateException ie = new InflateException(
- getParserStateDescription(context, attrs)
- + ": Error inflating class " + name, e);
- ie.setStackTrace(EMPTY_STACK_TRACE);
- throw ie;
- }
- }
这个tryCreatView方法如果尝试创建View失败之后才轮到Activity的创建方式,我们看一下tryCreateView方法:
- public final View tryCreateView(@Nullable View parent, @NonNull String name,
- @NonNull Context context,
- @NonNull AttributeSet attrs) {
- if (name.equals(TAG_1995)) {
- // Let's party like it's 1995!
- return new BlinkLayout(context, attrs);
- }
-
- View view;
- if (mFactory2 != null) {
- view = mFactory2.onCreateView(parent, name, context, attrs);
- } else if (mFactory != null) {
- view = mFactory.onCreateView(name, context, attrs);
- } else {
- view = null;
- }
-
- if (view == null && mPrivateFactory != null) {
- view = mPrivateFactory.onCreateView(parent, name, context, attrs);
- }
-
- return view;
- }
发现有几个变量不认识,mFactory2和mFactory还有mPrivateFactory,我们的AppCompatActivity就是使用的mFactory2,看AppCompatActivity的onCreate方法:
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- final AppCompatDelegate delegate = getDelegate();
- delegate.installViewFactory(); // ⭐
- delegate.onCreate(savedInstanceState);
- super.onCreate(savedInstanceState);
- }
我们继续跟踪这个方法:
- public void installViewFactory() {
- LayoutInflater layoutInflater = LayoutInflater.from(mContext);
- if (layoutInflater.getFactory() == null) {
- LayoutInflaterCompat.setFactory2(layoutInflater, this);
- } else {
- if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
- Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
- + " so we can not install AppCompat's");
- }
- }
- }
-
- //Factory2是一个接口
- public interface Factory2 extends Factory {
- @Nullable
- View onCreateView(@Nullable View parent, @NonNull String name,
- @NonNull Context context, @NonNull AttributeSet attrs);
- }
-
- //LayoutInflater实现了这个接口
- class AppCompatDelegateImpl extends AppCompatDelegate
- implements MenuBuilder.Callback, LayoutInflater.Factory2
这下看到了,AppCompatActivity的oncreate方法中调用installViewFactory方法,获取到layoutInflater对象,AppCompatDelegateImpl实现了Factory2的接口,
LayoutInflaterCompat.setFactory2(layoutInflater, this);
-
- public static void setFactory2(
- @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
- inflater.setFactory2(factory); // ⭐
-
- if (Build.VERSION.SDK_INT < 21) {
- final LayoutInflater.Factory f = inflater.getFactory();
- if (f instanceof LayoutInflater.Factory2) {
- // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
- // We will now try and force set the merged factory to mFactory2
- forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
- } else {
- // Else, we will force set the original wrapped Factory2
- forceSetFactory2(inflater, factory);
- }
- }
- }
-
-
- //LayoutInflater类中的方法
- public void setFactory2(Factory2 factory) {
- if (mFactorySet) {
- throw new IllegalStateException("A factory has already been set on this LayoutInflater");
- }
- if (factory == null) {
- throw new NullPointerException("Given factory can not be null");
- }
- mFactorySet = true;
- if (mFactory == null) {
- mFactory = mFactory2 = factory;
- } else {
- mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
- }
- }
可以看到吧Factory2传进了infalter中,所以Inflater的对象中Factory2不是null了。
Factory2不是null了,在执行创建createViewFromTag方法的tryCreateView时:
- public final View tryCreateView(@Nullable View parent, @NonNull String name,
- @NonNull Context context,
- @NonNull AttributeSet attrs) {
- if (name.equals(TAG_1995)) {
- // Let's party like it's 1995!
- return new BlinkLayout(context, attrs);
- }
-
- View view;
- if (mFactory2 != null) {
- view = mFactory2.onCreateView(parent, name, context, attrs);
- } else if (mFactory != null) {
- view = mFactory.onCreateView(name, context, attrs);
- } else {
- view = null;
- }
-
- if (view == null && mPrivateFactory != null) {
- view = mPrivateFactory.onCreateView(parent, name, context, attrs);
- }
-
- return view;
- }
mFactory2不是null了,就会执行 view = mFactory2.onCreateView(parent, name, context, attrs);
我们知道,这个mFactory2其实就是AppCompatDelegateImpl的实例对象,这个设计挺巧妙和上面ensureDecor有点像,添加了一些逻辑,使对view的创建逻辑转移到了AppCompatDelegateImpl中,所以我们下面看AppCompatDelegateImpl的onCreateView方法:
- @Override
- public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
- return createView(parent, name, context, attrs);
- }
-
- @Override
- public View createView(View parent, final String name, @NonNull Context context,
- @NonNull AttributeSet attrs) {
- if (mAppCompatViewInflater == null) {
- TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
- String viewInflaterClassName =
- a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
- if ((viewInflaterClassName == null)
- || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
- // Either default class name or set explicitly to null. In both cases
- // create the base inflater (no reflection)
- mAppCompatViewInflater = new AppCompatViewInflater();
- } else {
- try {
- Class> viewInflaterClass = Class.forName(viewInflaterClassName);
- mAppCompatViewInflater =
- (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
- .newInstance();
- } catch (Throwable t) {
- Log.i(TAG, "Failed to instantiate custom view inflater "
- + viewInflaterClassName + ". Falling back to default.", t);
- mAppCompatViewInflater = new AppCompatViewInflater();
- }
- }
- }
-
- boolean inheritContext = false;
- if (IS_PRE_LOLLIPOP) {
- inheritContext = (attrs instanceof XmlPullParser)
- // If we have a XmlPullParser, we can detect where we are in the layout
- ? ((XmlPullParser) attrs).getDepth() > 1
- // Otherwise we have to use the old heuristic
- : shouldInheritContext((ViewParent) parent);
- }
- //⭐
- return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
- IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
- true, /* Read read app:theme as a fallback at all times for legacy reasons */
- VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
- );
- }
可以看到创建了一个mAppCompatViewInflater ,最后调用了mAppCompatViewInflater的createView方法:
- final View createView(View parent, final String name, @NonNull Context context,
- @NonNull AttributeSet attrs, boolean inheritContext,
- boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
- final Context originalContext = context;
-
- // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
- // by using the parent's context
- if (inheritContext && parent != null) {
- context = parent.getContext();
- }
- if (readAndroidTheme || readAppTheme) {
- // We then apply the theme on the context, if specified
- context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
- }
- if (wrapContext) {
- context = TintContextWrapper.wrap(context);
- }
-
- View view = null;
-
- // We need to 'inject' our tint aware Views in place of the standard framework versions
- switch (name) {
- case "TextView":
- view = createTextView(context, attrs);
- verifyNotNull(view, name);
- break;
- case "ImageView":
- view = createImageView(context, attrs);
- verifyNotNull(view, name);
- break;
- case "Button":
- view = createButton(context, attrs);
- verifyNotNull(view, name);
- break;
- case "EditText":
- view = createEditText(context, attrs);
- verifyNotNull(view, name);
- break;
- case "Spinner":
- view = createSpinner(context, attrs);
- verifyNotNull(view, name);
- break;
- case "ImageButton":
- view = createImageButton(context, attrs);
- verifyNotNull(view, name);
- break;
- case "CheckBox":
- view = createCheckBox(context, attrs);
- verifyNotNull(view, name);
- break;
- case "RadioButton":
- view = createRadioButton(context, attrs);
- verifyNotNull(view, name);
- break;
- case "CheckedTextView":
- view = createCheckedTextView(context, attrs);
- verifyNotNull(view, name);
- break;
- case "AutoCompleteTextView":
- view = createAutoCompleteTextView(context, attrs);
- verifyNotNull(view, name);
- break;
- case "MultiAutoCompleteTextView":
- view = createMultiAutoCompleteTextView(context, attrs);
- verifyNotNull(view, name);
- break;
- case "RatingBar":
- view = createRatingBar(context, attrs);
- verifyNotNull(view, name);
- break;
- case "SeekBar":
- view = createSeekBar(context, attrs);
- verifyNotNull(view, name);
- break;
- case "ToggleButton":
- view = createToggleButton(context, attrs);
- verifyNotNull(view, name);
- break;
- default:
- // The fallback that allows extending class to take over view inflation
- // for other tags. Note that we don't check that the result is not-null.
- // That allows the custom inflater path to fall back on the default one
- // later in this method.
- view = createView(context, name, attrs);
- }
-
- if (view == null && originalContext != context) {
- // If the original context does not equal our themed context, then we need to manually
- // inflate it using the name so that android:theme takes effect.
- view = createViewFromTag(context, name, attrs);
- }
-
- if (view != null) {
- // If we have created a view, check its android:onClick
- checkOnClickListener(view, attrs);
- }
-
- return view;
- }
可以看到有一个switch,如果View是case中的一种,那就把布局参数传进去重新创建一个AppCompat的View,如果不是case中的一种,进default,这个createView方法直接返回null,
protected View createView(Context context, String name, AttributeSet attrs) {
return null;
}
然后走下面这句:
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
- private View createViewFromTag(Context context, String name, AttributeSet attrs) {
- if (name.equals("view")) {
- name = attrs.getAttributeValue(null, "class");
- }
-
- try {
- mConstructorArgs[0] = context;
- mConstructorArgs[1] = attrs;
-
- if (-1 == name.indexOf('.')) {
- for (int i = 0; i < sClassPrefixList.length; i++) {
- final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
- if (view != null) {
- return view;
- }
- }
- return null;
- } else {
- return createViewByPrefix(context, name, null);
- }
- } catch (Exception e) {
- // We do not want to catch these, lets return null and let the actual LayoutInflater
- // try
- return null;
- } finally {
- // Don't retain references on context.
- mConstructorArgs[0] = null;
- mConstructorArgs[1] = null;
- }
- }
-
- private View createViewByPrefix(Context context, String name, String prefix)
- throws ClassNotFoundException, InflateException {
- Constructor extends View> constructor = sConstructorMap.get(name);
-
- try {
- if (constructor == null) {
- // Class not found in the cache, see if it's real, and try to add it
- Class extends View> clazz = Class.forName(
- prefix != null ? (prefix + name) : name,
- false,
- context.getClassLoader()).asSubclass(View.class);
-
- constructor = clazz.getConstructor(sConstructorSignature);
- sConstructorMap.put(name, constructor);
- }
- constructor.setAccessible(true);
- return constructor.newInstance(mConstructorArgs);
- } catch (Exception e) {
- // We do not want to catch these, lets return null and let the actual LayoutInflater
- // try
- return null;
- }
- }
这个方法和Activity的逻辑一致了
总结一下:AppCompatActivity通过LayoutInfater解析创建View时,会通过setFactory2拦截原有Activity的逻辑,去执行AppCompatDelegateImpl的逻辑,在View解析和创建的时候,会先检查,如果是AppCompat新设计的View就是case里的那一堆,就把这个View转换成AppCompat新设计的View,如果不是还是按照之前的逻辑来。
1.setContentView的总结
setContentView总的来说就是创建DecorView,DecorView是一个FrameLayout,然后根据style选择系统自带的布局文件,(例如有没有title,这里说一下这个布局文件根布局是linearLayout,如果有title则是有一个viewStub和两个FrameLayout:title的和content的,如果没有title则是一个viewstub和一个content的FrameLayout),添加到DecorView上,最后再把我们自己的的activity_mian这种layout添加到content这个FrameLyout上。
注意Activity和AppCompatActivity有一些区别,但总体上的逻辑是不变的。
2.inflate的总结
总结了inflate三个参数的作用,总结了解析到include、merge等标签时的注意点,总结了如何解析xml文件和如何创建View的重要流程。