遇见LayoutInflater&Factory

奥体公园

LayoutInflater的获取

在我们写listview的adapter的getView方法中我们都会通过 LayoutInflater.from(mContext) 获取LayoutInflater实例。
现在我们通过源码来分析一下LayoutInflater实例的获取:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//LayoutInflater的获取
public abstract class LayoutInflater {
/**
* Obtains the LayoutInflater from the given 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;
}
}

context.getSystemService 是Android很重要的一个API,它是Activity的一个方法,根据传入的NAME来取得对应的Object,然后转换成相应的服务对象。以下介绍系统相应的服务。

Name 返回的对象 说明
WINDOW_SERVICE WindowManager 管理打开的窗口程序
LAYOUT_INFLATER_SERVICE LayoutInflater 取得xml里定义的view
ACTIVITY_SERVICE ActivityManager 管理应用程序的系统状态
POWER_SERVICE PowerManger 电源的服务
ALARM_SERVICE AlarmManager 闹钟的服务
NOTIFICATION_SERVICE NotificationManager 状态栏的服务
KEYGUARD_SERVICE KeyguardManager 键盘锁的服务
LOCATION_SERVICE LocationManager 位置的服务,如GPS
SEARCH_SERVICE SearchManager 搜索的服务
VEBRATOR_SERVICE Vebrator 手机震动的服务
CONNECTIVITY_SERVICE Connectivity 网络连接的服务
WIFI_SERVICE WifiManager Wi-Fi服务
TELEPHONY_SERVICE TeleponyManager 电话服务

获取LayoutInflater服务

1
2
3
4
5
6
7
8
9
10
11
12
class ContextImpl extends Context {
/***部分代码省略****/
static {
/***部分代码省略****/
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
}});
/***部分代码省略****/
}
/***部分代码省略****/
}

从源码可以看出LayoutInflater实例是由 PolicyManager.makeNewLayoutInflater获取的,PolicyManager有没有感觉很熟悉。上一章 Activity中的Window的setContentView 中我们获取Activity中的Window的实例的时候就是通过PolicyManager获取的,我们进一步往下跟进。

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
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";

private static final IPolicy sPolicy;

static {
// Pull in the actual implementation of the policy at run-time
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
} catch (ClassNotFoundException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
} catch (InstantiationException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(
POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
}
}
/***部分代码省略****/

public static LayoutInflater makeNewLayoutInflater(Context context) {
//反射获取实例
return sPolicy.makeNewLayoutInflater(context);
}
}

public class Policy implements IPolicy {
/***部分代码省略****/
public LayoutInflater makeNewLayoutInflater(Context context) {
//LayoutInflater的最终实例
return new PhoneLayoutInflater(context);
}
/***部分代码省略****/
}

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
35
36
37
38
39
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 onCreateView to instantiate names that correspond to the
widgets known to the Widget factory. If we don't find a match,
call through to our super class.
*/
@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);
}
}

LayoutInflater最常使用的方法

在Android中LayoutInflater中最常使用的情况基本都是调用inflate方法用来构造View对象。

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
public abstract class LayoutInflater {
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
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();
}
}
/**
* @param parser xml数据结构
* @param root 一个可依附的rootview
* @param attachToRoot 是否将parser解析生产的View添加在root上
*/
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 {
// Look for the root node.寻找根节点
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();

if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//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");
}
//递归调用,添加root的孩子节点
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml。根据当前的attrs和xml创建一个xml根view
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);
}
// Create layout params that match root, if supplied。构造LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}

if (DEBUG) {
System.out.println("-----> start inflating children");
}

// Inflate all children under temp against its context.递归调用,添加temp的孩子节点
rInflateChildren(parser, temp, attrs, true);

if (DEBUG) {
System.out.println("-----> done inflating children");
}

// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//将xml解析出来的viewgroup添加在root的根下
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
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 {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

return result;
}
}
}

这四个重载的inflate方法最终都是通过inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)进行实现的。

LayoutInflater的使用中重点关注inflate方法的参数含义:

  • inflate(xmlId, null); 只创建temp的View,然后直接返回temp。
  • inflate(xmlId, parent); 创建temp的View,然后执行root.addView(temp, params);最后返回root。
  • inflate(xmlId, parent, false); 创建temp的View,然后执行temp.setLayoutParams(params);然后再返回temp。
  • inflate(xmlId, parent, true); 创建temp的View,然后执行root.addView(temp, params);最后返回root。
  • inflate(xmlId, null, false); 只创建temp的View,然后直接返回temp。
  • inflate(xmlId, null, true); 只创建temp的View,然后直接返回temp。

LayoutInflater解析视图xml

  • xml视图树解析

