• setContentView详解


    一、简介

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

    二、源码分析

    1.两种setContentView

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

    2.Activity的setContentView

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

    1. public void setContentView(@LayoutRes int layoutResID) {
    2. getWindow().setContentView(layoutResID);
    3. initWindowDecorActionBar();
    4. }

    可以看到第一句getWindow().setContentView(layoutResID),这个getWindow是获取当前Activity的Window,在Android中Window的实现类是phoneWindow,所以我们要看phoneWindow的setContentView

    顺便提一下Activity的window的创建时机是在Activity的attach方法:

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

    1. public void setContentView(int layoutResID) {
    2. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    3. // decor, when theme attributes and the like are crystalized. Do not check the feature
    4. // before this happens.
    5. if (mContentParent == null) {
    6. installDecor(); //⭐这句关键流程
    7. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    8. mContentParent.removeAllViews();
    9. }
    10. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    11. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
    12. getContext());
    13. transitionTo(newScene);
    14. } else {
    15. mLayoutInflater.inflate(layoutResID, mContentParent);//⭐这句关键流程
    16. }
    17. mContentParent.requestApplyInsets();
    18. final Callback cb = getCallback();
    19. if (cb != null && !isDestroyed()) {
    20. cb.onContentChanged();
    21. }
    22. mContentParentExplicitlySet = true; //⭐这个flag记一下
    23. }

    上面代码中我标记了三处重点,我们下面继续分析这三个重点都干了什么,先分析第一个installDecor()

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

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

    (2.1.1).分析installDecor()方法中的generateDecor(-1)方法

    1. protected DecorView generateDecor(int featureId) {
    2. // System process doesn't have application context and in that case we need to directly use
    3. // the context we have. Otherwise we want the application context, so we don't cling to the
    4. // activity.
    5. Context context;
    6. if (mUseDecorContext) {
    7. Context applicationContext = getContext().getApplicationContext();
    8. if (applicationContext == null) {
    9. context = getContext();
    10. } else {
    11. context = new DecorContext(applicationContext, this);
    12. if (mTheme != -1) {
    13. context.setTheme(mTheme);
    14. }
    15. }
    16. } else {
    17. context = getContext();
    18. }
    19. return new DecorView(context, featureId, this, getAttributes());//⭐重点
    20. }

    创建了一个DecorView并且返回之后赋值给了mDecor,我们先看一下这个DecorVIew是什么:

     很明显是一个FrameLayout,这下我们知道了创建了一个FrameLayout类型的DecorView然后赋值给了mDecor变量,下面继续分析installDecor的第二个重点:generateLayout(mDecor)

    (2.1.2).分析installDecor的第二个重点:generateLayout(mDecor)

    1. protected ViewGroup generateLayout(DecorView decor) {
    2. ...
    3. else {
    4. // Embedded, so no decoration is needed.
    5. layoutResource = R.layout.screen_simple;
    6. // System.out.println("Simple!");
    7. }
    8. mDecor.startChanging();
    9. mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //⭐重点下面的方法
    10. ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    11. if (contentParent == null) {
    12. throw new RuntimeException("Window couldn't find content container view");
    13. }
    14. ...
    15. return contentParent;
    16. }
    17. //⭐DecorView的onResourcesLoaded方法
    18. void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    19. if (mBackdropFrameRenderer != null) {
    20. loadBackgroundDrawablesIfNeeded();
    21. mBackdropFrameRenderer.onResourcesLoaded(
    22. this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
    23. mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
    24. getCurrentColor(mNavigationColorViewState));
    25. }
    26. mDecorCaptionView = createDecorCaptionView(inflater);
    27. final View root = inflater.inflate(layoutResource, null);//⭐重点主线流程
    28. if (mDecorCaptionView != null) {
    29. if (mDecorCaptionView.getParent() == null) {
    30. addView(mDecorCaptionView,
    31. new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    32. }
    33. mDecorCaptionView.addView(root,
    34. new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    35. } else {
    36. // Put it below the color views. ⭐重点主线流程
    37. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    38. }
    39. mContentRoot = (ViewGroup) root;
    40. initializeElevation();
    41. }

    这个方法的作用就是,通过我们设置的style或者requestWindowFuture等来选出一个系统自带的布局文件,默认的是R.layout.screen_simple,选出布局文件后,通过调用mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);方法inflate出来后,add到DecorView上,我们详细看一下R.layout.screen_simple这个布局文件:

    1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    2. android:layout_width="match_parent"
    3. android:layout_height="match_parent"
    4. android:fitsSystemWindows="true"
    5. android:orientation="vertical">
    6. <ViewStub android:id="@+id/action_mode_bar_stub"
    7. android:inflatedId="@+id/action_mode_bar"
    8. android:layout="@layout/action_mode_bar"
    9. android:layout_width="match_parent"
    10. android:layout_height="wrap_content"
    11. android:theme="?attr/actionBarTheme" />
    12. <FrameLayout
    13. android:id="@android:id/content"
    14. android:layout_width="match_parent"
    15. android:layout_height="match_parent"
    16. android:foregroundInsidePadding="false"
    17. android:foregroundGravity="fill_horizontal|top"
    18. android:foreground="?android:attr/windowContentOverlay" />
    19. 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的作用首先我们先看一段代码:

    1. public class MainActivity extends AppCompatActivity {
    2. @Override
    3. protected void onCreate(Bundle savedInstanceState) {
    4. super.onCreate(savedInstanceState);
    5. setContentView(R.layout.activity_main);
    6. requestWindowFeature(Window.FEATURE_NO_TITLE);
    7. }
    8. }

    这段代码运行会报错:

    requestFeature() must be called before adding content

     为什么会报这个错误呢,从代码上来找:

    1. public final boolean requestWindowFeature(int featureId) {
    2. return getWindow().requestFeature(featureId);
    3. }
    4. //我们已经知道,getWindow其实获取的是PhoneWindow所以调用的是PhoneWindow的requestFeature
    5. @Override
    6. public boolean requestFeature(int featureId) {
    7. if (mContentParentExplicitlySet) { //⭐这就是报错的根源
    8. throw new AndroidRuntimeException("requestFeature() must be called before adding content");
    9. }
    10. final int features = getFeatures();
    11. final int newFeatures = features | (1 << featureId);
    12. if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
    13. (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
    14. // Another feature is enabled and the user is trying to enable the custom title feature
    15. // or custom title feature is enabled and the user is trying to enable another feature
    16. throw new AndroidRuntimeException(
    17. "You cannot combine custom titles with other title features");
    18. }
    19. if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
    20. return false; // Ignore. No title dominates.
    21. }
    22. if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
    23. // Remove the action bar feature if we have no title. No title dominates.
    24. removeFeature(FEATURE_ACTION_BAR);
    25. }
    26. if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
    27. getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
    28. throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
    29. }
    30. return super.requestFeature(featureId);
    31. }

    看到了报错的根源,其实就是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源码

    1. @Override
    2. public void setContentView(@LayoutRes int layoutResID) {
    3. getDelegate().setContentView(layoutResID);
    4. }

    (1.1).getDelegate()

    1. @NonNull
    2. public AppCompatDelegate getDelegate() {
    3. if (mDelegate == null) {
    4. mDelegate = AppCompatDelegate.create(this, this);
    5. }
    6. return mDelegate;
    7. }

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

    (2).AppCompatDelegate的setContentView方法

    1. @Override
    2. public void setContentView(int resId) {
    3. ensureSubDecor(); //⭐重点主线
    4. ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //⭐重点主线
    5. contentParent.removeAllViews();
    6. LayoutInflater.from(mContext).inflate(resId, contentParent);//⭐重点主线
    7. mAppCompatWindowCallback.getWrapped().onContentChanged();
    8. }

    (2.1).分析ensureSubDecor()方法

    1. private void ensureSubDecor() {
    2. if (!mSubDecorInstalled) {
    3. mSubDecor = createSubDecor(); // ⭐重点主线流程
    4. // If a title was set before we installed the decor, propagate it now
    5. CharSequence title = getTitle();
    6. if (!TextUtils.isEmpty(title)) {
    7. if (mDecorContentParent != null) {
    8. mDecorContentParent.setWindowTitle(title);
    9. } else if (peekSupportActionBar() != null) {
    10. peekSupportActionBar().setWindowTitle(title);
    11. } else if (mTitleView != null) {
    12. mTitleView.setText(title);
    13. }
    14. }
    15. applyFixedSizeWindow();
    16. onSubDecorInstalled(mSubDecor);
    17. mSubDecorInstalled = true;//⭐这个flag参数
    18. // Invalidate if the panel menu hasn't been created before this.
    19. // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
    20. // being called in the middle of onCreate or similar.
    21. // A pending invalidation will typically be resolved before the posted message
    22. // would run normally in order to satisfy instance state restoration.
    23. PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
    24. if (!mIsDestroyed && (st == null || st.menu == null)) {
    25. invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
    26. }
    27. }
    28. }

     主要的第一句:

    mSubDecor = createSubDecor();

    mSubDecor是一个ViewGroup类型的对象,下面我们分析createSubDecor()

    (2.1.1).ensureSubDecor()方法中的createSubDecor()方法

    1. private ViewGroup createSubDecor() {
    2. TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    3. //⭐这个错误是不是曾经见到过,如果用的Theme不是AppCompatTheme的就会报错
    4. if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
    5. a.recycle();
    6. throw new IllegalStateException(
    7. "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
    8. }
    9. ......
    10. // Now let's make sure that the Window has installed its decor by retrieving it
    11. ensureWindow();
    12. mWindow.getDecorView();
    13. final LayoutInflater inflater = LayoutInflater.from(mContext);
    14. ViewGroup subDecor = null;
    15. if (!mWindowNoTitle) {
    16. ......
    17. } else {
    18. ......
    19. subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
    20. ......
    21. }
    22. ......
    23. final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
    24. R.id.action_bar_activity_content);
    25. final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    26. if (windowContentView != null) {
    27. // There might be Views already added to the Window's content view so we need to
    28. // migrate them to our content view
    29. while (windowContentView.getChildCount() > 0) {
    30. final View child = windowContentView.getChildAt(0);
    31. windowContentView.removeViewAt(0);
    32. contentView.addView(child);
    33. }
    34. // Change our content FrameLayout to use the android.R.id.content id.
    35. // Useful for fragments.
    36. windowContentView.setId(View.NO_ID);
    37. contentView.setId(android.R.id.content);
    38. // The decorContent may have a foreground drawable set (windowContentOverlay).
    39. // Remove this as we handle it ourselves
    40. if (windowContentView instanceof FrameLayout) {
    41. ((FrameLayout) windowContentView).setForeground(null);
    42. }
    43. }
    44. // Now set the Window's content view with the decor
    45. mWindow.setContentView(subDecor);
    46. ......
    47. return subDecor;
    48. }

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

    (2.1.1.1).ensureWindow(); 

    1. private void ensureWindow() {
    2. // We lazily fetch the Window for Activities, to allow DayNight to apply in
    3. // attachBaseContext
    4. if (mWindow == null && mHost instanceof Activity) {
    5. attachToWindow(((Activity) mHost).getWindow());
    6. }
    7. if (mWindow == null) {
    8. throw new IllegalStateException("We have not been given a Window");
    9. }
    10. }

    首先我们要明确AppCompatActivity是继承自Activity的,所以window也是在attach方法中创建的,在AppCompatDelegateImpl中也维护了一个Window类型的变量是mWindow,就是通过这个ensureWindow方法经过检查后赋值过来的。说白了ensureWindow方法就是把AppCompatActivity中的Window对象赋值到AppCompatDelegateImpl对象中,当然对window设置的callBack啥的也换成AppCompatDelegateImpl中的。

    (2.1.1.2).mWindow.getDecorView()方法

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

    1. @Override
    2. public final @NonNull View getDecorView() {
    3. if (mDecor == null || mForceDecorInstall) {
    4. installDecor();
    5. }
    6. return mDecor;
    7. }

    可以看到是调用了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的主要区别

    1. ....
    2. subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
    3. ....
    4. final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
    5. R.id.action_bar_activity_content);
    6. final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    7. if (windowContentView != null) {
    8. // There might be Views already added to the Window's content view so we need to
    9. // migrate them to our content view
    10. while (windowContentView.getChildCount() > 0) {
    11. final View child = windowContentView.getChildAt(0);
    12. windowContentView.removeViewAt(0);
    13. contentView.addView(child);
    14. }
    15. // Change our content FrameLayout to use the android.R.id.content id.
    16. // Useful for fragments.
    17. windowContentView.setId(View.NO_ID);
    18. contentView.setId(android.R.id.content);
    19. ......
    20. }
    21. // Now set the Window's content view with the decor
    22. mWindow.setContentView(subDecor);
    23. ......
    24. return subDecor;
    25. }

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

    1. <androidx.appcompat.widget.FitWindowsLinearLayout
    2. xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:id="@+id/action_bar_root"
    4. android:layout_width="match_parent"
    5. android:layout_height="match_parent"
    6. android:orientation="vertical"
    7. android:fitsSystemWindows="true">
    8. <androidx.appcompat.widget.ViewStubCompat
    9. android:id="@+id/action_mode_bar_stub"
    10. android:inflatedId="@+id/action_mode_bar"
    11. android:layout="@layout/abc_action_mode_bar"
    12. android:layout_width="match_parent"
    13. android:layout_height="wrap_content" />
    14. <include layout="@layout/abc_screen_content_include" />
    15. androidx.appcompat.widget.FitWindowsLinearLayout>
    16. abc_screen_content_include的布局文件:
    17. <merge xmlns:android="http://schemas.android.com/apk/res/android">
    18. <androidx.appcompat.widget.ContentFrameLayout
    19. android:id="@id/action_bar_activity_content"
    20. android:layout_width="match_parent"
    21. android:layout_height="match_parent"
    22. android:foregroundGravity="fill_horizontal|top"
    23. android:foreground="?android:attr/windowContentOverlay" />
    24. 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方法

    1. @Override
    2. public void setContentView(View view) {
    3. setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    4. }
    5. @Override
    6. public void setContentView(View view, ViewGroup.LayoutParams params) {
    7. // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    8. // decor, when theme attributes and the like are crystalized. Do not check the feature
    9. // before this happens.
    10. if (mContentParent == null) {
    11. installDecor();
    12. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    13. mContentParent.removeAllViews();
    14. }
    15. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    16. view.setLayoutParams(params);
    17. final Scene newScene = new Scene(mContentParent, view);
    18. transitionTo(newScene);
    19. } else {
    20. mContentParent.addView(view, params); //⭐把contentView添加到mContentParent上
    21. }
    22. mContentParent.requestApplyInsets();
    23. final Callback cb = getCallback();
    24. if (cb != null && !isDestroyed()) {
    25. cb.onContentChanged();
    26. }
    27. mContentParentExplicitlySet = true;
    28. }

    我们看一下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调用逻辑:

    1. public boolean supportRequestWindowFeature(int featureId) {
    2. return getDelegate().requestWindowFeature(featureId);
    3. }
    4. @Override
    5. public boolean requestWindowFeature(int featureId) {
    6. featureId = sanitizeWindowFeatureId(featureId);
    7. if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) {
    8. return false; // Ignore. No title dominates.
    9. }
    10. if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) {
    11. // Remove the action bar feature if we have no title. No title dominates.
    12. mHasActionBar = false;
    13. }
    14. switch (featureId) {
    15. case FEATURE_SUPPORT_ACTION_BAR:
    16. throwFeatureRequestIfSubDecorInstalled();
    17. mHasActionBar = true;
    18. return true;
    19. case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
    20. throwFeatureRequestIfSubDecorInstalled();
    21. mOverlayActionBar = true;
    22. return true;
    23. case FEATURE_ACTION_MODE_OVERLAY:
    24. throwFeatureRequestIfSubDecorInstalled();
    25. mOverlayActionMode = true;
    26. return true;
    27. case Window.FEATURE_PROGRESS:
    28. throwFeatureRequestIfSubDecorInstalled();
    29. mFeatureProgress = true;
    30. return true;
    31. case Window.FEATURE_INDETERMINATE_PROGRESS:
    32. throwFeatureRequestIfSubDecorInstalled();
    33. mFeatureIndeterminateProgress = true;
    34. return true;
    35. case Window.FEATURE_NO_TITLE:
    36. throwFeatureRequestIfSubDecorInstalled();
    37. mWindowNoTitle = true;
    38. return true;
    39. }
    40. return mWindow.requestFeature(featureId);
    41. }
    42. //⭐mSubDecorInstalled 这参数眼熟吧
    43. private void throwFeatureRequestIfSubDecorInstalled() {
    44. if (mSubDecorInstalled) {
    45. throw new AndroidRuntimeException(
    46. "Window feature must be requested before adding content");
    47. }
    48. }

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

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

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

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

    (1).很经典的一道面试题,inflate三个参数都有什么作用

    或者说这三种写法有什么区别:

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

    1. public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    2. final Resources res = getContext().getResources();
    3. if (DEBUG) {
    4. Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
    5. + Integer.toHexString(resource) + ")");
    6. }
    7. View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
    8. if (view != null) {
    9. return view;
    10. }
    11. XmlResourceParser parser = res.getLayout(resource); //⭐把布局文件用xml解析器解析
    12. try {
    13. return inflate(parser, root, attachToRoot); //调用inflate重载的方法
    14. } finally {
    15. parser.close();
    16. }
    17. }

    继续往下看inflate的重载方法,这个方法中就有这道面试题的答案:

    1. public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    2. synchronized (mConstructorArgs) {
    3. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
    4. final Context inflaterContext = mContext;
    5. final AttributeSet attrs = Xml.asAttributeSet(parser);
    6. Context lastContext = (Context) mConstructorArgs[0];
    7. mConstructorArgs[0] = inflaterContext;
    8. View result = root; //⭐重点
    9. try {
    10. advanceToRootNode(parser); //⭐确保下面的代码是首先解析的根布局标签
    11. final String name = parser.getName();
    12. if (DEBUG) {
    13. System.out.println("**************************");
    14. System.out.println("Creating root view: "
    15. + name);
    16. System.out.println("**************************");
    17. }
    18. if (TAG_MERGE.equals(name)) {
    19. if (root == null || !attachToRoot) {
    20. throw new InflateException(" can be used only with a valid "
    21. + "ViewGroup root and attachToRoot=true");
    22. }
    23. rInflate(parser, root, inflaterContext, attrs, false);
    24. } else { //⭐重点 else单独分析
    25. // Temp is the root view that was found in the xml
    26. final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    27. ViewGroup.LayoutParams params = null;
    28. if (root != null) {
    29. if (DEBUG) {
    30. System.out.println("Creating params from root: " +
    31. root);
    32. }
    33. // Create layout params that match root, if supplied
    34. params = root.generateLayoutParams(attrs);
    35. if (!attachToRoot) {
    36. // Set the layout params for temp if we are not
    37. // attaching. (If we are, we use addView, below)
    38. temp.setLayoutParams(params);
    39. }
    40. }
    41. if (DEBUG) {
    42. System.out.println("-----> start inflating children");
    43. }
    44. // Inflate all children under temp against its context.
    45. rInflateChildren(parser, temp, attrs, true);
    46. if (DEBUG) {
    47. System.out.println("-----> done inflating children");
    48. }
    49. // We are supposed to attach all the views we found (int temp)
    50. // to root. Do that now.
    51. if (root != null && attachToRoot) {
    52. root.addView(temp, params);
    53. }
    54. // Decide whether to return the root that was passed in or the
    55. // top view found in xml.
    56. if (root == null || !attachToRoot) {
    57. result = temp;
    58. }
    59. }
    60. } catch (XmlPullParserException e) {
    61. final InflateException ie = new InflateException(e.getMessage(), e);
    62. ie.setStackTrace(EMPTY_STACK_TRACE);
    63. throw ie;
    64. } catch (Exception e) {
    65. final InflateException ie = new InflateException(
    66. getParserStateDescription(inflaterContext, attrs)
    67. + ": " + e.getMessage(), e);
    68. ie.setStackTrace(EMPTY_STACK_TRACE);
    69. throw ie;
    70. } finally {
    71. // Don't retain static reference on context.
    72. mConstructorArgs[0] = lastContext;
    73. mConstructorArgs[1] = null;
    74. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    75. }
    76. return result;
    77. }
    78. }

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

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

    1. private void advanceToRootNode(XmlPullParser parser)
    2. throws InflateException, IOException, XmlPullParserException {
    3. // Look for the root node.
    4. int type;
    5. while ((type = parser.next()) != XmlPullParser.START_TAG &&
    6. type != XmlPullParser.END_DOCUMENT) {
    7. // Empty
    8. }
    9. if (type != XmlPullParser.START_TAG) {
    10. throw new InflateException(parser.getPositionDescription()
    11. + ": No start tag found!");
    12. }
    13. }

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

    1. else {
    2. // Temp is the root view that was found in the xml
    3. final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    4. ViewGroup.LayoutParams params = null;
    5. if (root != null) {
    6. if (DEBUG) {
    7. System.out.println("Creating params from root: " +
    8. root);
    9. }
    10. // Create layout params that match root, if supplied
    11. params = root.generateLayoutParams(attrs);
    12. if (!attachToRoot) {
    13. // Set the layout params for temp if we are not
    14. // attaching. (If we are, we use addView, below)
    15. temp.setLayoutParams(params);
    16. }
    17. }
    18. if (DEBUG) {
    19. System.out.println("-----> start inflating children");
    20. }
    21. // Inflate all children under temp against its context.
    22. rInflateChildren(parser, temp, attrs, true);
    23. if (DEBUG) {
    24. System.out.println("-----> done inflating children");
    25. }
    26. // We are supposed to attach all the views we found (int temp)
    27. // to root. Do that now.
    28. if (root != null && attachToRoot) {
    29. root.addView(temp, params);
    30. }
    31. // Decide whether to return the root that was passed in or the
    32. // top view found in xml.
    33. if (root == null || !attachToRoot) {
    34. result = temp;
    35. }
    36. }

    下面逐句分析:

    (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(" 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).解析不是根标签的布局

    1. void rInflate(XmlPullParser parser, View parent, Context context,
    2. AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    3. final int depth = parser.getDepth();
    4. int type;
    5. boolean pendingRequestFocus = false;
    6. while (((type = parser.next()) != XmlPullParser.END_TAG ||
    7. parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
    8. if (type != XmlPullParser.START_TAG) {
    9. continue;
    10. }
    11. final String name = parser.getName();
    12. if (TAG_REQUEST_FOCUS.equals(name)) {
    13. pendingRequestFocus = true;
    14. consumeChildElements(parser);
    15. } else if (TAG_TAG.equals(name)) {
    16. parseViewTag(parser, parent, attrs);
    17. } else if (TAG_INCLUDE.equals(name)) {
    18. if (parser.getDepth() == 0) {
    19. throw new InflateException(" cannot be the root element");
    20. }
    21. parseInclude(parser, context, parent, attrs);
    22. } else if (TAG_MERGE.equals(name)) {
    23. throw new InflateException(" must be the root element");
    24. } else {
    25. final View view = createViewFromTag(parent, name, context, attrs);
    26. final ViewGroup viewGroup = (ViewGroup) parent;
    27. final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
    28. rInflateChildren(parser, view, attrs, true);
    29. viewGroup.addView(view, params);
    30. }
    31. }
    32. if (pendingRequestFocus) {
    33. parent.restoreDefaultFocus();
    34. }
    35. if (finishInflate) {
    36. parent.onFinishInflate();
    37. }
    38. }

    (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的流程

    1. private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    2. return createViewFromTag(parent, name, context, attrs, false);
    3. }
    4. @UnsupportedAppUsage
    5. View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
    6. boolean ignoreThemeAttr) {
    7. if (name.equals("view")) {
    8. name = attrs.getAttributeValue(null, "class");
    9. }
    10. // Apply a theme wrapper, if allowed and one is specified.
    11. if (!ignoreThemeAttr) {
    12. final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
    13. final int themeResId = ta.getResourceId(0, 0);
    14. if (themeResId != 0) {
    15. context = new ContextThemeWrapper(context, themeResId);
    16. }
    17. ta.recycle();
    18. }
    19. try {
    20. View view = tryCreateView(parent, name, context, attrs);
    21. if (view == null) {
    22. final Object lastContext = mConstructorArgs[0];
    23. mConstructorArgs[0] = context;
    24. try {
    25. if (-1 == name.indexOf('.')) { //⭐重点
    26. view = onCreateView(context, parent, name, attrs); //⭐重点
    27. } else {
    28. view = createView(context, name, null, attrs); //⭐重点
    29. }
    30. } finally {
    31. mConstructorArgs[0] = lastContext;
    32. }
    33. }
    34. return view;
    35. } catch (InflateException e) {
    36. throw e;
    37. } catch (ClassNotFoundException e) {
    38. final InflateException ie = new InflateException(
    39. getParserStateDescription(context, attrs)
    40. + ": Error inflating class " + name, e);
    41. ie.setStackTrace(EMPTY_STACK_TRACE);
    42. throw ie;
    43. } catch (Exception e) {
    44. final InflateException ie = new InflateException(
    45. getParserStateDescription(context, attrs)
    46. + ": Error inflating class " + name, e);
    47. ie.setStackTrace(EMPTY_STACK_TRACE);
    48. throw ie;
    49. }
    50. }

     我们分析重点:

    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方法也被重写了:

    1. private static final String[] sClassPrefixList = {
    2. "android.widget.",
    3. "android.webkit.",
    4. "android.app."
    5. };
    6. @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
    7. for (String prefix : sClassPrefixList) {
    8. try {
    9. View view = createView(name, prefix, attrs);
    10. if (view != null) {
    11. return view;
    12. }
    13. } catch (ClassNotFoundException e) {
    14. // In this case we want to let the base class take a crack
    15. // at it.
    16. }
    17. }
    18. return super.onCreateView(name, attrs);
    19. }
    20. 最后还调用了super:
    21. protected View onCreateView(String name, AttributeSet attrs)
    22. throws ClassNotFoundException {
    23. return createView(name, "android.view.", attrs);
    24. }

    可以看到onCreateView最后还是调用了createVIew,所以onCreateView和createView主要的差别就是这个prefix的前缀,不是自定义View需要有前缀,想想LinearLayout这个类的全类名是"android.widget.LinearLayout",这下知道了这些SDK自带的不是自定义View标签都会在这里补全全类名,最后看一下createView

    (2.2.4).createView方法

    1. static final Class[] mConstructorSignature = new Class[] {
    2. Context.class, AttributeSet.class}; //View的两个参数
    3. public final View createView(@NonNull Context viewContext, @NonNull String name,
    4. @Nullable String prefix, @Nullable AttributeSet attrs)
    5. throws ClassNotFoundException, InflateException {
    6. Objects.requireNonNull(viewContext);
    7. Objects.requireNonNull(name);
    8. Constructorextends View> constructor = sConstructorMap.get(name);
    9. if (constructor != null && !verifyClassLoader(constructor)) {
    10. constructor = null;
    11. sConstructorMap.remove(name);
    12. }
    13. Classextends View> clazz = null;
    14. try {
    15. Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
    16. if (constructor == null) {//⭐主要就是通过反射去创建view
    17. // Class not found in the cache, see if it's real, and try to add it
    18. clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
    19. mContext.getClassLoader()).asSubclass(View.class);
    20. if (mFilter != null && clazz != null) {
    21. boolean allowed = mFilter.onLoadClass(clazz);
    22. if (!allowed) {
    23. failNotAllowed(name, prefix, viewContext, attrs);
    24. }
    25. }
    26. constructor = clazz.getConstructor(mConstructorSignature);//通过反射获取两个参数的构造方法,两个参数分别是context与AttributeSet(XML的参数集合)
    27. constructor.setAccessible(true);
    28. sConstructorMap.put(name, constructor); //用map缓存起来
    29. } else { //constructor不为null说明map里面有直接用map里面的
    30. // If we have a filter, apply it to cached constructor
    31. if (mFilter != null) {
    32. // Have we seen this name before?
    33. Boolean allowedState = mFilterMap.get(name);
    34. if (allowedState == null) {
    35. // New class -- remember whether it is allowed
    36. clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
    37. mContext.getClassLoader()).asSubclass(View.class);
    38. boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
    39. mFilterMap.put(name, allowed);
    40. if (!allowed) {
    41. failNotAllowed(name, prefix, viewContext, attrs);
    42. }
    43. } else if (allowedState.equals(Boolean.FALSE)) {
    44. failNotAllowed(name, prefix, viewContext, attrs);
    45. }
    46. }
    47. }
    48. Object lastContext = mConstructorArgs[0];
    49. mConstructorArgs[0] = viewContext;
    50. Object[] args = mConstructorArgs;
    51. args[1] = attrs;
    52. try {
    53. final View view = constructor.newInstance(args);
    54. if (view instanceof ViewStub) {
    55. // Use the same context when inflating ViewStub later.
    56. final ViewStub viewStub = (ViewStub) view;
    57. viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
    58. }
    59. return view;
    60. } finally {
    61. mConstructorArgs[0] = lastContext;
    62. }
    63. } catch (NoSuchMethodException e) {
    64. final InflateException ie = new InflateException(
    65. getParserStateDescription(viewContext, attrs)
    66. + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
    67. ie.setStackTrace(EMPTY_STACK_TRACE);
    68. throw ie;
    69. } catch (ClassCastException e) {
    70. // If loaded class is not a View subclass
    71. final InflateException ie = new InflateException(
    72. getParserStateDescription(viewContext, attrs)
    73. + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
    74. ie.setStackTrace(EMPTY_STACK_TRACE);
    75. throw ie;
    76. } catch (ClassNotFoundException e) {
    77. // If loadClass fails, we should propagate the exception.
    78. throw e;
    79. } catch (Exception e) {
    80. final InflateException ie = new InflateException(
    81. getParserStateDescription(viewContext, attrs) + ": Error inflating class "
    82. + (clazz == null ? "" : clazz.getName()), e);
    83. ie.setStackTrace(EMPTY_STACK_TRACE);
    84. throw ie;
    85. } finally {
    86. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    87. }
    88. }

    这么一大堆代码其实就是通过反射去创建View,注意里面的注释

    b.再分析AppCompatActivity的createViewFromTag创建view的流程

    1. private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    2. return createViewFromTag(parent, name, context, attrs, false);
    3. }
    4. @UnsupportedAppUsage
    5. View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
    6. boolean ignoreThemeAttr) {
    7. if (name.equals("view")) {
    8. name = attrs.getAttributeValue(null, "class");
    9. }
    10. // Apply a theme wrapper, if allowed and one is specified.
    11. if (!ignoreThemeAttr) {
    12. final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
    13. final int themeResId = ta.getResourceId(0, 0);
    14. if (themeResId != 0) {
    15. context = new ContextThemeWrapper(context, themeResId);
    16. }
    17. ta.recycle();
    18. }
    19. try {
    20. View view = tryCreateView(parent, name, context, attrs);//⭐重点
    21. if (view == null) {
    22. final Object lastContext = mConstructorArgs[0];
    23. mConstructorArgs[0] = context;
    24. try {
    25. if (-1 == name.indexOf('.')) {
    26. view = onCreateView(context, parent, name, attrs);
    27. } else {
    28. view = createView(context, name, null, attrs);
    29. }
    30. } finally {
    31. mConstructorArgs[0] = lastContext;
    32. }
    33. }
    34. return view;
    35. } catch (InflateException e) {
    36. throw e;
    37. } catch (ClassNotFoundException e) {
    38. final InflateException ie = new InflateException(
    39. getParserStateDescription(context, attrs)
    40. + ": Error inflating class " + name, e);
    41. ie.setStackTrace(EMPTY_STACK_TRACE);
    42. throw ie;
    43. } catch (Exception e) {
    44. final InflateException ie = new InflateException(
    45. getParserStateDescription(context, attrs)
    46. + ": Error inflating class " + name, e);
    47. ie.setStackTrace(EMPTY_STACK_TRACE);
    48. throw ie;
    49. }
    50. }

     这个tryCreatView方法如果尝试创建View失败之后才轮到Activity的创建方式,我们看一下tryCreateView方法:

    1. public final View tryCreateView(@Nullable View parent, @NonNull String name,
    2. @NonNull Context context,
    3. @NonNull AttributeSet attrs) {
    4. if (name.equals(TAG_1995)) {
    5. // Let's party like it's 1995!
    6. return new BlinkLayout(context, attrs);
    7. }
    8. View view;
    9. if (mFactory2 != null) {
    10. view = mFactory2.onCreateView(parent, name, context, attrs);
    11. } else if (mFactory != null) {
    12. view = mFactory.onCreateView(name, context, attrs);
    13. } else {
    14. view = null;
    15. }
    16. if (view == null && mPrivateFactory != null) {
    17. view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    18. }
    19. return view;
    20. }

    发现有几个变量不认识,mFactory2和mFactory还有mPrivateFactory,我们的AppCompatActivity就是使用的mFactory2,看AppCompatActivity的onCreate方法:

    1. @Override
    2. protected void onCreate(@Nullable Bundle savedInstanceState) {
    3. final AppCompatDelegate delegate = getDelegate();
    4. delegate.installViewFactory(); // ⭐
    5. delegate.onCreate(savedInstanceState);
    6. super.onCreate(savedInstanceState);
    7. }

    我们继续跟踪这个方法:

    1. public void installViewFactory() {
    2. LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    3. if (layoutInflater.getFactory() == null) {
    4. LayoutInflaterCompat.setFactory2(layoutInflater, this);
    5. } else {
    6. if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
    7. Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
    8. + " so we can not install AppCompat's");
    9. }
    10. }
    11. }
    12. //Factory2是一个接口
    13. public interface Factory2 extends Factory {
    14. @Nullable
    15. View onCreateView(@Nullable View parent, @NonNull String name,
    16. @NonNull Context context, @NonNull AttributeSet attrs);
    17. }
    18. //LayoutInflater实现了这个接口
    19. class AppCompatDelegateImpl extends AppCompatDelegate
    20. implements MenuBuilder.Callback, LayoutInflater.Factory2

    这下看到了,AppCompatActivity的oncreate方法中调用installViewFactory方法,获取到layoutInflater对象,AppCompatDelegateImpl实现了Factory2的接口,

    LayoutInflaterCompat.setFactory2(layoutInflater, this);
    1. public static void setFactory2(
    2. @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
    3. inflater.setFactory2(factory); // ⭐
    4. if (Build.VERSION.SDK_INT < 21) {
    5. final LayoutInflater.Factory f = inflater.getFactory();
    6. if (f instanceof LayoutInflater.Factory2) {
    7. // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
    8. // We will now try and force set the merged factory to mFactory2
    9. forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
    10. } else {
    11. // Else, we will force set the original wrapped Factory2
    12. forceSetFactory2(inflater, factory);
    13. }
    14. }
    15. }
    16. //LayoutInflater类中的方法
    17. public void setFactory2(Factory2 factory) {
    18. if (mFactorySet) {
    19. throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    20. }
    21. if (factory == null) {
    22. throw new NullPointerException("Given factory can not be null");
    23. }
    24. mFactorySet = true;
    25. if (mFactory == null) {
    26. mFactory = mFactory2 = factory;
    27. } else {
    28. mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    29. }
    30. }

    可以看到吧Factory2传进了infalter中,所以Inflater的对象中Factory2不是null了。

    Factory2不是null了,在执行创建createViewFromTag方法的tryCreateView时:

    1. public final View tryCreateView(@Nullable View parent, @NonNull String name,
    2. @NonNull Context context,
    3. @NonNull AttributeSet attrs) {
    4. if (name.equals(TAG_1995)) {
    5. // Let's party like it's 1995!
    6. return new BlinkLayout(context, attrs);
    7. }
    8. View view;
    9. if (mFactory2 != null) {
    10. view = mFactory2.onCreateView(parent, name, context, attrs);
    11. } else if (mFactory != null) {
    12. view = mFactory.onCreateView(name, context, attrs);
    13. } else {
    14. view = null;
    15. }
    16. if (view == null && mPrivateFactory != null) {
    17. view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    18. }
    19. return view;
    20. }

    mFactory2不是null了,就会执行 view = mFactory2.onCreateView(parent, name, context, attrs);

    我们知道,这个mFactory2其实就是AppCompatDelegateImpl的实例对象,这个设计挺巧妙和上面ensureDecor有点像,添加了一些逻辑,使对view的创建逻辑转移到了AppCompatDelegateImpl中,所以我们下面看AppCompatDelegateImpl的onCreateView方法:

    1. @Override
    2. public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    3. return createView(parent, name, context, attrs);
    4. }
    5. @Override
    6. public View createView(View parent, final String name, @NonNull Context context,
    7. @NonNull AttributeSet attrs) {
    8. if (mAppCompatViewInflater == null) {
    9. TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    10. String viewInflaterClassName =
    11. a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
    12. if ((viewInflaterClassName == null)
    13. || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
    14. // Either default class name or set explicitly to null. In both cases
    15. // create the base inflater (no reflection)
    16. mAppCompatViewInflater = new AppCompatViewInflater();
    17. } else {
    18. try {
    19. Class viewInflaterClass = Class.forName(viewInflaterClassName);
    20. mAppCompatViewInflater =
    21. (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
    22. .newInstance();
    23. } catch (Throwable t) {
    24. Log.i(TAG, "Failed to instantiate custom view inflater "
    25. + viewInflaterClassName + ". Falling back to default.", t);
    26. mAppCompatViewInflater = new AppCompatViewInflater();
    27. }
    28. }
    29. }
    30. boolean inheritContext = false;
    31. if (IS_PRE_LOLLIPOP) {
    32. inheritContext = (attrs instanceof XmlPullParser)
    33. // If we have a XmlPullParser, we can detect where we are in the layout
    34. ? ((XmlPullParser) attrs).getDepth() > 1
    35. // Otherwise we have to use the old heuristic
    36. : shouldInheritContext((ViewParent) parent);
    37. }
    38. //⭐
    39. return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
    40. IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
    41. true, /* Read read app:theme as a fallback at all times for legacy reasons */
    42. VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
    43. );
    44. }

    可以看到创建了一个mAppCompatViewInflater ,最后调用了mAppCompatViewInflater的createView方法:

    1. final View createView(View parent, final String name, @NonNull Context context,
    2. @NonNull AttributeSet attrs, boolean inheritContext,
    3. boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    4. final Context originalContext = context;
    5. // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
    6. // by using the parent's context
    7. if (inheritContext && parent != null) {
    8. context = parent.getContext();
    9. }
    10. if (readAndroidTheme || readAppTheme) {
    11. // We then apply the theme on the context, if specified
    12. context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
    13. }
    14. if (wrapContext) {
    15. context = TintContextWrapper.wrap(context);
    16. }
    17. View view = null;
    18. // We need to 'inject' our tint aware Views in place of the standard framework versions
    19. switch (name) {
    20. case "TextView":
    21. view = createTextView(context, attrs);
    22. verifyNotNull(view, name);
    23. break;
    24. case "ImageView":
    25. view = createImageView(context, attrs);
    26. verifyNotNull(view, name);
    27. break;
    28. case "Button":
    29. view = createButton(context, attrs);
    30. verifyNotNull(view, name);
    31. break;
    32. case "EditText":
    33. view = createEditText(context, attrs);
    34. verifyNotNull(view, name);
    35. break;
    36. case "Spinner":
    37. view = createSpinner(context, attrs);
    38. verifyNotNull(view, name);
    39. break;
    40. case "ImageButton":
    41. view = createImageButton(context, attrs);
    42. verifyNotNull(view, name);
    43. break;
    44. case "CheckBox":
    45. view = createCheckBox(context, attrs);
    46. verifyNotNull(view, name);
    47. break;
    48. case "RadioButton":
    49. view = createRadioButton(context, attrs);
    50. verifyNotNull(view, name);
    51. break;
    52. case "CheckedTextView":
    53. view = createCheckedTextView(context, attrs);
    54. verifyNotNull(view, name);
    55. break;
    56. case "AutoCompleteTextView":
    57. view = createAutoCompleteTextView(context, attrs);
    58. verifyNotNull(view, name);
    59. break;
    60. case "MultiAutoCompleteTextView":
    61. view = createMultiAutoCompleteTextView(context, attrs);
    62. verifyNotNull(view, name);
    63. break;
    64. case "RatingBar":
    65. view = createRatingBar(context, attrs);
    66. verifyNotNull(view, name);
    67. break;
    68. case "SeekBar":
    69. view = createSeekBar(context, attrs);
    70. verifyNotNull(view, name);
    71. break;
    72. case "ToggleButton":
    73. view = createToggleButton(context, attrs);
    74. verifyNotNull(view, name);
    75. break;
    76. default:
    77. // The fallback that allows extending class to take over view inflation
    78. // for other tags. Note that we don't check that the result is not-null.
    79. // That allows the custom inflater path to fall back on the default one
    80. // later in this method.
    81. view = createView(context, name, attrs);
    82. }
    83. if (view == null && originalContext != context) {
    84. // If the original context does not equal our themed context, then we need to manually
    85. // inflate it using the name so that android:theme takes effect.
    86. view = createViewFromTag(context, name, attrs);
    87. }
    88. if (view != null) {
    89. // If we have created a view, check its android:onClick
    90. checkOnClickListener(view, attrs);
    91. }
    92. return view;
    93. }

    可以看到有一个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);
    }
    1. private View createViewFromTag(Context context, String name, AttributeSet attrs) {
    2. if (name.equals("view")) {
    3. name = attrs.getAttributeValue(null, "class");
    4. }
    5. try {
    6. mConstructorArgs[0] = context;
    7. mConstructorArgs[1] = attrs;
    8. if (-1 == name.indexOf('.')) {
    9. for (int i = 0; i < sClassPrefixList.length; i++) {
    10. final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
    11. if (view != null) {
    12. return view;
    13. }
    14. }
    15. return null;
    16. } else {
    17. return createViewByPrefix(context, name, null);
    18. }
    19. } catch (Exception e) {
    20. // We do not want to catch these, lets return null and let the actual LayoutInflater
    21. // try
    22. return null;
    23. } finally {
    24. // Don't retain references on context.
    25. mConstructorArgs[0] = null;
    26. mConstructorArgs[1] = null;
    27. }
    28. }
    29. private View createViewByPrefix(Context context, String name, String prefix)
    30. throws ClassNotFoundException, InflateException {
    31. Constructorextends View> constructor = sConstructorMap.get(name);
    32. try {
    33. if (constructor == null) {
    34. // Class not found in the cache, see if it's real, and try to add it
    35. Classextends View> clazz = Class.forName(
    36. prefix != null ? (prefix + name) : name,
    37. false,
    38. context.getClassLoader()).asSubclass(View.class);
    39. constructor = clazz.getConstructor(sConstructorSignature);
    40. sConstructorMap.put(name, constructor);
    41. }
    42. constructor.setAccessible(true);
    43. return constructor.newInstance(mConstructorArgs);
    44. } catch (Exception e) {
    45. // We do not want to catch these, lets return null and let the actual LayoutInflater
    46. // try
    47. return null;
    48. }
    49. }

    这个方法和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的重要流程。

  • 相关阅读:
    Mybatis源码的理解
    【机器学习】大模型环境下的应用:计算机视觉的探索与实践
    VBA 剪切板
    SSM+广西壮族文化宣传网站 毕业设计-附源码230932
    linux下keepalived的安装和配置
    gvim小记,包含了获取行号的函数【个人笔记,不喜勿喷】
    Redis主从复制
    分布式数据库Apache Doris HA集群部署
    一个反向代理神器 ——Nginx Proxy Manager
    40 - 前置操作符和后置操作符
  • 原文地址:https://blog.csdn.net/m0_37707561/article/details/125890487