Android学习之路(10) setContentView详解

news2024/11/24 20:01:36

一、简介

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" />
    <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源码解析再说。

(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 {
 
    @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后面设置就起不到作用,所以有了这个设计。

3.AppCompatActivity的setContentView

(1).AppCompatActivity的setContentView源码

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

(1.1).getDelegate()

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

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

(2).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();
    }

(2.1).分析ensureSubDecor()方法

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

(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 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;
    }

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

(2.1.1.1).ensureWindow();

    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中的。

(2.1.1.2).mWindow.getDecorView()方法
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上面)。

(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 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出来的,接下来我们通过一张图加深理解:

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

(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 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;
        }
    }

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

(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 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).分析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);
    }
 
    @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 ? "<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);
    }
 
    @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的重要流程。

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

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

相关文章

【SpringBoot】如何使用 HandlerInterceptor 拦截器? 为什么不使用 SpingAOP ?

文章目录 前言一、为什么不使用 SpringAOP ?1, 需求分析2, SpringAOP 能实现吗? 二、使用 HandlerInterceptor1, 实现 HandlerInterceptor 接口2, 将自定义拦截器加入到系统配置 三、HandlerInterceptor 实现原理源码分析 总结 前言 各位读者好, 我是小陈, 这是我的个人主页,…

基于全新电脑环境安装pytorch的GPU版本

前言&#xff1a; 距离第一次安装深度学习的GPU环境已经过去了4年多&#xff08;当时TensorFlow特别麻烦&#xff09;&#xff0c;现在发现安装pytorch的GPU版本还是很简单方便的&#xff0c;流程记录如下。 安装步骤&#xff1a; 步骤一&#xff1a;官网下载Anaconda Free…

【无法联网】电脑wifi列表为空的解决方案

打开电脑, 发现wifi列表为空, 点击设置显示未连接 首先检查是不是网卡驱动有问题, cmd, devmgmt.msc 找到网络适配器, 看看网卡前面是否有感叹号, 如果没有则说明网卡没问题, 有问题则重装驱动 看看网络协议是否设置正确 找到"控制面板\所有控制面板项\网络和共享中心&…

微服务通信[HTTP|RPC同步通信、MQ异步通信]

概念 A服务调用B服务,B服务调C服务,C服务调D服务,即微服务之间的通信(也可以叫微服务之间的调用) HTTP同步通信 一种轻量级的通信协议,常用于在不同的微服务之间进行通信,也是最简单的通信方式使用REST ful为开发规范&#xff0c;将服务对外暴露的HTTP调用方式为REST API(如GET…

飞腾FT-2000/4、D2000 log报错指导(3)

在爱好者群中遇见了很多的固件问题,这里总结记录了大家的交流内容和调试心得。主要是飞腾桌面CPU FT-2000/4 D2000相关的,包含uboot和UEFI。希望对大家调试有所帮助。 这个专题会持续更新,凑够一些就发。 23 在s3 唤醒时报错如下 check suspend ,Platform exception report…

你对SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用呢?

一、什么是SPA SPA&#xff08;single-page application&#xff09;&#xff0c;翻译过来就是单页应用SPA是一种网络应用程序或网站的模型&#xff0c;它通过动态重写当前页面来与用户交互&#xff0c;这种方法避免了页面之间切换打断用户体验在单页应用中&#xff0c;所有必…

【kubernetes】使用KubeSphere devops部署我的微服务系统

KubeSphere Devops 入门使用KubeSphere的Devops功能部署"我的微服务系统" &#xff08;内容学习于尚硅谷云原生课程&#xff09; kubesphere devops官方文档&#xff1a; https://v3-1.docs.kubesphere.io/zh/docs/devops-user-guide/how-to-use/create-a-pipeline-u…

nginx调优(二)

一、event模块: 用于配置服务器的事件驱动机制的模块。它定义了 Nginx 如何处理并发连接和网络事件&#xff0c;以及如何与底层操作系统的事件机制交互。 1.最大并发连接数&#xff1a; worker_connections 65536; 2.选择事件驱动&#xff1a; nginx默认使用epoll时间驱动类…

【Java】Java基础

