Android学习之路(10) setContentView详解

一、简介

setContentView我们在Activity中经常见到,它的作用就是把我们的布局文件放在Activity中显示,下面我们根据源码分析setContentView是如何做到的

二、源码分析

1.两种setContentView

注意Activity的setContentView和AppCompatActivity的setContentView是有一些区别的,所以我们要分析两钟setContentView,下面先分析Activity的

2.Activity的setContentView

(1).从Activity的setContentView这个方法开始

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方法:

(2).继续跟踪到phoneWindow的setContentView

    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()

(2.1).分析phoneWindow的setContentView的第一个关键点installDecor()

installDecor主要是我用红框标记出来的是重点,我们先分析generateDecor(-1)这个方法:

(2.1.1).分析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)

(2.1.2).分析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" /><FrameLayoutandroid: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源码解析再说。

(2.2).继续分析phoneWindow的setContentView的第二个关键流程重点

mLayoutInflater.inflate(layoutResID, mContentParent);

这句很明显,layoutResID是我们的activity_main.layout这种自己写的布局文件,把它inflate到mContentParent中,通过图片让大家有一个更清晰的感官:

(2.3).继续分析phoneWindow的setContentView的第三个关键流程重点

mContentParentExplicitlySet = true;

这个flag的作用首先我们先看一段代码:

public class MainActivity extends AppCompatActivity {@Overrideprotected 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@Overridepublic 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 featurethrow 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后面设置就起不到作用,所以有了这个设计。

3.AppCompatActivity的setContentView

(1).AppCompatActivity的setContentView源码

    @Overridepublic void setContentView(@LayoutRes int layoutResID) {getDelegate().setContentView(layoutResID);}

(1.1).getDelegate()

    @NonNullpublic AppCompatDelegate getDelegate() {if (mDelegate == null) {mDelegate = AppCompatDelegate.create(this, this);}return mDelegate;}

实际上实现类是AppCompatDelegate,getDelegate().setContentView(layoutResID);的setContentView实际上是AppCompatDelegate的setContentView方法

(2).AppCompatDelegate的setContentView方法

    @Overridepublic void setContentView(int resId) {ensureSubDecor(); //⭐重点主线ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //⭐重点主线contentParent.removeAllViews();LayoutInflater.from(mContext).inflate(resId, contentParent);//⭐重点主线mAppCompatWindowCallback.getWrapped().onContentChanged();}

(2.1).分析ensureSubDecor()方法

    private void ensureSubDecor() {if (!mSubDecorInstalled) {mSubDecor = createSubDecor(); // ⭐重点主线流程// If a title was set before we installed the decor, propagate it nowCharSequence 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()

(2.1.1).ensureSubDecor()方法中的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 itensureWindow(); 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 viewwhile (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 ourselvesif (windowContentView instanceof FrameLayout) {((FrameLayout) windowContentView).setForeground(null);}}// Now set the Window's content view with the decormWindow.setContentView(subDecor);
......return subDecor;}

上面显示出来的基本上都很重要,我们一句一句分析:

(2.1.1.1).ensureWindow();

    private void ensureWindow() {// We lazily fetch the Window for Activities, to allow DayNight to apply in// attachBaseContextif (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中的。

(2.1.1.2).mWindow.getDecorView()方法
Window的实现类,所以我们要看PhoneWindow的getDecorView()方法:

    @Overridepublic 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上面)。

(2.1.1.3).后面要分析的这一堆代码就是Activity和AppCompatActivity的setContentView的主要区别

....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 viewwhile (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 decormWindow.setContentView(subDecor);
......return subDecor;}

通过我们设置的style选出一个布局文件,这一步好像在installDecor中已经做过了,这样好像重复了,为什么有这样的一个重复?有这样一个重复是为了不影响原来的代码的同时,把一部分对style处理的逻辑转移到AppCompatDelegateImpl中,例如对windowTitle的隐藏与显示,这里可看出来设计师的设计,通过下面的学习慢慢体会,先看一下这个布局文件:

<androidx.appcompat.widget.FitWindowsLinearLayoutxmlns: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.ViewStubCompatandroid: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.ContentFrameLayoutandroid: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方法

    @Overridepublic void setContentView(View view) {setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));}@Overridepublic 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出来的,接下来我们通过一张图加深理解:

(2.1.2).ensureSubDecor()方法中的一个boolean值mSubDecorInstalled
这个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);}@Overridepublic 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");}}

(2.2).AppCompatDelegateImpl中的setContentView(View v)剩下的一起说

ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //⭐重点主线
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//⭐重点主线

获取contentview,注意这个contentView和Activity的contentView的区别,看上面那张图,把所有的view清除掉,然后把我们的activity_main的layout这种我们自己的布局加载上去

3.分析LayoutInflater.from(mContext).inflate(resId, contentParent);方法

(1).很经典的一道面试题,inflate三个参数都有什么作用
或者说这三种写法有什么区别:

