插件框架实现思路及原理

时间:2022-10-19 00:25:33

插件框架实现思路及原理

一、技术可行性

a) apk的安装处理流程

i. apkcopy/data/app

ii. 解压apk中的class.dex,并对其进行优化,获得odex(即JIT)。最后保存到/data/dalvik_cache

iii. 还有一些权限和包信息,会缓存到/data/system中的packages.listpackages.xml中。


b) android上,对apk包的加载逻辑

i. 加载逻辑

 Zygote(孵化器)在成功启动一Android进程后,会根据packages.list的内容(启动时会加载到system_process中的pakcagemanager中),把odex文件,加载到dalvik中,完成逻辑的加载;


ii. 资源读取

资源读取,主要有两三个类,分别是ResourceAssertManagerLayoutInflater

当在显示界面时,就通过这三个类读取资源。


c) 结论和猜想

i. apk相对于整个android系统而言,其本身就是一种插件形式体现。根据上面关于逻辑和资源的读取概述,完全是可以静默实现的。其次,class.dex并没有包含Android SDK的代码,只是保留对Android SDK接口的调用。 可以这样想象,Android SDK即插件框架,而Android OS即为整个插件的宿主环境。因此这就可以解释了,为什么在1.x编译的代码,在2.x甚至3.x都可以运行,因为只要插件宿主的接口(Android SDK)不变,插件运行时所调用的接口都可以被找到。


ii. 为了减少内存占用,ResourceAssertManagerLayoutInflater必然不会把apk中的所有资源都加载进来,而是用时才加载并缓存,而且还有一些的处理机制(如最不常用清除等)。因此这些类当中,必然存在一个指明资源路径的字段或者结构。


iii. 要保证兼容性,插件框架公开给插件的接口,必须遵守Open-Close(开发-封闭)原则。另外,一些已经废弃掉接口,同样需要保留。比如Service中的setForegroundJDK的中关于Thread的一些接口等。


iv. 可以尝试通过反射,修改ResourceAssertManagerLayoutInflater中指明资源路径的字段;另外,还可以查看源码,查找设置资源路径的方法。



二、技术实现要点

a) 逻辑加载

i. 针对接口编程。这个是所有插件框架的基本设计模型。

ii. 通过DexClassLoader加载插件所实现的插件接口,详细可参考PluginManagerImpl中的parserPlugin方法实现,关键代码如下:

插件框架实现思路及原理

插件框架实现思路及原理


b) AssertManager的实现

经查阅Android源码,发现AssertManager的实例生成,用到两个隐藏的方法,如下所示:

插件框架实现思路及原理

插件框架实现思路及原理

插件框架实现思路及原理


插件框架实现思路及原理

通过以上代码,我们就可以得到我们插件的AssertManager了,关键代码如下所示:

插件框架实现思路及原理

插件框架实现思路及原理


c) Resource的实现

有了插件专用的AssertManager,那么插件的Resource也轻易得到了,关键代码如下所示:

插件框架实现思路及原理插件框架实现思路及原理


d) LayoutInflater的实现

一般我们要获取LayoutInflater,都必须通过Context来获得,即是说LayoutInflater的资源读取,都是通过ContextgetResoure以及getAssert读取,是直接跟宿主挂勾的。这里有两个方法可选选择:

其一,自己重写Context类,并把getResouregetAssert的返回值改为上面所得的插件资源相关的实例,即包装法;

其二,考虑到平时我们用LayoutInflater时,主要是用来加载布局文件(XML),因此可以投机取巧点,只针对inflater进行修改。

目前框架采用的是第二种方法。先看看LayoutInflater的源码实现,如下所示:

插件框架实现思路及原理插件框架实现思路及原理

插件框架实现思路及原理

插件框架实现思路及原理


因此,只调用插件的Resources,并调用其第二个方法,并可实现加载插件的布局问题,为了方便使用,定义了一个ILayoutInflater接口,封装实现细节,关键代码如下:

插件框架实现思路及原理

插件框架实现思路及原理


事实上,通过以上的方法,还是无法完成插件XML布局文件的加载,通过跟踪源码,会发生View的生成,还需要因为利用到当前Context(Activity)的一个类型Theme的实例。跟踪过程如下:

View(Context context, AttributeSet arrts, int defStyle)

Context.obtainStyledAttributes(AttrobiteSet arrts, int[]  attrs, int defStyleAttr, int defStyleRes)

Context.getTheme()

......

而这个Theme类型,是Resource的一个内部类,不单可以直接引用Resource的,还通过Context保存AssertManager的引用。源码如下:

插件框架实现思路及原理

插件框架实现思路及原理


因此,我们还需要通过反射的方式,把当前Theme的实例,替换成我们插件的。当XML布局文件解释成功后,再恢复过来。留意上面代码中的beginend方法,就是这个过程的封装。关键代码如下:

插件框架实现思路及原理

插件框架实现思路及原理



而插件的Theme实例,可通过ResourcenewTheme获得,关键代码如下:

插件框架实现思路及原理

插件框架实现思路及原理


e) 混合资源解析的实现

基本上,上面的有了上面的三个类,就可以完全加载插件的读取插件的资源和逻辑。但往往事情并不是这么简单。

考虑到UI的可重用性,插件里,往往会很多的用到宿主所提示的UI库接口。因此,就需要考虑,当在解释插件的xml布局时,如何混合使用两方的资源。从上面的过程可得,无论是ResourcesAssertManager还是LayoutInflater,都是同时只能针对一方资源。当在插件的布局文件中,使用了两方的文件,必然会因为找不到资源,解释出错。

不过,LayoutInflater,提示了一个setFactory,可以在解释XML布局文件时,优先解释。关键代码如下:

插件框架实现思路及原理

插件框架实现思路及原理


插件框架实现思路及原理

插件框架实现思路及原理


三、待完善的地方

在宿主中定义的资源,目前除了自定义view之外,都不可以使用。比如文字、颜色值、样式等。

插件的权限,必须是宿主的子集。

插件中包含so的加载逻辑,还没有实现。

目前插件的加载,都是加载dex,而不是odex。还需要查阅源码,手工实现这个优化过程。