前言
不要慌拢共分9步,逻辑很垂直线性,递归逻辑不多
1. 从AppCompatActivity的setContentView作为入口
//AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
//Activity将setContentView委派给了代理AppCompatDelegate去执行
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
2. 进入AppCompatDelegate继续追查setContentView
//AppCompatDelegate.java
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, callback);
}
//AppCompatDelegate只是个虚类,实际由AppCompatDelegateImpl去实现setContentView
3. 进入AppCompatDelegateImpl后找到setContentView的具体实现
//AppCompatDelegateImpl.java
@Override
public void setContentView(int resId) {
ensureSubDecor();//将Activity attach到window上 并拿到父容器mSubDecor
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
4. 加载resId的艰巨任务交给了LayoutInflater
//LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
//XmlResourceParser登场 XMl的解析读取即将开始
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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
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;
int type;
//如果 parser.next() 既不是开始 也不是结束 那就一直next,直到找到符合要求的标签
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
//如果 parser.next()到最后没有找到开始标签,就证明这个xml是不合法的
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//经过上面的while会将parser的指针指向xml根节点的开始标签
final String name = parser.getName();//获取根节点标签名字
//遇到Merge标签,就去将merge指向的xml引进来
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 {
//这是主流程!!!
//第一步,通过createViewFromTag将xml根布局标签对应的根View创建出来
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
//第二步,根据xml里的配置生成LayoutParams,并设置给根View
if (root != null) {
// 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);
}
}
//第三步,将根View下属的所有子View从xml里创建出来。核心技术在这里
rInflateChildren(parser, temp, attrs, true);
//截至到这里xml里描述的所有view控件就已经被创建并载入内存
//如果外部传入的父容器不为空,就把根View添加进去
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//如果外部传入的父容器为空,那么根View直接作为结果返回
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
5. 接下来分析核心方法rInflateChildren,xml如何转化为View对象?
//LayoutInflater.java
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
//很显然rInflateChildren仅仅是对rInflate做了一个包装,上一步解析根View时,遇到merge标签就是直接调的rInflate,接下来看rInflate
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
//Xml构建的UI其实是一个树形结构,这个depth代表当前遍历到的标签所位于的高度,是随着遍历随时变化的一个值,根标签为0,以此类推到叶子节点,每一层增加1
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
//这个循环里的处理,都是从开始标签开始的,所以不是的话直接continue
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
//如果遇到<requestFocus>标签做出相应的设置,然后通过consumeChildElements忽略<requestFocus>下的所有子标签,也就是说,不管你<requestFocus>下有多少子tag一概不管。consumeChildElements是个static方法,可能别处也会用到,核心就是xml遍历时忽略指定标签下的所有子标签
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
//遇到<tag>标签,提取其k v值并通过parent.setTag(k, v)设置给parent
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
//<include />不能作为根标签
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
//遇到<include />标签提取其指向的布局xml,最后还是调用rInflateChildren去解析include的xml,这块有点递归的意思
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//这块很明显,<merge />只能是一个xml的根标签,不得作为子标签出现
throw new InflateException("<merge /> must be the root element");
} else {
//createViewFromTag根据此时遍历到的标签创建View,然后递归调用rInflateChildren继续创建该标签的子标签,递归结束后添加到父容器parent
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();
}
}
6. 根据上面的分析rInflateChildren就是遍历xml,递归调用rInflateChildren,通过createViewFromTag创建发现的每一个View,接下来分析createViewFromTag函数
//LayoutInflater.java
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// 设置主题theme
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();
}
//对于隐藏布局BlinkLayout的解析,这玩意有点彩蛋的意思,正经人谁用他,哈哈
//使用这个布局的页面每隔0.5秒是闪一次
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
//View创建的核心就是这几个Factory,待会扒一扒他
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);
}
//如果走到这里view还是空,说明是上面三个Factory都处理不了的View
//这是时候就要根据标签名字直接生成了
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//标签名不含包名,说明是系统控件,"android.view."+name,通过这样组装出来的路径,反射创建这个View,最终也是通过else里的createView实现的
view = onCreateView(parent, name, attrs);
} else {
//这里的标签是含有包名的全路径,一般是第三方或者自定义View,也是根据这个路径反射创建View
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
7. 上一步分析了createViewFromTag,可知主要是通过Factory和createView来最终创建View的,createView好理解根据类的全路径名,反射构造函数创建,那么这俩Factory干嘛的。
//LayoutInflater.java
//1.这两个Factory注入都是位于LayoutInflater.java
//2.这两个Factory只能注入一次
//3.如果你想注入第二次,可以在注入前反射修改mFactorySet为false,然后再注入
//4.从createViewFromTag的分析可知,Factory2优先级高于Factory1
public void setFactory(Factory 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 = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}
/**
* Like {@link #setFactory}, but allows you to set a {@link Factory2}
* interface.
*/
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);
}
}
//此时不得不提LayoutInflaterCompat.java里的setFactory
//1.这个是LayoutInflaterCompat是LayoutInflater的一个包装类
//2.这个方法传入一个普通的LayoutInflaterFactory
//3.当Build.VERSION.SDK_INT >= 21时将普通Factory直接包装为Factory2
//4.说明官方已经不建议或者废弃了Factory,转而使用Factory2
@Deprecated
public static void setFactory(
@NonNull LayoutInflater inflater, @NonNull LayoutInflaterFactory factory) {
if (Build.VERSION.SDK_INT >= 21) {
inflater.setFactory2(factory != null ? new Factory2Wrapper(factory) : null);
} else {
final LayoutInflater.Factory2 factory2 = factory != null
? new Factory2Wrapper(factory) : null;
inflater.setFactory2(factory2);
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, factory2);
}
}
}
8. 上一步得知Factory已经处于过期状态,接下来直接分析Factory2
那就先查找一下调用setFactory2的在哪里,调用的地方很多,但是惊喜的看到了,上面分析里第3步提到的AppCompatDelegateImpl
@Override
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");
}
}
}
public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
//Factory2就是一个接口 上面的installViewFactory里的LayoutInflaterCompat.setFactory2(layoutInflater, this)说明onCreateView的实现就在上面分析里第3步提到的AppCompatDelegateImpl里
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP,
true,
VectorEnabledTintResources.shouldBeUsed()
);
}
//目标锁定mAppCompatViewInflater也就是AppCompatViewInflater.java,所有谜题的答案就在这里了
9. 闲言碎语不要讲,直接进入AppCompatViewInflater的createView方法
- 大家直接看就能秒懂了,多余的话其实不用说
- 能一直追到这里的,都是好奇心爱好者,哈
- 这是Fatory2,不用看Factory1大概也是这意思
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;
}
10. 总结
- setContentView是所有Android开发者会的第一个函数,他是那么亲切,又陌生,亲切用的次数实在是太多了,陌生是因为我们从来没有好好关注过他
- Inflater的setFactory2函数可以从xml解析这个级别拦截所有View创建过程,功能无比强大
- 自定义Factory2是Android换肤功能三大技术流派之一的底层核心逻辑
- 从静态xml文件到手机屏幕上酷炫效果的飞跃,就是今天分析的主题