AppCompatActivity下的setContentView流程以及LayoutInflater

534 阅读6分钟
  1. 在 AppCompatActivity 的 onCreate方法中,就指定了LayoutInflater的Factory2
  2. AppCompatDelegateImpl 就是实现Factory的类
  3. setContentView是 就是通过 AppCompatDelegateImpl 的setContentView
  4. 此时根据不同的主题初始化mSubDecor(相当于包裹着我们xml的View),并把这个添加到Window上,只是添加到window中的DecorView上
  5. 然后通过I/O 来读取xml,然后 LayoutInflater 通过xml解析器 循环并且递归遍历每一个View,创建View
  6. 因为继承AppCompatActivity ,此时会把一些 TextView节点,ImageView节点呀,Button 节点呀,都会通过 new 的方式创建一个 AppCompatTextView,来兼容版本的
  7. 其他的比如 ViewGroup,LearLayout呀,自定义View呀 都是通过反射来创建的

setContentView

  1. AppCompatActivity 的setContentView(int resId)
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    // 设置 LayoutInflater 的 Factory2
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}
  1. AppCompatDelegateImpl.setContentView(int resId)
// 对应 AppCompatActivity 在onCreate中 设置Factory2方法
@Override
public void installViewFactory() {
	// 得到 LayoutInflater
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
    	// 设置Factory2,为下面创建View 做铺垫
        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");
        }
    }
}
@Override
public void setContentView(int resId) {
    ensureSubDecor();// 根据不同的主题创建不同的mSubDecor(这个mSubDecor在DecorView下面,我们自定义View上面)
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    // 先移除content中所有的View,然后通过下面代码把contentParent传进去,才开始真正的解析xml,生成View
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    mAppCompatWindowCallback.getWrapped().onContentChanged();
 }

 private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
        //  根据不同的主题创建mSubDecor,同时会调用 window.setContentView(mSubDecor)
            mSubDecor = createSubDecor();

	}
}

// 创建 subDecor , 同时会调用 window.setContentView(mSubDecor),也就是添加的 DecorView上
private ViewGroup createSubDecor() {


      final LayoutInflater inflater = LayoutInflater.from(mContext);
      ViewGroup subDecor = null;

      // 根据不同的主题创建 subDecor
      if (!mWindowNoTitle) {
          if (mIsFloating) {
              // If we're floating, inflate the dialog title decor
              subDecor = (ViewGroup) inflater.inflate(
                      R.layout.abc_dialog_title_material, null);

              // Floating windows can never have an action bar, reset the flags
              mHasActionBar = mOverlayActionBar = false;
          } else if (mHasActionBar) {
              /**
               * This needs some explanation. As we can not use the android:theme attribute
               * pre-L, we emulate it by manually creating a LayoutInflater using a
               * ContextThemeWrapper pointing to actionBarTheme.
               */
              TypedValue outValue = new TypedValue();
              mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

              Context themedContext;
              if (outValue.resourceId != 0) {
                  themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
              } else {
                  themedContext = mContext;
              }

              // Now inflate the view using the themed context and set it as the content view
              subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                      .inflate(R.layout.abc_screen_toolbar, null);

              mDecorContentParent = (DecorContentParent) subDecor
                      .findViewById(R.id.decor_content_parent);
              mDecorContentParent.setWindowCallback(getWindowCallback());

              /**
               * Propagate features to DecorContentParent
               */
              if (mOverlayActionBar) {
                  mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
              }
              if (mFeatureProgress) {
                  mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
              }
              if (mFeatureIndeterminateProgress) {
                  mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
              }
          }
      } else {

      }





      // Now set the Window's content view with the decor
      // 添加给PhoneWindow,也就是添加的 DecorView上
      mWindow.setContentView(subDecor);



      return subDecor;
  }
     

然后我们看一下 window的唯一实现类 PhoneWindow的 setContentView

 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) {
        // 初始化 DecorView,并且根据@android:id/content 找到 mContentParent
            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 {
        	// 把  AppCompatDelegateImpl 的 mSubDecor 添加到 mContentParent 这个里面
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

LayoutInflater.from

也就是读取xml,利用xml解析器,解析每个节点,并通过构造方法或者反射创建

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            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();

				// 是否是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);
                } 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他的子Children
                    rInflateChildren(parser, temp, attrs, true);

                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }

                }

            } 
            return result;
        }
    }

// inflate 子View
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
    rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}    

//
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;
		// 遍历子View , 只遍历子,不遍历孙子,当递归走到子的时候,又会遍历
        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)) {
            //include 标签
                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);
            //merge 标签
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
            	// 创建 通过new 或者 反射的形式创建View
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                // 递归inflate子View
                rInflateChildren(parser, view, attrs, true);
                // 添加到父View中
                viewGroup.addView(view, params);
            }
        }

      
    }
 
 // 创建View
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    try {
    	// 去尝试创建View
        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;
    } 
}


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

	// 如果界面Activity继承 AppCompatActivity ,mFactory2 是有值的。然而就是 AppCompatDelegateImpl 
    View view;
    if (mFactory2 != null) {
    	// 所以这里走到 AppCompatDelegateImpl 的  onCreateView 中
        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;
}


继续看 AppCompatDelegateImpl 的 createView 方法

@Override
public View createView(View parent, final String name, @NonNull Context context,
      @NonNull AttributeSet attrs) {

     // 最后是通过这种方法创建的View,继续跟进去 
  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 */
  );
}

看 AppCompatViewInflater 的 createView,如果是 TextView,就通过new的方式创建AppCompatTextView,如果是ImageView 就通过 new的方式创建AppCompatImageView,只是做一下转化兼容老版本,如果不是这样的View,这里返回null,继续走 LayoutInflater.createViewFromTag 通过反射去创建

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