递归执行rInflate生产View并添加给父容器

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
public abstract class LayoutInflater {
/**
* 将parser解析器中包含的view结合属性标签attrs生产view添加在parent容器中
* @param parser xml解析器
* @param parent 父容器
* @param attrs 属性标签集合
* @param finishInflate 生产view之后是否执行父容器的onFinishInflate方法。
*/
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;

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)) { //requestFocus标签解析
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) { //tag标签解析
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) { //include标签解析
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) { //merge标签解析
throw new InflateException("<merge /> must be the root element");
} else {
//View标签解析
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
//View所在容器(ViewGroup)的属性解析
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//循环遍历xml的子节点
rInflateChildren(parser, view, attrs, true);
//将解析出的view和其对于的属性参数添加在父容器中
viewGroup.addView(view, params);
}
}

if (finishInflate) {
parent.onFinishInflate();
}
}
}
  • 单个View布局的解析

调用createViewFromTag,设置View的Theme属性。再调用CreateView方法创建view

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
public abstract class LayoutInflater {
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}

protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}

/**
* 将parser解析器中包含的view结合属性标签attrs生产view添加在parent容器中
* @param parent 父容器
* @param name view名称
* @param context 上下文环境
* @param attrs 属性标签集合
*/
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) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}

// Apply a theme wrapper, if allowed and one is specified.//应用theme
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;
/*************************start Factory*/
//使用LayoutInflater的Factory,对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);
}
/*************************end Factory*/

if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//创建Android原生的View(android.view包下面的view)
view = onCreateView(parent, name, attrs);
} else {
//创建自定义View或者依赖包中的View(xml中声明的是全路径)
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;
}
}

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);
//判断View的构造是否进行缓存
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
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);
}
}
//static final Class<?>[] mConstructorSignature = new Class[] {Context.class, AttributeSet.class};
constructor = clazz.getConstructor(mConstructorSignature);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
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;
constructor.setAccessible(true);
//读取View的构造函数,传入context、attrs作为参数。View(Context context, AttributeSet attrs);
final View view = constructor.newInstance(args);
//处理ViewStub标签
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;

} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;

} catch (ClassCastException e) {
// If loaded class is not a View subclass
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}

LayoutInflater创建View的总结

  • 在inflate方法中,通过Resource.getLayout(resource)生产XmlResourceParser对象;
  • 利用该对象实例生产XmlPullAttributes以便于xml标签中的属性。然后将这个两个对象传递到rInflate方法中,解析layout对应的xml文件;
  • 接着将(父容器、xml中View的名称、属性标签)传递给createViewFromTag方法创建对应的View;
  • 在createViewFromTag方法中执行LayoutInflater.Factory或者LayoutInflater的createView方法。
  • 在createView方法中我们已知View的类名和View的属性标签集合,通过Java反射执行View的构造方法创建View对象。这也就是为什么我们在自定义View的时候必须复写View的构造函数View(Context context, AttributeSet attrs);

LayoutInflater.Factory简介

LayoutInflater.Factory这个类在我们开发的过程中很少越到。但是我们在查看LayoutInflater解析View源码的过程中可以看到如果LayoutInflater中有mFactory这个实例那么可以通过mFactory创建View,同时也能修改入参AttributeSet属性值。

1
2
3
4
5
6
7
8
9
10
11
public abstract class LayoutInflater {
/***部分代码省略****/
public interface Factory {
public View onCreateView(String name, Context context, AttributeSet attrs);
}

public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
/***部分代码省略****/
}
  • LayoutInflater有两个工厂类,Factory和Factory2,区别只是在于Factory2可以传入父容器作为参数。
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 abstract class LayoutInflater {
/***部分代码省略****/
public void setFactory(Factory factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}

public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
/***部分代码省略****/
}

这两个方法的功能基本是一致的,setFactory2是在Android3.0之后以后引入的,所以我们要根据SDK的版本去选择调用上述方法。

在supportv4下边也有LayoutInflaterCompat可以做相同的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class LayoutInflaterCompat {
/***部分代码省略****/
static final LayoutInflaterCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
IMPL = new LayoutInflaterCompatImplV21();
} else if (version >= 11) {
IMPL = new LayoutInflaterCompatImplV11();
} else {
IMPL = new LayoutInflaterCompatImplBase();
}
}
private LayoutInflaterCompat() {
}
public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
IMPL.setFactory(inflater, factory);
}
}

LayoutInflater.Factory的使用

