在AppCompatActivity的onCreate方法中我们都知道setContentView这个方法是加载布局文件。这个方法使用很简单直接把layout布局文件放进去就可以了。那么具体内部是怎么将它显示到桌面的呢,今天就从setContentView()来解析一下Android中View的创建过程。
打开androidx/appcompat/app/AppCompatActivity.java,这里setContentView就一行代码,调用AppCompatDelegate的setContentView()。
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
AppCompatDelegate是一个抽象类,我们分析一下它的实现类AppCompatDelegateImpl在setContentView()方法中做了什么。
@Override
public void setContentView(int resId) {
ensureSubDecor();
//获取content视图
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
//移除viewGroup下的所有子View
contentParent.removeAllViews();
//解析布局文件添加到content视图中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
打开/frameworks/base/core/java/android/view/LayoutInflater.java查看解析布局文件的LayoutInflater.inflate() 方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
...
//获取布局解析器
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;
try {
advanceToRootNode(parser);
//取得XML标签名字
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
//如果是merge标签调用rInflate方法,递归执行解析
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 通过xml的tag来构建View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//获取布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//设置临时的布局参数
temp.setLayoutParams(params);
}
}
//递归执行解析,继续解析temp下的子项,也走到了rInflate()方法
rInflateChildren(parser, temp, attrs, true);
// 如果root不为空并且attachToRoot为true
if (root != null && attachToRoot) {
//将View填充到ViewGroup
root.addView(temp, params);
}
// 如果root为空 或者 attachToRoot为false,直接返回temp
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
...
}
return result;
}
}
在inflate方法中主要做了一下几步:
1.解析layout.xml文件的根标签
2.判断是否是merge,如果是merge那么调用rInflate()递归方法,实例化视图将View添加到ViewGroup中,然后调用onFinishInflate()。
3.如果不是merge,调用createViewFromTag()来创建view并添加到ViewGroup中,之后调用rInflate()继续递归解析子View;
4.通过attachToRoot,返回视图。
rInflate方法同样也调用createViewFromTag方法,打开createViewFromTag():
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
...
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//通过.来判断是自定义View还是内置View
if (-1 == name.indexOf('.')) {
//创建内置View
view = onCreateView(context, parent, name, attrs);
} else {
//创建自定义View
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} ...
}
通过继续跟踪代码,发现自定义View还是内置View,最后都调用了createView方法:
@Nullable
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Objects.requireNonNull(viewContext);
Objects.requireNonNull(name);
//从缓存中获取构造
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
if (constructor == null) {
//类载入器, 初始化对象
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
//从class中获取构造
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//加入缓存中
sConstructorMap.put(name, constructor);
} else {
...
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
//设置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]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
}
...
}
-
总结:
- 通过 pull解析XML文件获取到 View 的标签;
- 通过标签中是否有.判断是自定义View还是内置View
- 通过反射的方式来创建 View 对象;
- 通过深度优先的顺序遍历View, 形成View树;