我们先看源码,看完之后总结:

    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("<merge /> 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 xmlfinal 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 suppliedparams = 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;}}

上面的代码太多我把关于这道题的主要代码逻辑拿出来:

(1.1).advanceToRootNode()这个方法就是确保第一个解析的是布局的根标签

    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!");}}

(1.2).else的逻辑单独分析

else {// Temp is the root view that was found in the xmlfinal 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 suppliedparams = 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 suppliedparams = 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).分析inflate方法如何解析和创建View的
(2.1).在inflate布局文件的根标签时要注意merge标签

if (TAG_MERGE.equals(name)) {if (root == null || !attachToRoot) {throw new InflateException("<merge /> 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("<include /> cannot be the root element");}parseInclude(parser, context, parent, attrs);} else if (TAG_MERGE.equals(name)) {throw new InflateException("<merge /> 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);}@UnsupportedAppUsageView 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 itclazz = 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 constructorif (mFilter != null) {// Have we seen this name before?Boolean allowedState = mFilterMap.get(name);if (allowedState == null) {// New class -- remember whether it is allowedclazz = 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 subclassfinal 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 ? "<unknown>" : 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);}@UnsupportedAppUsageView 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方法:

    @Overrideprotected 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 {@NullableView onCreateView(@Nullable View parent, @NonNull String name,@NonNull Context context, @NonNull AttributeSet attrs);}//LayoutInflater实现了这个接口
class AppCompatDelegateImpl extends AppCompatDelegateimplements 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 mFactory2forceSetFactory2(inflater, (LayoutInflater.Factory2) f);} else {// Else, we will force set the original wrapped Factory2forceSetFactory2(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方法:

    @Overridepublic final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {return createView(parent, name, context, attrs);}@Overridepublic 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 contextif (inheritContext && parent != null) {context = parent.getContext();}if (readAndroidTheme || readAppTheme) {// We then apply the theme on the context, if specifiedcontext = 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 versionsswitch (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:onClickcheckOnClickListener(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// tryreturn 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 itClass<? 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// tryreturn 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的重要流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/56613.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

斯坦福人生设计课——简略笔记

来源&#xff1a;⽐尔博内特 戴夫伊万斯 著图书《人生设计课》 目录 一、认清当下的情况&#xff0c;从四个维度观察自己的人生 二、平衡人生&#xff0c;但不要走入误区 2.1 记录你的“美好时光日志”&#xff1a; 2.1.1 记录内容&#xff1a; 2.1.2 辅助反思的方法&…

WebRTC音视频通话-iOS端调用ossrs直播拉流

WebRTC音视频通话-iOS端调用ossrs直播拉流 之前实现iOS端调用ossrs服务&#xff0c;文中提到了推流。没有写拉流流程&#xff0c;所以会用到文中的WebRTCClient。请详细查看&#xff1a;https://blog.csdn.net/gloryFlow/article/details/132262724 一、iOS播放端拉流效果 二…

强训第40天

选择 A 在域名解析之前&#xff0c;需要先与访问域名服务器&#xff0c;与域名服务器之间的网络通信需要涉及相邻设备之间的数据传输。也就是ARP&#xff0c;所以ARP是第一个 B C D 可靠传输的基础是确认应答以及超时重传。这俩是通过序号和确认序号交付的 A B 对用户是不透明的…

Rust之自动化测试(一):如何编写测试

开发环境 Windows 10Rust 1.71.1 VS Code 1.81.1 项目工程 这里继续沿用上次工程rust-demo 编写自动化测试 Edsger W. Dijkstra在他1972年的文章《谦逊的程序员》中说&#xff0c;“程序测试可以是一种非常有效的方法来显示错误的存在&#xff0c;但它对于显示它们的不存在…

架构评估-架构师之路(十二)

软件系统质量属性 软件系统质量熟悉分为 开发期质量属性 和 运行期质量属性。 质量属性 性能&#xff1a;指 系统的响应能力&#xff0c;如 响应时间&#xff0c;吞吐率。 设计策略&#xff1a;优先级队列、增加计算资源、减少计算开销、引入并发机制、采用资源调度。 可靠…

【ArcGIS Pro二次开发】(62):复制字段

应网友需求&#xff0c;做了这么一个复制字段的小工具。 假定这样一个场景&#xff0c;手头有一个要素1&#xff0c;要素里有10个字段&#xff0c;另一个要素2&#xff0c;除了shape_area等图形字段外&#xff0c;没有其它字段。 现在的需求是&#xff0c;想把要素1中的8个字…

【跨域异常】

想在前端使用vue获取后端接口的数据&#xff0c;但是报了跨域异常&#xff0c;如下图所示。 一种解决的方式是&#xff0c;在后端Controller接口上加上CrossOrigin&#xff0c;从后端解决跨域问题。 还要注意前端请求的url要加上协议&#xff0c;比如http://

vue使用vant中的popup层,在popup层中加搜索功能后,input框获取焦点 ios机型的软键盘不会将popup顶起来的问题

1.使用vant的popup弹出层做了一个piker的选择器,用户需要在此基础上增加筛选功能。也就是输入框 2.可是在ios机型中,input框在获取焦点以后,ios的软键盘弹起会遮盖住我们的popup层,导致体验不是很好 3.在大佬的解答及帮助下,采用窗口滚动的方式解决此方法 <Popupv-model&q…

数据结构--树4.2(二叉树)

目录 一、二叉树的定义和特点 1、定义 2、特点 二、二叉树的基本形态 1、空二叉树 2、只有一个根结点 3、根结点只有左子树 4、根结点只有右子树 5、根结点既有左子树又有右子树 6、斜树 7、满二叉树 8、满二叉树和完全二叉树 三、二叉树的性质 一、二叉树的定义和…

Element——table排序,上移下移功能。及按钮上一条下一条功能

需求&#xff1a;table排序&#xff0c;可操作排序上移下移功能。判断第一行上移禁用和最后一行下移禁用&#xff0c;排序根据后端返回的字段 <el-table:data"tableData"style"width: 100%"><el-table-column type"index" label"序…

JVM虚拟机:定位对象的两种方式

定位对象的方式 1、句柄池 2、直接指针 ‘句柄池 直接指针 在Java中&#xff0c;可以使用两种方式来定位对象&#xff1a;句柄池和直接指针。 1. 句柄池&#xff1a;在Java的句柄池模型中&#xff0c;Java虚拟机&#xff08;JVM&#xff09;会为每个对象创建一个句柄&#xff…

软件工程(九) UML顺序-活动-状态-通信图

顺序图和后面的一些图,要求没有用例图和类图那么高,但仍然是比较重要的,我们也需要按程度去了解。 1、顺序图 顺序图(sequence diagram, 顺序图),顺序图是一种交互图(interaction diagram),它强调的是对象之间消息发送的顺序,同时显示对象之间的交互。 下面以一个简…

算法通关村第5关【白银】| 哈希和栈经典算法题

1.两个栈实现队列 思路&#xff1a;两个栈&#xff0c;一个输入栈&#xff0c;一个输出栈。 当需要输入的时候就往inStack中插入&#xff0c;需要输出就往outStack中输出&#xff0c;当输出栈是空就倒出输入栈的数据到输出栈中&#xff0c;这样就保证了后插入的数据从栈顶倒入…

HCIP-HCS华为私有云的使用

1、概述 HCS&#xff08;HuaweiCoudStack&#xff09;华为私有云&#xff1a;6.3 之前叫FusionSphere OpenStack&#xff0c;6.3.1 版本开始叫FusionCloud&#xff0c;6.5.1 版本开始叫HuaweiCloud Stack (HCS)华为私有云软件。 开源openstack&#xff0c;发放云主机的流程&am…

【应用层】网络基础 -- HTTPS协议

HTTPS 协议原理加密为什么要加密常见的加密方式对称加密非对称加密 数据摘要&&数据指纹 HTTPS 的工作过程探究方案1-只使用对称加密方案2-只使用非对称加密方案3-双方都使用非对称加密方案4-非对称加密对称加密中间人攻击-针对上面的场景 CA认证理解数据签名方案5-非对…

git 统计(命令)

查询某人某个时刻提交了多少代码 added 添加代码 removed 删除代码 total 总代码 git log --author刘俊秦 --since2023-08-01 00:00:00 --until2023-08-23 23:00:00 --prettytformat: --numstat | awk { add $1; subs $2; loc $1 - $2 } END { printf "added lines: %s…

【猿灰灰赠书活动 - 03期】- 【RHCSA/RHCE 红帽Linux认证学习指南(第7版) EX200 EX300】

说明&#xff1a;博文为大家争取福利&#xff0c;与清华大学出版社合作进行送书活动 图书&#xff1a;《RHCSA/RHCE 红帽Linux认证学习指南(第7版) EX200 & EX300》 一、好书推荐 图书介绍 《RHCSA/RHCE 红帽Linux认证学习指南&#xff08;第7版&#xff09; EX200 & E…

【2023最新版】R安装(直接+Anaconda)及使用(Pycharm配置R)教程

目录 一、R语言 1. R官网 2. R介绍 二、直接安装R 1. 下载 2. 安装 三、Pycharm使用R 1. 安装Pycharm 2. R Language for IntelliJ插件 3. R设置 报错 4. R软件包 安装 加载 查看已安装的包 四、使用Anaconda创建R语言虚拟环境 1. 安装Anaconda 2. 创建R语言…

arcgis+postgresql+postgis使用介绍

关于arcgis在postgresql创建地理数据库我分享一下自己的经历&#xff1a; 众所周知&#xff0c;arcgis如果在oracle中创建地理数据库&#xff0c;必须要使用ArcToolbox里面的地理数据库工具去创建&#xff0c;在里面发现它还可以创建sql_server, postgresql数据库类型&#xf…

当我焦虑时,我从CSDN的博主身上学到了什么?

文章目录 前言一、思考为什么会产生差距1.1 懒惰1.2 没有合理的规划学习时间 二、我该如何做&#xff1f;2.1 认真生活规律作息2.2 做事就是0和1 结语 前言 我们在学习的过程当中总会遇到一些比我们自己优秀的人&#xff0c;不论你是在更好的985或211院校学习&#xff0c;还是…