0%

LayoutInflater 源码分析

0. 前言

LayoutInflater(布局填充器) 在安卓开发中,可以说是扮演着相当重要的角色,它让我们的 ListViewRecyclerView 等很容易变得多姿多彩,也正是它如此容易的操作,让它不由地多出了一份神秘…这篇博文将基于 Android 6.0LayoutInflater 的源码进行一定分析。

1. 获取实例

1
2
3
protected LayoutInflater(Context context) {
mContext = context;
}

保护权限的构造方法使得获取 LayoutInflater 并不能直接使用 new 关键字,需要使用静态方法 from(context) 来从 SystemService 取得:

1
2
3
4
5
6
7
8
9
10
11
/**
* 从给定的Context获取实例
*/
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(...) 的最终实现在 ContextImpl 调用的 SystemServiceRegistry 类中:

1
2
3
4
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}

也就是从 SYSTEM_SERVICE_FETCHERS 这个常量 HashMap 中获取,那么这些系统服务是什么时候 put 进去的呢?答案就在类开头的静态代码块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 静态代码块 第一次访问此类(SystemServiceRegistry)时执行
static {
// 注册辅助功能服务
registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
new CachedServiceFetcher<AccessibilityManager>() {
@Override
public AccessibilityManager createService(ContextImpl ctx) {
return AccessibilityManager.getInstance(ctx);
}});
...
// 注册布局填充器服务
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}
});
...
}

// 注册的时候放入SYSTEM_SERVICE_FETCHERS
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}

既然放进去了,那么如何取出呢?

1
return fetcher != null ? fetcher.getService(ctx) : null;

取出服务是通过 Fetcher 取出的, Fetcher 又是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ServiceFetcher是一个抽象接口
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}

// CachedServiceFetcher 抽象类为其实现
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
...
@Override
public final T getService(ContextImpl ctx) {
// 取得缓存区
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// 从缓存区中获取
Object service = cache[mCacheIndex];
if (service == null) {
// 如果缓存区中没有就调用createService(context)
service = createService(ctx);
// 并且放入缓存区
cache[mCacheIndex] = service;
}
return (T)service;
}
}

// 创建实例抽象方法
public abstract T createService(ContextImpl ctx);

好了,这下终于找到 LayoutInflater 的实例化入口了,就在 CachedServiceFetcher<T> 接口的实现中,也就是 registerService(...) 的第三个参数:

1
2
3
4
5
6
7
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
// 创建实例
return new PhoneLayoutInflater(ctx.getOuterContext());
}
}

这里 new 出了 PhoneLayoutInflater 实例,原来 PhoneLayoutInflater 才是我们使用到 LayoutInflater :

1
2
3
4
// 继承自LayoutInflater
public class PhoneLayoutInflater extends LayoutInflater {
...
}

2. 填充视图

inflate(...) 有多个方法重载,此处以参数较为全面的,也是常用的一个进行分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot){
// 获取Resources
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
// 传入一个Layout 返回该xml的解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
// 接着调用含有XmlPullParser参数的重载方法
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

接下来就来到那个含有XmlPullParser参数的重载方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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;
// 参数root将作为返回初值 防止填充错误返回null
View result = root;
try {
// 查找根节点
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// 跳出循环这意味到达根节点
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();

if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
// 如开头果是<merge />标签
if (TAG_MERGE.equals(name)) {
// 那么必须要展示在root布局中
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 遍历子view
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 填充根ViewGroup
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 取得当前父容器的LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 如果attachToRoot为false 则应用父容器的LayoutParams
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// 遍历子view
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// 如果attachToRoot为false 则调用父容器的addView方法
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 其它情况直接返回填充好的view
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (Exception e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// 保存最后一次的Context 以便它用
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
// 事务结束
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}

思路就是解析 xml ,对参数进行合理的应用,然后遍历子 view ,从而带领出两条路: 一个是填充根 view group ,一个是遍历子 view ,分别是以下两个后面将进行分析的方法:

1
2
3
4
// 根布局
createViewFromTag(root, name, inflaterContext, attrs);
// 子布局
rInflate(parser, root, inflaterContext, attrs, false);

还有一个重点是,这段代码让我们更加理解了 `inflate(...)` 后面两个参数 `ViewGroup root` 和 `boolean attachToRoot` 的联系:
  • root 指的是需要将填充好的view所放在的父容器
  • attachToRoot 指的是是否链接到root父容器,如果为true,则调用父容器的addView()方法,否则应用父容器的LayoutParams

另外这里补充几个标签的知识:

<merge/><include/><View Stub/> 标签的使用与区别

  • <merge/> 将视图组合,减少UI层次,但只能用于根节点,比如include一个layout,这个layout中就可以用merge作为根节点,防止include后造成多层嵌套
  • <include/> 包含一个layout布局,减少代码重复
  • <View Stub/> 用于需要时才加载的布局,比如错误信息的展示

2.1 填充根布局

填充根布局调用 createViewFromTag(...) 来获取 View

1
2
3
4
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
// 调用下面的重载方法
return createViewFromTag(parent, name, context, attrs, false);
}

其实调用的是含包访问权限的其重载方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// 参数parent 需要显示在之上的父容器
// 参数name 解析xml中跟节点名字
// 参数attrs 主题属性
// 参数ignoreThemeAttr 是否忽略主题样式
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// 如果标签是view
if (name.equals("view")) {
// 则获取null命名空间中class属性的值
name = attrs.getAttributeValue(null, "class");
}
// 如果不忽略主题样式
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
// 使用带有主题样式的ContextThemeWrapper来替换原有的context
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
// 如果标签是blink
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
// 1955是什么典故么?
// 返回 内部继承自FrameLayout的BlinkLayout
return new BlinkLayout(context, attrs);
}
try {
// 使用非空的Factory调用onCreateView(...)方法
View view;
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);
}
if (view == null) {
// 如果Factory都无法正常工作
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
// 则直接调用内部onCreateView(...)方法
try {
// 判断是否包含'.'
// contains()方法内部也是通过调用indexOf()
if (-1 == name.indexOf('.')) {
// 包含表明指定了完整类名
// 比如 android.support.v7.widget.CardView
view = onCreateView(parent, name, attrs);
} else {
// 否则传入null使用默认的前缀
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);
ie.initCause(e);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}

经过一番折腾,原来真正的创建 ViewcreateView(name, null, attrs) 方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 依旧从缓存区中获取构造器
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
try {
// 开始事务
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// 如果构造器为获取到 就使用反射获取 并强制转换为View的class
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 允许客户端对其进行过滤 比如RemoteViews
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
// 获取构造器并放入缓存
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// 过滤
if (mFilter != null) {
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
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[] args = mConstructorArgs;
args[1] = attrs;
// 使用构造器创建对象
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// 如果是ViewStub 就为其设置LayoutInflater 以便后续inflate
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} catch (NoSuchMethodException e) {
...
}

至此,根布局的填充算是完成了。

2.2 遍历子布局

接下来便是遍历子布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 深度优先遍历
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
// 获取深度
final int depth = parser.getDepth();
int type;
// 只要到达结束标签 就一直循环
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} 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 {
// 如果是ViewGroup 调用填充父容器用到的方法
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中
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}

遍历子布局就是一个不断递归的过程,递归完毕parent就被充满了内容,这时返回到 inflate() 方法中,各种绚丽花哨的效果就被填充完毕,就可以随时进行展示了。

总结

LayoutInflater 是一个神奇有力的工具,将它用好,相信你的App一定会更加绚丽多姿,通过此文,希望你对 LayoutInflater 有更近一步的了解,对安卓源码的精妙设计也更感兴趣!