上一篇我们介绍了Android资源加载以及宿主如何加载插件中的资源的问题。文末我也提出了一个问题,多个插件如果存在包名相同的view的话,如何应对LayoutInflater加载布局会根据view的包名对view进行相应缓存导致的bug呢?
Activity静态代理之资源加载(下)
Android开发者对于LayoutInflate肯定都不陌生,我们通过getLayoutInflater() 或者 getSystemService(Class) 获取到系统提供给我们的实例,去将一个xml文件转换成View对象。
1 | LayoutInflater layoutInflaterInstance = LayoutInflater.from(context); |
我们看LayoutInflater.from(context)方法:
1 | /** |
看到这就很清楚了,LayoutInflate实例其实是系统服务提供。开头我们说到LayoutInflate默认会根据包名缓存View,这到底是怎么回事呢?我们从inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)这个方法的源码看起:
1 | public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { |
其中XmlResourceParser是xml解析器,它不是我们今天的重点,我们还是接着看inflate过程:
1 | public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { |
我们可以看到,其中有一个createViewFromTag()方法,生成了实际的View对象:
1 | private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) { |
接着看:
1 | View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, |
这里我们看到,View的创建调用的方法,有这么几个优先级:
mFactory2 > mFactory > mPrivateFactory > onCreateView(系统控件) / createView(非系统控件)
那我们所说的View的缓存,其实就是出现在createView方法内部:
1 | public final View createView(String name, String prefix, AttributeSet attrs) |
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 | public class LayoutInflaterManager { |
其中LayoutInflaterFactory是我们自己实现了LayoutInflater.Factory2接口:
1 | public class LayoutInflaterFactory implements LayoutInflater.Factory2 { |
我们通过AssetManager去缓存不同的LayoutInflater实例,在插件中这样写:
1 | LayoutInflaterManager.getInstance(context); |
问题解决~
下一篇我打算说一说插件框架如何支持插件包中的so文件,谢谢~