PhoneLayoutInflater

1
2
3
4
5
6
7
8
9
10
11
12
13
package android.view;
public abstract class 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;
}
......
}

博客地址
很明显这是一个抽象类,那么肯定有一个具体的实现类。
通过上一篇的注册服务的时候的方法中可以看到,:

1
2
3
4
5
6
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});

最终真正注册的是PhoneLayoutInflater类。
这个类的完整代码就很少

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
package com.android.internal.policy;
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
public PhoneLayoutInflater(Context context) {
super(context);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
//主要就重写了这个方法
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
}

onCreateView方法中调用的是LayoutInflater类中的createView(String name, String prefix, AttributeSet attrs)方法。主要就是把"android.widget.", "android.webkit.", "android.app."三个参数依次传进去。
他的作用就是在传进来的view前面加上前缀,例如,把Button变成android.widget.Button,然后根据这个完整的路径来创建相应的View类。

这也就是这也就是我们在布局文件中用系统的控件不需要写完整的包名,而自定义控件和support包中的必须写完整的包名。因为系统的控件会在这里补全完整包名。

那么布局文件xml是怎么和LayoutInflater联系起来的呢?

那就从头开始说了。
布局文件使用的第一步就是在activity中调用setContentView(R.layout.activity_main)
看一下Activity的源码:

1
2
3
4
5
6
7
8
9
10
11
12
package android.app;
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback {
。。。
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
}

getWindow()获取的是Window类。

先大致看一下Activity的界面架构图:

UI架构图,DecorView打错了

每个Activity都包含一个Window,通常都是PhoneWindow。PhoneWindow将一个DecorView设置为整个窗口的根View。DecorView分为两部分,其中一部分就是我们设置的ContentView。PhoneWindow通过setContentView把布局设置给DecorView的ContentView。

看前面的代码,getWindow().setContentView(layoutResID);;
getWindow()获取的是一个Window,mWindow。

mWindow在Activity的attach()方法中初始化,这是Activity创建后执行的第一个方法,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window) {
attachBaseContext(context);
......
//初始化Window,就是PhoneWindow
mWindow = new PhoneWindow(this, window);
......
mWindow.getLayoutInflater().setPrivateFactory(this);
......
}

然后看PhoneWindow的setContentView方法:

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
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
public PhoneWindow(Context context) {
super(context);
//构造方法中通过单例获取LayoutInflater
mLayoutInflater = LayoutInflater.from(context);
}
......
public void setContentView(int layoutResID) {
//mContentParent就是PhoneWindow的根布局DecorView,当DecorView为空时先创建。不等于空就移除原来所有的View。
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//这里就用到了LayoutInflater来加载布局。
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
......
}

进入LayoutInflater.inflate

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
93
94
95
96
97
package android.view;
public abstract class LayoutInflater {
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
......
//第三个参数表示是否添加到父View上,
//PhoneWindow调用时传的就是DecorView,必定不为空,所以这个值为TRUE。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
......
//第一个参数是XML解析器,解析的是布局文件。
//第二个参数是要解析的布局的父布局,
//第三个参数是是否把解析的布局加到父布局上
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 {
// 寻找根节点 root
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
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)) {
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 {
// 不是merge标签就调用createViewFromTag直接解析布局
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
......
// 生成布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//如果不绑定到父视图的话,就将参数设置给temp
temp.setLayoutParams(params);
}
}
......
//解析布局的所有子View,里面也是调用rInflate方法。
rInflateChildren(parser, temp, attrs, true);
......
// 如果绑定到父视图的话就把temp添加到父视图上
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 如果不绑定到父视图的话就直接返回temp
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
......
}
return result;
}
}
}

这里用到了递归,采用深度优先遍历,所以如果布局层次过多的话效率就会很低。
分析一下过程:
(1)解析布局文件的根元素
(2)如果根元素是merge,就直接调用rInflate进行解析,rInflate会将merge标签下的所有子View直接添加到跟标签中。
(3)如果是普通标签,就调用createViewFromTag对元素进行解析。
(4)然后调用rInflate方法递归根元素的所有子元素,生成View加载temp下.
(5)返回解析的根视图。

所以解析单个元素就是用下面这个createViewFromTag的方法了:

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
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
//如果是用其他的LayoutInflate创建的LayoutInflate,就用之前的那个onCreateView方法
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);
}
//全新的LayoutInflate
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//名字中不包含“.”,类似 <Button> 说明是系统控件,根据
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//说明是自定义控件 类似 <com.xxx.xxx.MyView>
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
}

最终还是调用createView方法

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
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// 找不到缓存的构造方法,如果传入的第二个参数prefix为null,就说明name是完整的包名,是自定义控件,直接反射加载自定义View。
//如果第二个参数不为 null,就由PhoneLayoutInflate加上前缀组成完整的类名。
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
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);
}
......
//生成View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
//如果是ViewStub就延迟加载
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} catch (NoSuchMethodException e) {
......
}
}

总结

LayoutInflater的工作过程就是,

  • 先获取系统的LayoutInflater服务,
  • 然后解析传入的布局文件xml,用深度优先遍历每一个节点,根据每个节点的名称生成对应的View并加载到父布局,
  • 整个树加载完成后就形成一个最终的View返回出来,完成布局解析。