入口:
Activity.setContentView(@LayoutRes int layoutResID)做了什么?
每个Activity都要设置一个布局:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里调用了getWindow().setContentView(layoutResID);
,Window 类的实现类PhoneWindow。查看PhoneWindow的setContentView方法:
public void setContentView(int layoutResID) {
...
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
这里最终使用LayoutInflater加载布局。
LayoutInflater的使用只有一句话:
LayoutInflater.from(mContext).inflate(resId, contentParent);
可以分为两个部分:
-
LayoutInflater.from(mContext)
通过mContext获取LayoutInflater对象 -
inflate(resId, contentParent)
将布局resId转化层View,加入contentParent中
获取LayoutInflater对象
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
这里使用了getSystemService方法去获取LayoutInflater对象,所以进入ContextImpl类的getSystemService()方法:
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
这里获取的是SystemServiceRegistry中的service,去SystemServiceRegistry中查找:
static {
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
...
}
可以看到,在SystemServiceRegistry类被加载的时候,在static静态块中注册了LAYOUT_INFLATER_SERVICE
。这里直接返回了PhoneLayoutInflater类的对象,PhoneLayoutInflater是LayoutInflater的子类,在LayoutInflater的基础上重写了onCreateView方法,为系统自带的View添加前缀:
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
View view = createView(name, prefix, attrs);
...
}
return super.onCreateView(name, attrs);
}
所以,LayoutInflater.from(mContext)
最终返回一个LayoutInflater的子类PhoneLayoutInflater的对象。
加载布局文件
LayoutInflater加载布局依靠inflate方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 获取布局文件到解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
这里调用了inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
方法:
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;
// 找到START_TAG标签,即整个布局的开头
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
final String name = parser.getName();
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");
}
// 如果是布局开头是一个merge标签,那就直接递归去解析标签里面的view
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 这里先创建根布局的View,例如LinearLayout\RelativeLayout等
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 将parser中获取的布局属性转换层LayoutParams,然后放入根布局的view中
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// 递归解析根View下的所有布局,转换成View
rInflateChildren(parser, temp, attrs, true);
// attachToRoot在入参中设置为false,不会走下面的代码,
//如果attachToRoot为true的话,就会将解析得到的布局View加入传入的根View
if (root != null && attachToRoot) {
root.addView(temp, params);
}
}
return result;
}
}
inflate方法的主体流程就是这些,这里主要看以下几个方法:
- 创建根布局的View:
createViewFromTag(root, name, inflaterContext, attrs);
- 遍历创建子布局:
rInflateChildren(parser, temp, attrs, true);
- 最终创建View的方法:
view = createView(name, prefix, attrs);
创建根布局的View:
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) {
try {
View view;
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 如何标签的名字没有“.”,那么就是系统自带的View,如TextView等
// 使用onCreateView(parent, name, attrs)方法
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
// 如果是有“.”,说明是自定义的View,或者扩展库的View
// 使用createView(name, null, attrs);方法
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
这里的onCreateView(parent, name, attrs)
和createView(name, null, attrs)
的区别在于onCreateView使用了PhoneLayoutInflater重写的onCreateView方法,将系统View添加了前缀然后再使用createView方法进行创建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;
// 遍历整个View树
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
final String name = parser.getName();
// 获取通过标签获取View
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);
// 将获取的View插入父View中
viewGroup.addView(view, params);
}
}
这根布局下创建子布局的View通过在While循环下进行递归,一层一层的实现
最终创建View的方法
最终创建View的方法在createView中:
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 获取当前标签对于的view的构造器
Constructor<? extends View> constructor = sConstructorMap.get(name);
// 如果构造器无效,则从缓存中删除
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
if (constructor == null) {
// 如果构造器为空,通过类名去获取构造器,并加入缓存中
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 这里限定构造器的入参是:Context.class, AttributeSet.class两个类型的对象
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
// 通过反射创建View对象
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
//对ViewStub进行异步加载处理
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
}
从代码中可以看出,最终创建View是通过反射的方式创建,并且构造方法必须是两个入参的那种。
总结
- LayoutInflater的创建最终是一个PhoneLayoutInflater对象,只是通过SystemService在类加载的时候创建,并没有跨进程的操作。
- LayoutInflater在解析布局文件的时候,是通过XmlResourceParser完成的。布局文件是一个树形结构,xml的解析就是一层层的解析,所以View的创建也是一层层创建,创建的时候使用递归的形式完成。
- 在LayoutInflater中,View的创建是通过反射完成的,效率并不高;View反射创建的时候是通过调用View的两个入参的构造方法,所以在写自定义View的时候如果你的View要在布局文件中插入,就必须要重写View的两个入参的构造方法。
网友评论