今天要学习源码的两个问题:
- Layout.xml布局是怎么加载解析的
- Layout.xml中的 view 标签又是怎么被转化成对象的
针对这两个问题引出源码学习的流程,带着问题去看源码。
总体流程:
问题 1 布局是怎么加载的 :
首先我们最熟悉的代码肯定是:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
那就让我们从 setContentView(R.layout.activity_main); 入手。进入方法发现:
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
进入到 AppCompatDelegateImplV9 这是个为委托类,不是今天的重点,setContentView(int resId) 中地一句就是 ensureSubDecor(); 这个方法,由名字可知,创建sub decor view对象的。进入该方法 继续~
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
// 创建 sub decor
mSubDecor = createSubDecor();
applyFixedSizeWindow();
...省略...
}
}
}
首先会进入 createSubDecor() 创建对象 继续 ~
private ViewGroup createSubDecor() {
..判断主题..省略...
// Now let's make sure that the Window has installed its decor by retrieving it
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
....省略..根据条件传教 subdecor.......
// 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());
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
...省略....
return subDecor;
}
首先是设置主题,然后 mWindow.getDecorView(); 确保decor view 已经创建,接下来根据不同的条件创建不同的 sub decor ,subdecor其实是一个 toolbar + framelayout 布局,content_parent 其实就是我们自己布局的父布局。 接下里就是 mWindow.setContentView(subDecor); 把subdecor传给window中的decorview。
先看 mWindow.getDecorView();
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 创建 decorView对象
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 创建 decorview 内部的 contentparent布局对象
mContentParent = generateLayout(mDecor);
...省略...
到这里首先是通过generateDecor(-1); 创建decorview的对象,然后在generateLayout(mDecor); 创建 内部的contentparent 对象。
然后decorView和contentparent对象创建完成之后,使用 mWindow.setContentView(subDecor); 把subdecor 对象addview放入contentparent中,代码
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)) {
} else {
mContentParent.addView(view, params);
}
...省略..
}
setContentView(其实就是把我们创建的subdecor 对象add进入 DecorVIew中的 contentparent中去。
然后代码又回到 setContentView(int resId)
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
这时候的mSubDecor对象已经被我们创建并且被放入window 的 decorview中去。然后把我们的布局 给 加入到 subdecor中。
问题1 我们就有答案了
接下来看问题 2 ,我们布局被加载到 系统的decorview中 后是怎么被实例化的...通过上面的代码我们能看到
LayoutInflater.from(mContext).inflate(resId, contentParent);
我们的布局被传进去了,接下来就跟踪 layoutinflater。
问题 2 加载了我们的布局xml之后, tag标签又是怎么被创建成对象的呢?
通过上述我们知道我们要跟踪 LayoutInflater 的 inflate() 方法。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 把 layoutRes 转换成 parser 解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
首先就是根据layout id 获取对应的parser解析器 res.getLayout(resource); 然后 inflate(parser, root, attachToRoot); 解析布局。
先看下 res.getLayout(resource); 最后会调到 loadXmlResourceParser() 中:
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
// 得到 TypedValue 实例
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
// 根据 layout id 把布局信息 赋值给 TypedValue 对象
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
// 根据 layout 信息 返回该布局的 parser 解析器
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
.....省略......
}
通过 impl.getValue(id, value, true); 根据 layout id 把layout 布局信息赋值给 typedvalue 对象,最终调到 native 方法中。
final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
synchronized (this) {
final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
........省略...........
}
最后调用下述方法把 parser 返回去
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
这里就是根据被赋值的 value 对象创建对应layout id 的 parser。
最后又回到了 LayoutInflater的 inflate() 方法中
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
........省略.............
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 {
// Temp is the root view that was found in the xml
// 根据 tag name 创建对应的 view 对象
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
......省略.....
return result;
}
}
通过上述代码我们知道 <merge /> 和 <include /> 是不能作为 root 根布局的。
然后就是 final View temp = createViewFromTag(root, name, inflaterContext, attrs); 创建对象
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
try {
View view;
if (mFactory2 != null) {
// 回调创建 view 对象
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
// 创建 view 对象,如果为空
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
..........省略......
}
然后就是通过
view = mFactory2.onCreateView(parent, name, context, attrs); 回调创建对象,这个回调其实 是 AppCompatDelegateImplV9 中的 onCreateView(parent, name, context, attrs) 方法
这个方法待会说,如果这个方法返回来的view 依然是 null 就会走到下面的判断
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
这个方法中会根据 当前 view 是否包含 “.” 来 通过 反射创建 view 对象。
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
........省略..........
} else {
// 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 = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
// 反射创建 view 对象
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]));
}
mConstructorArgs[0] = lastContext;
return view;
}
回过头 让我们继续分析 AppCompatDelegateImplV9 中的 onCreateView(parent, name, context, attrs) 方法。
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
// First let the Activity's Factory try and inflate the view
final View view = callActivityOnCreateView(parent, name, context, attrs);
if (view != null) {
return view;
}
// If the Factory didn't handle it, let our createView() method try
return createView(parent, name, context, attrs);
}
这个方法最后会走到 createView(parent, name, context, attrs);
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
........省略.......
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();
}
}
}
.........省略.....
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 */
);
}
这个方法有两个地方1是创建了 AppCompatViewInflater 对象,2 调用了 AppCompatViewInflater 对象的 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
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;
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);
}
return view;
}
根据这个方法可以看到,他会根据 name 判断当前是那个 view 然后 调用createxxx()方法创建 view,其中一个例子:
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
可以看到这里是 new 出来的,AppCOmpat XXX view 是谷歌做的兼容。
基本上整个流程就结束了,我们看到了 我们的view 创建的地方。