找到当前Activity中的id=R.id.text的TextView将其替换为Button,并修改BackgroundColor。

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
public class MainActivity extends Activity {
final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
if ("TextView".equals(name)) {
Log.e(TAG, "name = " + name);
int n = attrs.getAttributeCount();
//打印所有属性标签
for (int i = 0; i < n; i++) {
Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
}
for (int i = 0; i < n; i++) {
if (attrs.getAttributeName(i).equals("id")) {
String attributeValue = attrs.getAttributeValue(i);
String id = attributeValue.substring(1, attributeValue.length());
if (R.id.text == Integer.valueOf(id)) {
Button button = new Button(context, attrs);
button.setBackgroundColor(Color.RED);
return button;
}
}
}
}
return null;
}
});
setContentView(R.layout.activity_main);
}
}

Console输出:

1
2
3
4
5
MainActivity: name = TextView
MainActivity: id , @2131492944
MainActivity: layout_width , -2
MainActivity: layout_height , -2
MainActivity: text , Hello World!

是不是发现LayoutInflater的Factory功能很好很强大。

这里提一个问题,如果把上面代码中的MainActivity的父类修改为AppCompatActivity会怎么样呢?我们试着运行一下。

1
2
3
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.tzx.dexload/com.example.tzx.dexload.MainActivity}:
java.lang.IllegalStateException: A factory has already been set on this LayoutInflater

程序运行报错:A factory has already been set on this LayoutInflater。这个是在执行LayoutInflater的setFactory方法时抛出的异常。因为mFactorySet=true。。。。这个时候我们发现LayoutInflater的Factory已经被设置过了。具体是在哪里设置的呢?我们看看下边LayoutInflater.Factory在Android源码中的使用部分内容。

LayoutInflater.Factory在Android源码中的使用

在我们开发过程是很少使用到LayoutInflater.Factory,但是Android在supportv7中就使用,我们来学习一下。

在AppComPatActivity中的onCreate就进行了LayoutInflater.Factory的设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}

public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
}
  • 根据不通的sdk版本做适配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class AppCompatDelegate {
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return create(activity, activity.getWindow(), callback);
}
/***部分代码省略****/

//对于不同的版本做适配
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV7(context, window, callback);
}
}
}

LayoutInflaterFactory的实现类,以及触发LayoutInflater.setFactory的调用。

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
class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase
implements MenuBuilder.Callback, LayoutInflaterFactory {
/***部分代码省略****/
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//进行setFactory的设置
LayoutInflaterCompat.setFactory(layoutInflater, this);
} else {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
/***部分代码省略****/
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
final boolean isPre21 = Build.VERSION.SDK_INT < 21;

if (mAppCompatViewInflater == null) {
//具体的实现类,下文会有讲到
mAppCompatViewInflater = new AppCompatViewInflater();
}

// We only want the View to inherit it's context if we're running pre-v21
final boolean inheritContext = isPre21 && mSubDecorInstalled
&& shouldInheritContext((ViewParent) parent);

return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
true /* Read read app:theme as a fallback at all times for legacy reasons */
);
}
}
  • 根据不同的版本找到LayoutInflater的包装类
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
public final class LayoutInflaterCompat {

interface LayoutInflaterCompatImpl {
public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory);
public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater);
}

static class LayoutInflaterCompatImplBase implements LayoutInflaterCompatImpl {
@Override
public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory) {
LayoutInflaterCompatBase.setFactory(layoutInflater, factory);
}

@Override
public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater) {
return LayoutInflaterCompatBase.getFactory(layoutInflater);
}
}

static final LayoutInflaterCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
IMPL = new LayoutInflaterCompatImplV21();
} else if (version >= 11) {
IMPL = new LayoutInflaterCompatImplV11();
} else {
IMPL = new LayoutInflaterCompatImplBase();
}
}

/***部分代码省略****/

private LayoutInflaterCompat() {
}

public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
IMPL.setFactory(inflater, factory);
}

public static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
return IMPL.getFactory(inflater);
}
}
  • FactoryWrapper类通过调用LayoutInflaterFactory的onCreateView方法,实现了LayoutInflater.Factory接口。最终调用了LayoutInflater的setFactory方法,使得在LayoutInflater.createViewFromTag中创建View的时候通过Factory进行创建。
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
public interface LayoutInflaterFactory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

class LayoutInflaterCompatBase {

static class FactoryWrapper implements LayoutInflater.Factory {

final LayoutInflaterFactory mDelegateFactory;

FactoryWrapper(LayoutInflaterFactory delegateFactory) {
mDelegateFactory = delegateFactory;
}

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
//调用LayoutInflaterFactory实现类的onCreateView(null, name, context, attrs)方法
return mDelegateFactory.onCreateView(null, name, context, attrs);
}

public String toString() {
return getClass().getName() + "{" + mDelegateFactory + "}";
}
}

static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
//最终调用了LayoutInflater的setFactory方法,对Factory进行设置
inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
}

