一、了解LayoutInflater
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
//省略异常捕捉的代码..
return LayoutInflater;
}
由此可知LayoutInflater的实例是通过getSystemService(Context.LAYOUT_INFLATER_SERVICE)获取到的, 接下来看看是如何获取到的
/**
* ContextImpl.getSystemService
*/
@Override
public Object getSystemService(String name) {
// 1. 调用了 SystemServiceRegistry 的静态方法, invokestatic 指令会触发类的初始化
// 即会先初始化这个 SystemServiceRegistry 类中的静态代码块
return SystemServiceRegistry.getSystemService(this, name);
}
/**
* 2. 在静态代码块中注册服务
* SystemServiceRegistry.static{...}
*/
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}
});
/**
* SystemServiceRegistry.registerService
*/
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
// 3. 将静态代码块中注册的系统服务 put 到集合中缓存, 保证进程间的唯一性
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
/**
* SystemServiceRegistry.getSystemService
*/
public static Object getSystemService(ContextImpl ctx, String name) {
// 4. 从缓存中取
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
上面的源码分析的很透彻, Andriod 7.0 中的源码, 是通过 SystemServiceRegistry 完成了相应的操作
二、View构建的流程
/**
* View.inflate
*/
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
/**
* LayoutInflater.inflate
*/
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
/**
* LayoutInflater.inflate
* <p>
* @param resource:为我们定义的xml布局(R.layout.xxx)
* @param root:可用于存放我们xml布局的父容器
* @param attachToRoot:该boolean变量为是否将我们xml布局加入root中
*/
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
/**
* LayoutInflater.inflate
* <p>
* @param parser: 为xml解析器
* @param root: 可用于存放我们xml布局的父容器
* @param attachToRoot: 该boolean变量为是否将我们xml布局加入root中
*/
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot);
分析了源码之后可以得出如下的等价关系:
-
View.inflate(context, R.layout.xxx, viewGroup/null) ->
LayoutInflater.from(context).inflate(R.layout.xxx, viewGroup/null) -
LayoutInflater.from(context).inflate(R.layout.xxx, viewGroup/null) ->
LayoutInflater.from(context).inflate(R.layout.xxx, viewGroup/null, true/false)
由上述的源码可知: 无论我们调用哪一种inflate方法,最终都会交由
inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) 来执行
接下来就来分析一下这个 inflate 方法做了哪些事情
/**
* 从指定的 XML 节点填充一个新的 View 层级
*
* @param parser XML dom node containing the description of the view
* hierarchy.
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* if (attachToRoot == true) root 则作为解析 xml 所生成 View 的父类
* if (attachToRoot == false) root 则会为解析 xml 所生成的 View 生成一个 LayoutParams
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
// 这个 mConstructorArgs 为创建 View 实例的构造函数的参数数组
// 记录之前 mConstructorArgs[0] 中保存的 Context
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
// 给 result 赋初值
View result = root;
try {
// while 循环查找 xml 中第一个有效的节点标签
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// 什么也没做...
}
// 解析到最后 type 仍然是 START_TAG 说明当前 XML 没有起始节点则扔出异常
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
// 判断解析到的根节点是否为 Merge 标签
if (TAG_MERGE.equals(name)) {
// 若为 merge 标签则必须保证传入的 root 不为空, 且 attachToRoot 为 true
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid ViewGroup root and attachToRoot=true");
}
// 1. 若 xml 跟 View 为 <Merge> 标签并且通过了上面的验证, 则调用 rInflate 方法
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 2. 若 xml 不为根节点的情况, 调用 createViewFromTag 来找到并创建根节点的 View
// Temp 是 xml 根节点的 View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 从 xml 文件解析的属性集合中获取 temp 的 LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 若不需要添加到 root 中则为其绑定 LayoutParams
temp.setLayoutParams(params);
}
}
// 以 temp 为父 ViewGroup 遍历 temp 的子孩子, 添加到对应的父容器中
rInflateChildren(parser, temp, attrs, true);
// 判断是否需要将解析出来的 temp 添加到 root 中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 若满足以下条件, 则直接返回从 xml 解析出来的 temp(View)
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
} catch (Exception e) {
} finally {
// 将 mConstructorArgs[0] 还原成之前的 lastContext, 防止内存泄漏
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
return result;
}
}
/**
* LayoutInflater.rInflate
*/
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
// 获取当前 parser 所处的深度
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
// 忽略 TAG
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)) {
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);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
// 1.1 可以看到同样是通过 createViewFromTag 来创建View, 可见它非常重要
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 1.2 这里同样也调用了 rInflateChildren 方法来解析 view 的子孩子
rInflateChildren(parser, view, attrs, true);
// 添加到 parent 中去
viewGroup.addView(view, params);
}
}
// 等待获取焦点
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
// 是否结束 inflate 流程
if (finishInflate) {
parent.onFinishInflate();
}
}
/**
* LayoutInflater.rInflateChildren
*/
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
// 1.2.1 递归调用 rInflate 方法, 这样就将解析到的 View 一层一层的都添加到各自的 parent 中去了
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
好了, 是时候总结一下了, 代码量还是有些多的, 不过思路很清晰, 不得不叹服 Google 的技巧, inflate 方法主要做了如下事情:
- 解析 XML 文件, 找到第一个有效的起始节点
- 起始节点为 <Merge> 标签
- 验证是否满足 (root != null && attachToRoot) 的条件, 不满足则抛出异常, 这也说明了 Merge 不能作为根 View 单独存在
- 调用了 rInflate 方法去创建 Xml 中的 View 的实例对象, 且添加到各自对应的 parent 中
- 调用了 createViewFromTag 创建View
- 调用 rInflateChildren 进行递归
- 起始节点不为 <Merge> 标签
- 直接通过 createViewFromTag 来创建 View 实例对象 temp
- 接着调用了 rInflateChildren, 以 temp 为父 ViewGroup 遍历 temp 的子孩子, 添加到对应的父容器中
通过 inflate 方法后, View 的树形结构就成功的构建起来了
好! 重点来了, View是怎么创建的, 我们继续追溯 createViewFromTag 这个方法
/**
* LayoutInflater.createViewFromTag
*/
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// ... 省略部分代码
try {
View view;
// 1. 判断用户是否给LayoutInflater 添加了 Factory/Factory2
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// 2. 调用内部的私有工厂去创建 View 实例
// 这个 mPrivateFactory(Factory2) 在 LayoutInflater 的 Constructor 中被创建
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
// 3. 调用 LayoutInflater 内部自己定义的方法去执行 View 的创建
if (view == null) {
// 记录之前的 Context
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 判断解析到的节点名称中是否带有 ".", 没有 "." 则说明是系统控件
if (-1 == name.indexOf('.')) {
// 3.1 调用 onCreateView 来创建系统 View
view = onCreateView(parent, name, attrs);
} else {
// 3.2 调用 createView 来创建自定义 View
view = createView(name, null, attrs);
}
} finally {
// 还原为之前的 Context
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
} catch (ClassNotFoundException e) {
} catch (Exception e) {
}
}
/**
* LayoutInflater.onCreateView
*/
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
// 可见这个传入的 parent 完全无用武之地, 可能是 Google 故意在这里留了一层, 提升拓展的可能性
return onCreateView(name, attrs);
}
/**
* LayoutInflater.onCreateView
*/
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
// 这个比较有意思, 它给系统控件拼接了一个前缀, 最终还是回调了 createView 来创建View
return createView(name, "android.view.", attrs);
}
createViewFromTag 这个方法还是比较有意思的, 它做了如下的事情:
- 优先使用 Factory2 创建 View
- 次优先使用 Factory 创建 View
- 次次优先使用 LayoutInflater.mPrivateFactory 对象来创建 View
- 如果上述方式都无法创建 View
- LayoutInflater.onCreateView() 创建系统View, 最终还是调用 create 方法
- LayoutInflater.createView() 创建自定义View
好了, 终于快走到终点了, 最后来看看 createView 方法
/**
* LayoutInflater.createView
*/
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 1. 通过名字从缓存中获取View的构造函数
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) {
// 2. 若缓存中没有 View 的 Constructor, 尝试通过 ClassLoader 去加载这个 View 的 Class
// prefix+ name 拼接 Class 的全限定名
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 3. 通过 Class 获取构造函数, 加入缓存
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
}
// 记录之前的 Context
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// 构造方法的第 0 个参数为 Context
mConstructorArgs[0] = mContext;
}
// 构造方法的第 1 个参数为 AttributeSet
Object[] args = mConstructorArgs;
args[1] = attrs;
// 4. 终于看到了这个方法反射构造对象
final View view = constructor.newInstance(args);
// 5. 若为 ViewStub 对象则懒加载
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
// 还原成之前 Context
mConstructorArgs[0] = lastContext;
// 返回回去
return view;
} catch (NoSuchMethodException e) {
} catch (ClassCastException e) {
} catch (ClassNotFoundException e) {
} catch (Exception e) {
} finally {
}
}
好了分析到这儿 View 创建的流程就比较明了了, 最终无非是反射创建对象, 从代码中也可以看到很多细节和值得学习的地方
网友评论