聊聊陈旧的插件化
不长不短的职业生涯里,有一段搞插件化的经历,当时所在的团队也是行业里比较知名的最早搞插件化的团队之一。虽然理论上是使用方,但因为业务的需要,要把大插件拆成更小颗粒度的小插件,所以会比较深度的做源码级别的定制修改。
1 什么是插件化
插件化要解决的问题总的来说有三个方面
- 动态性:也就是更新功能无需依赖发版,动态下发应用的新功能。
- 包体积:一个巨型的APP功能模块很多,包体积自然小不了。插件化可以把不同的功能模块制作成单独的插件,按需下载应用,有效控制包体积。同时,对于一些“边缘功能”,对于每个用户个体来说可能,使用不到,插件化按需下载的优势也就体现出来了。
- 热修复: 对于线上的bug,利用插件化技术的动态性,可以在不发版的情况下实现热修。
说了这么多,简单的讲,插件化就是不依赖于发版使APP具备动态更新的能力。业界也管这个叫【免安装】。
2 怎么实现插件化
Android要实现插件化就是要解决三方面的问题。
- 代码动态化。
- 组件插件化。
- 资源的插件化
2.1 代码(类)的动态化
正常情况下,程序员写代码 -> 打包APK -> 发版 -> 用户安装、使用。
现在要解决这样一个问题,不重新安装app的情况下,如何让程序员编写的代码在已经被用户安装了的APP上跑起来。
Java语言的特性天然具备动态性。ClassLoader可以动态加载类文件(.class ,.dex,.jar)。Android插件化的基石之一在于此。
编写代码然后打包成dex或者apk,然后App获取到对应的类文件,利用classLoader动态加载类,创建对象,利用反射就可以调用类/对象的方法,获取类/对象的属性。
让代码能够动态的下发动态的执行。
当然这只是一个最基本的原理,里面还有涉及到很多的细节,比如
- 不同插件是相同classloader加载还是不同classloader加载。
- 宿主APP与插件APP是否是使用同一ClassLoader。
- 如果涉及到不同ClassLoader,加载的类如何进行通信。
对于这些问题的解决,不同的插件化框架也有不同的方案,各有利弊,如果大家感兴趣,后续会单独开篇详细的聊一聊。
2.2 组件插件化
上一节,说到我们利用classloader的动态加载机制配合反射,可以让代码动态化起来。有一个很重要的问题,Android系统中Activity、Service等组件是系统组件。他的特点是系统调用系统管理的。比如Activity著名的那些回调函数,都是System_Server进程那挂了号,对于系统进程来讲是有感知的。另外一方面我们每创建一个Activity组件都要在Manifest.xm里注册上,这个动作的意义就是让系统知道我们的应用里有哪些组件。相应的AMS都会对注册进行校验。
如果我们动态的下发一个Activity类,是不能像正常的类一样运行起来。如何实现组件的插件化?
简单的说,就是占坑+转掉.
既然不能动态的在Manifest.xml清单文件里动态的注册,但是可以在Manifest里预埋几个等用的时候拿出来用,解决注册问题。
既然生命周期函数都是系统调用的,不能我们触发,我们可以实现转调。简单的说启动一个插件Activty的时候,其实先启动占坑的Activity -> 加载创建插件Activity(当作一个普通的类对象) -> 占坑的Activity转调插件Activity。
关于组件的插件化大概思想如此,具体实现上也不同框架也会有不同的方案,hook的点也不一样。Replugin hook了ClassLoader,使得在加载占坑activity的时候替换为了加载插件的Activity。VirtualApk hook 了Instrumentation来模拟系统启动Activity等。
当然真正实现起来还是有一些问题需要解决,比如多进程的实现、不同启动模式的实现等。
2.3 资源的插件化
正常开发我们使用诸如 R.xx.x的方式索引资源,但是如果我们在一个插件的Activity中如果不做处理,直接使用该方式去是索引不到资源的。因为此时是在宿主的Resource中查找插件的资源。
插件Apk中的图片,layout等资源也是需要进行插件化处理,使得能够正确的访问到插件中的资源。资源的插件化核心是对插件APK中的Resource对象实例化,这样通过Resource对像代码中可能访问到插件的资源。
实现 的方式主要有两种,
一种是把插件中的资源合并到宿主中,这样使用宿主的Resource对象既能访问到插件的资源也能访问到宿主的资源。这种方式也会带来一个比较头疼的问题,资源冲突问题,通常的方案是id固定,这里就不做展开。
另外一种方案为插件创建单独的Resource对象。
packageArchiveInfo.applicationInfo.publicSourceDir = archiveFilePath
packageArchiveInfo.applicationInfo.sourceDir = archiveFilePath
val resource = packageManager.getResourcesForApplication(packageArchiveInfo.applicationInfo)
3 其他
经过以上,可以实现一个插件化最核心的东西,除此之外,还需要做
- 插件的安装,插件apk的解压,释放。
- 插件的注册,使得宿主和其他插件能够发现目标插件并与之通信。
- 试想这样一种场景,宿主中已经依赖了某个library(A),我们插件中也依赖A。作为插件中A的这个依赖是不是就是重复的,如何解决这一个问题。
- 编译器插件的生成。
4 结
从比较宏观的视角聊了下,插件化解决的问题,以及实现一个插件化大概的主体思路,是很粗颗粒度的描述。每一部分单独拆出来去分析研究会有很多东西挖掘出来。也在文中埋了一些坑,今后视具体情况再做分享。
thx 😊
来源:juejin.cn/post/7283087306604314636