环境准备 安装JDK和JRE 下载JDK&#xff0c;可以在官网Java Downloads | Oracle 中国下载&#xff0c;但是这里需要注册才能够下载。在Index of java-local/jdk (huaweicloud.com)也可以下载到&#xff0c;但是版本比较老&#xff0c;关系不大&#xff0c;直接下载&#xff0…

OpenGL-入门-BMP像素图glReadPixels

glReadPixels函数用于从帧缓冲区中读取像素数据。它可以用来获取屏幕上特定位置的像素颜色值或者获取一块区域内的像素数据。下面是该函数的基本语法&#xff1a; void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *da…

【Kafka】Kafka Stream简单使用

一、实时流式计算 1. 概念 一般流式计算会与批量计算相比较。在流式计算模型中&#xff0c;输入是持续的&#xff0c;可以认为在时间上是无界的&#xff0c;也就意味着&#xff0c;永远拿不到全量数据去做计算。同时&#xff0c;计算结果是持续输出的&#xff0c;也即计算结果…

微信小程序 基于Android的美容理发师预约管理系统

&#xff0c;本系统主要根据管理员、用户及理发师的实际需要&#xff0c;方便用户利用互联网实现对商品信息进行立即订购&#xff0c;同时让管理者可以通过这个系统对用户实际需求以及各信息进行管理。设计该系统主要目的是为了方便用户、理发师可以有一个非常好的平台体验&…

Mac下Docker Desktop开启本地远程访问

mac系统下&#xff0c;为了在idea里方便使用docker&#xff0c;需要开启Docker Desktop本地远程访问。 开启方法是在设置-高级下&#xff0c;开启“Allow the default Docker socket to be used (requires password)”&#xff0c;特此记录一下&#xff1a; 开启后的效果&…

iOS swift5 扫描二维码

文章目录 1.生成二维码图片2.扫描二维码&#xff08;含上下扫描动画&#xff09;2.1 记得在info.plist中添加相机权限描述 1.生成二维码图片 import UIKit import CoreImagefunc generateQRCode(from string: String) -> UIImage? {let data string.data(using: String.En…

计算机视觉:深层卷积神经网络的构建

本文重点 上一节课程中我们学习了单卷积层的前向传播,本次课程我们构建一个具有三个卷积层的卷积神经网络,然后从输入(39*39*3)开始进行三次卷积操作,我们来看一下每次卷积的输入和输出维度的变化。 第一层 第一层使用3*3*3的过滤器来提取特征,那么f[1]=3,然后步长s[…

4. 池化层相关概念

4.1 池化层原理 ① 最大池化层有时也被称为下采样。 ② dilation为空洞卷积&#xff0c;如下图所示。 ③ Ceil_model为当超出区域时&#xff0c;只取最左上角的值。 ④ 池化使得数据由5 * 5 变为3 * 3,甚至1 * 1的&#xff0c;这样导致计算的参数会大大减小。例如1080P的电…

PHP8的匿名函数-PHP8知识详解

php 8引入了匿名函数&#xff08;Anonymous Functions&#xff09;&#xff0c;它是一种创建短生命周期的函数&#xff0c;不需要命名&#xff0c;并且可以在其作用域内直接使用。以下是在PHP 8中使用匿名函数的知识要点&#xff1a; 1、创建匿名函数&#xff0c;语法格式如下&…

7.react useReducer使用与常见问题

useReducer函数 1. useState的替代方案.接收一个(state, action)>newState的reducer, 并返回当前的state以及与其配套的dispatch方法2. 在某些场景下,useReducer会比useState更加适用,例如state逻辑较为复杂, 且**包含多个子值**,或者下一个state依赖于之前的state等清楚us…

postgresql-日期函数

postgresql-日期函数 日期时间函数计算时间间隔获取时间中的信息截断日期/时间创建日期/时间获取系统时间CURRENT_DATE当前事务开始时间 时区转换 日期时间函数 PostgreSQL 提供了以下日期和时间运算的算术运算符。 计算时间间隔 age(timestamp, timestamp)函数用于计算两…

Uniapp笔记(五)uniapp语法4

本章目标 授权登录【难点、重点】 条件编译【理解】 小程序分包【理解】 一、授权登录 我的模块其实是两个组件&#xff0c;一个是登录组件&#xff0c;一个是用户信息组件&#xff0c;根据用户的登录状态判断是否要显示那个组件 1、登录的基本布局 <template><…