static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
LayoutInflater.Factory factory = inflater.getFactory();
if (factory instanceof FactoryWrapper) {
return ((FactoryWrapper) factory).mDelegateFactory;
}
return null;
}

}
  • 在LayoutInflaterFactory的实现类之一AppCompatDelegateImplV7中,找到了setFactory的实际使用意义实际意思。
    • 在LayoutInflater.createViewFromTag方法中调用Factory.onCreateView(name, context, attrs)方法
    • Factory的实现类FactoryWrapper中,调用LayoutInflaterFactory的onCreateView(null, name, context, 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
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
class AppCompatViewInflater {
/***部分代码省略****/
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;

// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}

View view = null;

// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
break;
case "Spinner":
view = new AppCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new AppCompatImageButton(context, attrs);
break;
case "CheckBox":
view = new AppCompatCheckBox(context, attrs);
break;
case "RadioButton":
view = new AppCompatRadioButton(context, attrs);
break;
case "CheckedTextView":
view = new AppCompatCheckedTextView(context, attrs);
break;
case "AutoCompleteTextView":
view = new AppCompatAutoCompleteTextView(context, attrs);
break;
case "MultiAutoCompleteTextView":
view = new AppCompatMultiAutoCompleteTextView(context, attrs);
break;
case "RatingBar":
view = new AppCompatRatingBar(context, attrs);
break;
case "SeekBar":
view = new AppCompatSeekBar(context, attrs);
break;
}

if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}

if (view != null) {
// If we have created a view, check it's android:onClick
checkOnClickListener(view, attrs);
}

return view;
}
}
  • AppCompatViewInflater作为LayoutInflaterFactory的的onCreateView方法的最终实现类,通过createView方法替换了一些我们想要自己替换的View。比如:
原始View 实际创建的View
TextView AppCompatTextView
ImageView AppCompatImageView
Button AppCompatButton
…… ……

在appcompat使用自定义的LayoutInflater.Factory

这里我们有两种书写方式:

  • 继续使用 LayoutInflater.from(this).setFactory 方法。
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
LayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
AppCompatDelegate delegate = getDelegate();
//调用AppCompatDelegate的createView方法将第一个参数设置为null
View view = delegate.createView(null, name, context, attrs);
if ("TextView".equals(name)) {
Log.e(TAG, "name = " + name);
int n = attrs.getAttributeCount();
for (int i = 0; i < n; i++) {
Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
}
for (int i = 0; i < n; i++) {
if (attrs.getAttributeName(i).equals("id")) {
String attributeValue = attrs.getAttributeValue(i);
String id = attributeValue.substring(1, attributeValue.length());
if (R.id.text == Integer.valueOf(id)) {
Button button = new Button(context, attrs);
button.setBackgroundColor(Color.RED);
button.setAllCaps(false);
return button;
}

}
}
}
return view;
}
});
  • 使用LayoutInflaterCompat.setFactory方法
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
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//appcompat 创建view代码
AppCompatDelegate delegate = getDelegate();
View view = delegate.createView(parent, name, context, attrs);
if ("TextView".equals(name)) {
Log.e(TAG, "name = " + name);
int n = attrs.getAttributeCount();
for (int i = 0; i < n; i++) {
Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
}
for (int i = 0; i < n; i++) {
if (attrs.getAttributeName(i).equals("id")) {
String attributeValue = attrs.getAttributeValue(i);
String id = attributeValue.substring(1, attributeValue.length());
if (R.id.text == Integer.valueOf(id)) {
Button button = new Button(context, attrs);
button.setBackgroundColor(Color.RED);
button.setAllCaps(false);
return button;
}

}
}
}
return view;
}
});

两种写法的原理是相同的,因为上面讲述的LayoutInflater.Factory的实现类FactoryWrapper实现onCreateView方法的时候调用的AppCompatDelegate.onCreateView的时候第一个参数传递的值就是null。

Android应用中的换肤(夜间模式)是不是也利用的是LayoutInflater.Factory原理实现的呢,我们一起期待下一篇关于Android换肤文章。

参考文章:
Android 探究 LayoutInflater setFactory

文章到这里就全部讲述完啦,若有其他需要交流的可以留言哦~!~!

想阅读作者的更多文章,可以查看我 个人博客 和公共号:

振兴书城
文章目录
  1. 1. LayoutInflater的获取
  2. 2. 获取LayoutInflater服务
  3. 3. PhoneLayoutInflater的实现
  4. 4. LayoutInflater最常使用的方法
  5. 5. LayoutInflater解析视图xml
  6. 6. LayoutInflater创建View的总结
  7. 7. LayoutInflater.Factory简介
  8. 8. LayoutInflater.Factory的使用
  9. 9. LayoutInflater.Factory在Android源码中的使用
  10. 10. 在appcompat使用自定义的LayoutInflater.Factory
,