浅谈java SPI机制在Android 模块化开发中的应用+实例

本文主要介绍使用Java SPI(Service Provider Interface)机制帮助我们在模块化开发中自动组合我们的模块,提高开发效率。

文章后面我会介绍我是如何使用他为uniapp插件实现零配置注入模块的。

在Android日常开发中,模块化的开发已经是一件再平常不过的事情了,一个Android Gradle项目分为若干个子module,在每个module中开发独立的功能模块,在app主模块中引用这些模块,然后在打包成一个App。

这里产生了一个问题, 主module如何自动发现子模块中的服务呢? 当我们移除了一个AAR包,我们如何自动移除这个服务呢? 这里面就花样百出了,有愚蠢的(例如在主项目中维护一张配置表, 每次有修改去改这个配置表)、也有酷炫的。

例如Arouter这个开源库中,通过使用APT(annotation processor tool) 为每个模块生成了路由信息自动编译成java文件,但这种处理过程在各个module之间是相互隔离的,甚至不在同一时间发生。App模块无法得知子模块的路由表!

那么Arouter是如何做的呢? 他使用一个Gradle插件,通过Gradle的transform接口,对java字节码在打包到Dex之前进行修改,遍历每个class文件,将路由表织入到一个class文件预留的空白方法中。这种酷炫的操作虽然也能达到我们的需求,但是他为了修改一个class文件,遍历扫描了所以class文件,会严重拖慢我们的打包速度,项目越大这总影响也越大。

我曾经给Arouter提过issue(https://github.com/alibaba/ARouter/issues/716) , 但是没有被采纳。

其实这个问题,已经被前人遇到并给出了很好的解决方案。Android背靠庞大的java技术栈, 这是Andorid选择Java的一大优势。我们可以从Java开发的其他地方寻早答案。

什么是Java SPI(Service Provider Interface)

通俗的讲就是一个jar包的META-INF/services目录下,包含一个文件,文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔。

我们使用JDK提供的ServiceLoader类,通过ServiceLoader.load或者Service.providers方法拿到实现类的实例。

我们的接口定义和实现可能在不同的jar包中,实现类的jar包只需要在META-INF/services创建接口的全限定类名的文件,内容是实现类的全限定类名
,这样在运行的时候这个实现就能被应用发现并使用。

在Android中META-INF/services目录属于资源文件,打包的时候被打包到apk包的根目录下。

我们知道Android处理资源文件冲突有大致三种方式:1.报错、2.合并、3.覆盖(默认)。 不在gradle脚本中做特殊处理的情况下, 冲突的资源文件会按优先级覆盖, 而META-INF/services我们惊奇的发现他的默认行为是合并。

众所周知,一个APK或者AAR包其实也是一个Jar包,他符合Jar包的标准和规范,Android也部分遵循Jar包的打包、编译流程。这是我们能在Android中使用这种机制的前提。

应用实例

最近技术调研了uniApp,并且要为公司的App开发几个Android插件。查看官方文档,发现每开发一个插件都需要手动编辑package.json, 如果有变动还要手动去修改。这样开发太繁琐,而且容易出错,有没有方法把这些过程自动化, 这对我们之后进行CI集成也是至关重要。

为此,我开发了一个插件开发框架EZUniPlugin , 为开发uniapp或者weex插件提供自动注册的能力,同时提供uniapp文档中没有的功能拓展,简化了开发流程,为自动化、持续集成提供支持!。

解决的思路就是APT + SPI + init content provider。

  • APT

使用注解编译器将我们的插件入口类信息写入到META-INF/services中

  • SPI

在打包过程中多个插件包信息被自动合并到一个文件中

  • init content provider

注册一个 content provider 自动介入App启动过程,读取service获取插件信息,完成插件注册。

全程不需要uniapp提供任何方法,因为uniapp在Android端是基于Weex的,我们只需要实现向Weex注册我们的插件就行了。

现在,我们开发一个插件只需要导出aar包,然后扔到对应目录里,就能直接生效!

如果是开源项目还可以像我一样直接发布到Jcenter(如何发布开源项目到Jcenter),这样连包都不用拷了,只需要在配置文件中添加一个gradle依赖就行了!

你可能感兴趣的:(浅谈java SPI机制在Android 模块化开发中的应用+实例)