Shadow解决插件和宿主有同名View的方法解析

问题描述

在“免安装运行App”这个场景中,插件代码一般和宿主是彻底不相关的。甚至项目都是独立管理的,插件和宿主是不一样团队开发,不一样版本发布管理的。在这种状况下,插件和宿主中出现相同名字的类是很是常见的。只要设计好ClassLoader的结构,将插件和宿主的ClassLoader隔离开,就能够避免Java层面的类冲突问题。可是,Android系统中存在一个错误的设计,致使LayoutInflater在inflate的时候使用的View类并非简单的从ClassLoader中读取的,而是自行作了一层缓存,以View的类名做为Key保存了Class对象。这个缓存存储在一个静态域上,所以在同一个进程中,不能存在两个同名的View都使用LayoutInflater构造。这个问题常见于插件框架对于support包中的RecycleView支持上。无论这个问题的话,当宿主和插件都使用了support包中的RecycleView,就会出现提示信息相似于“RecycleView cannot cast to RecycleView”的Crash异常。git

传统的解决方案

方法一,逃避法。我见过一些插件框架,在宿主和插件都用到support包时,就将插件用的support包去掉,让插件直接使用宿主的support包。这样作能够避免前面例子中使用RecycleView等support包中的View的问题。可是既不能避免其余宿主和插件中重名的自定义View的问题,也使得插件使用的support包版本和预期的不一致了。github

方法二,清空缓存法。还有一些插件框架,经过反射修改LayoutInflater的缓存,清空缓存达到目的。这样作有两个问题,一是宿主随时可能再次inflate view,再次写入缓存。插件框架可能要频繁清空缓存,甚至每次使用LayoutInflater前都要清空缓存。这样作会使得LayoutInflater失去缓存机制,形成性能降低。二是经过反射修改这个缓存是不安全的,须要兼容多种版本的Android系统,还会涉及访问系统私有API。缓存

Shadow的解决方案

在开发Shadow的时候,咱们仔细学习了LayoutInflater的设计。发现经过LayoutInflater自带的设计就能够解决这个问题。安全

LayoutInflater是能够继承重写的,并且插件中用到的LayoutInflater都是由插件框架中间层提供的Context返回的,所以咱们可让插件中拿到的全部LayoutInflater都是咱们自定义的LayoutInflater。框架

LayoutInflater具备一个注入Factory的设计,若是设置了LayoutInflater的Factory,LayoutInflater在构造View时就会先试图让注入的Factory构造。若是Factory可以构造出来,就会优先使用。若是构造不出来,才会走内置的构造逻辑。而咱们前面说的问题就是内置的构造逻辑形成的。所以,咱们就写了一个自定义的Factory,而后复制了本来内置构造逻辑的代码。在这段逻辑中,也有缓存机制。可是咱们将缓存的Key添加了标记插件apk的“partKey”做为一部分,这样相同名字的View在缓存中就是不一样的Key了。所以,咱们既保留了LayoutInflater本来的缓存设计,还让它支持了多插件。因此在Shadow中,不论是宿主和插件,仍是多插件之间,均可以使用相同名字的View。性能

这里还有一个小的知识点,就是一个LayoutInflater对象只能set一次Factory。把咱们自定义的Factory设置进去了,插件的代码拿到这个LayoutInflater就不能再次set Factory了。可是LayoutInflater的设计又容许clone一个LayoutInflater对象,保留它的的Factory。当新的LayoutInflater被set Factory时,一个内置的逻辑会将两个Factory合并起来使用。所以在Shadow的代码中,能够看到ShadowLayoutInflater中包含了一个InnerInflater,套了两层LayoutInflater,就是这个缘由。学习

这部分相关代码能够查看com.tencent.shadow.core.runtime.ShadowFactory2类和相关类的实现。优化

PS

最新版本的Android系统已经没有这个Bug了。为了兼容低版本的Android系统,这个问题仍是须要解决的。插件

github.com/Tencent/Sha…设计

欢迎你们参与到Shadow的开发中来,Shadow的代码有很是多值得优化的地方,让咱们开源共建起来!

相关文章
相关标签/搜索