聊聊Android的静态代理插件框架原理[04]--Activity静态代理之资源加载(下)

上一篇我们介绍了Android资源加载以及宿主如何加载插件中的资源的问题。文末我也提出了一个问题,多个插件如果存在包名相同的view的话,如何应对LayoutInflater加载布局会根据view的包名对view进行相应缓存导致的bug呢?

Activity静态代理之资源加载(下)

Android开发者对于LayoutInflate肯定都不陌生,我们通过getLayoutInflater() 或者 getSystemService(Class) 获取到系统提供给我们的实例,去将一个xml文件转换成View对象。

1
2
LayoutInflater layoutInflaterInstance = LayoutInflater.from(context);
layoutInflaterInstance.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot);

我们看LayoutInflater.from(context)方法:

1
2
3
4
5
6
7
8
9
10
11
/**
* 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;
}

看到这就很清楚了,LayoutInflate实例其实是系统服务提供。开头我们说到LayoutInflate默认会根据包名缓存View,这到底是怎么回事呢?我们从inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)这个方法的源码看起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();
}
}

其中XmlResourceParser是xml解析器,它不是我们今天的重点,我们还是接着看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
98
99
100
101
102
103
104
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("**************************");
}

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");
}

rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
// 通过createViewFromTag()方法,生成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
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.
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) {
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) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;

Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

return result;
}
}

我们可以看到,其中有一个createViewFromTag()方法,生成了实际的View对象:

1
2
3
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
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;
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) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
//系统控件
view = onCreateView(parent, name, attrs);
} else {
//自定义控件
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, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;

} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}

这里我们看到,View的创建调用的方法,有这么几个优先级:

mFactory2 > mFactory > mPrivateFactory > onCreateView(系统控件) / createView(非系统控件)

那我们所说的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
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
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) {
// 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);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
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;

final View view = constructor.newInstance(args);
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) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;

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

sConstructorMap是什么呢?其实就是根据View的包名缓存了View的构造函数。正常情况下,相同包名的view有且只有一个,所以这样缓存是完全没有问题的。但是插件化之后,不同的插件包完全可能存在包名相同的View,如果还采用默认的缓存方法的话,肯定会有问题的。

1
private static final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<>();

看到这里,有的小伙伴可能存在疑惑了,java里类怎么能包名相同呢?其实包名相同并不是bug,还记得我们系列的前文提到的ClassLoader类吗?通过反射拿到的类,只有ClassLoader和包名都相同,才能确定是同一个类。显然,这也给我们提供了一个思路,插件化的view,不仅需要包名相同,也要判断ClassLoader相同才认为是一个类。

那如何解决这个问题呢?一个比较简单的处理方法就是,不同的插件使用不同的LayoutInflate实例,那如何创建LayoutInflate实例呢?
我们注意到LayoutInflate API文档有这么一句话:

To create a new LayoutInflater with an additional LayoutInflater.Factory for your own views, 
you can use cloneInContext(Context) to clone an existing ViewFactory, 
and then call setFactory(LayoutInflater.Factory) on it to include your Factory.

好的,那一个简单的LayoutInflaterManager就设计出来了:

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
public class LayoutInflaterManager {

private LayoutInflaterManager() {

}

private static LayoutInflaterManager sInstance = new LayoutInflaterManager();

public static LayoutInflaterManager getInstance() {
return sInstance;
}

Map<AssetManager, LayoutInflater> mLayoutInflaterCache = new HashMap<>();

public LayoutInflater getLayoutInflater(Context context) {
LayoutInflater layoutInflater = mLayoutInflaterCache.get(context.getAssets());
if (layoutInflater == null) {
layoutInflater = LayoutInflater.from(context).cloneInContext(context);
LayoutInflaterFactory factory = new LayoutInflaterFactory();
layoutInflater.setFactory2(factory);
mLayoutInflaterCache.put(context.getAssets(), layoutInflater);
}
return layoutInflater;
}

public void clear() {
mLayoutInflaterCache.clear();
}
}

其中LayoutInflaterFactory是我们自己实现了LayoutInflater.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
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
public class LayoutInflaterFactory implements LayoutInflater.Factory2 {

private final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<>();
private final Class<?>[] mConstructorSignature = new Class[]{Context.class, AttributeSet.class};
private final Object[] mConstructorArgs = new Object[2];

@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
//如果没有'.',说明是Android系统控件,直接返回null,让系统自己createView
if (-1 == name.indexOf('.')) {
return null;
}
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = context;
Class<? extends View> clazz = null;
//先从本地缓存读取
Constructor<? extends View> constructor = sConstructorMap.get(name);
try {
if (constructor == null) {
//没有缓存,根据类名创建Constructor对象存入缓存
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(name).asSubclass(View.class);
constructor = clazz.getConstructor(mConstructorSignature);
sConstructorMap.put(name, constructor);
}
Object[] args = mConstructorArgs;
args[1] = attrs;
constructor.setAccessible(true);
return constructor.newInstance(args);
} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + 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 " + name);
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loaded class is not a View subclass
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class not found " + name);
ie.initCause(e);
throw ie;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
} finally {
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
}

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}

}

我们通过AssetManager去缓存不同的LayoutInflater实例,在插件中这样写:

1
LayoutInflaterManager.getInstance(context);

问题解决~

下一篇我打算说一说插件框架如何支持插件包中的so文件,谢谢~