一个基于 SPI 的 Android 组件化框架

一个基于 SPI 的 Android 组件化框架

详细的源码请移步 github:spi_component

首先,我们思考下什么是组件化,什么是插件化?什么时候我们用组件化,什么时候我们用插件化?

什么是组件化,什么是插件化。

所谓的组件化就是将整个项目拆分为几个组件,单个组件高内聚,组件间低耦合,这些组件拼到一起,编译成一个目标 app

所谓的插件化就是将整个项目拆分为几个模块,分别打包编译,在主 app 需要用到某个模块的时候
按需加载这些模块。

什么时候我们用组件化,什么时候我们用插件化

这是一个策略问题,用何种方式需要根据实际情况来。

刚刚说了,组件化是将项目拆分为几个组件,单个组件高内聚,组件间低耦合。所以复用会比较方便,如果公司产品多可以考虑这种方式。

另外,组件化框架一般会允许组件单独编译,单独调试,由于组件一般不会太大,所以编译调试单个组件的时间较整体编译会少很多。而且组件化方式编译的 app 是常规模式编译的,可以较好的适配新的 Android 版本,相较插件化更稳定。

插件化是将整个项目拆分为几个模块,和组件化类似,有相似的优势,但是一般插件化方案都涉及 Hook 技术,而这些技术随便 Android 版本迭代,可能会被封杀,存在一些适配的风险,可能会不稳定。

随着我们的业务发展,我们的主工程越来越复杂,不得不考虑组件化和插件化来简化我们的开发
工作,考虑到稳定性,我们决定改造我们的工程,后续切换到组件化开发。

组件化的一些思考

内部需求

在改造的时候,我们采集了一下内部对于组件化需求:

  1. 学习成本低,希望比较容易上手(接入简单)。
  2. 现有的代码改动少,调用方式最好不变。
  3. 组件之间互相调用(希望可以直接使用其他组件的资源,相对显式打开其他组件的页面等)方便、高效。
  4. 希望不要引入太多无关的代码。
  5. 希望新增组件的方式简单点。

开源方案待解决问题

参考一些开源的组件化方案,有很多借鉴的地方,但是感觉还是有些不是很符合我们的需求,比如:

  1. 组件间的资源无法较好的使用,Activity 的跳转还好,但是涉及到 Fragment 的调用的时候,比如组件 1 需要使用组件 2 里面的 Fragment 的时候就不是很方便了,基本无解。
  2. 有些组件的初始化需要放到 Application 里面,或者需要使用 Application 里面的一些方法,主流的组件化方法感觉对这块支持不好,主 App 维护一个 Application , 组件作为单独 App 的时候也需要维护一个 Application ,修改是相对繁琐。
  3. 组件的单独编译和作为 library 库编译方式不灵活,主要表现为,想单独编译的时候,需要修改脚本,切换为 library 库的时候也需要修改脚本。

架构介绍

模块介绍

鉴于以上的一些特点,我们希望可以在现有的基础上做一些优化,希望组件化开发更简单,更方便。

于是,我设想了这个基于 SPI 的组件化框架。框架的大致架构如下:

模块名称 作用
core 框架的核心,就是 app 的骨架,定义了各个组件对外暴露的部分。可通过 getXXX 方式暴露出去。而且比较灵活, Fragment 资源图片等都可以暴露。
app app 的承载容器,就是对外发布的时候,用这个项目来编译,包含所有的组件
compx 某个组件,依赖 core ,实现 core 里面这个组件对外暴露的业务逻辑等。
single-alone/compx-alone 组件单独调试编译入口,就是编译出来的 app 只有这一个组件实现了。

core 模块定义了各种组件承担的业务和对外暴露的一些资源等,主要是通过接口暴露。
同时提供各个模块的默认实现。

compx 模块是一个组件的实现,实现的是 core 模块里面定义的的组件,需要依赖 core
模块。

app 模块,就是我们对外发布的 app ,把所有的组件放到一起编译。 依赖 core 和所有的 comp
模块。

single-alone/compx-alone 某个组件的入口 app ,即依赖 core 和某个 compx 模块,
方便单独调试某个模块。

组件是如何工作的

  1. 一个组件需要在 core 模块里面定义好,我们暂用 ICompXService 来表示,其实就是一个接口,然后这个接口可以定义这个组件想对外暴露的东西,比如可以获取某个值(getXXX),可以获取一个 Fragment (getXXXFragment) 等。

  2. 新建一个 compX 模块,依赖 core 模块,然后实现刚刚在 core 模块里面定义的 ICompXService 接口,我们记为 CompXServiceImpl ,这样我们就有了一个组件。

  3. core 模块的 AppTool 类里面定义一个 ICompXService ,这样我们就有了一个组件,当 AppTool 类被加载的时候,通过 SPI ,我们就可以把 compX 里面实现的 CompXServiceImpl 和刚刚定义的 ICompXService 绑定起来。( SPI 的原理大家可以参考
    Java SPI机制原理和使用场景)

  4. app 模块同时依赖 corecompX 模块,然后通过 core 模块的 AppTool 类,我们可以就可以使用 CompXServiceImpl 了。

组件的原理大概就是这样的。

一个基于 SPI 的 Android 组件化框架

疑难点是如何解决的

1 组件直接使用其他组件的 Fragment 等其他资源

上面介绍了, compX 通过 core 里面的 ICompXService 定义的,提供了对外的接口,如果有 Fragment 需要对外暴露,直接在 ICompXService 里面定义一个方法暴露就好了,然后实现的时候,暴露出需要暴露的 Fragment 就好了,其他组件通过 AppTool 类拿到 ICompXService 时候,就可以通过接口暴露的方法使用 Fragment 了。

2 组件的初始化需要放到 Application 里面

有时候,我们需要在 Application 初始化一些资源,一般情况下,我们的做法是,在主 app 里面的 Application 里面添加初始化的代码,然后在组件 app 的 Application 里面添加初始化的代码。
这样有一个问题就是如果需要改动代码,主 App 和组件的 App 里面都需要修改,同时,主 App 的 Application 代码是比较复杂的,因为主 App 里面包含所有的组件,在修改主 App 的 Application 的风险是比较大的。

为了解决这个问题,我们的做法是抽象出来一个类 XApplication ,这个类和 Application 类类似,把主要的方法,比如 onCreate 方法提取出来,通过 ICompXServicegetApplication 方法,在初始化 AppTool 的时候,就可以拿到组件的 XApplication ,然后,在 App 定义的 Application 里面的回调方法里面同步调用组件的 XApplication 里面对于的方法。

通过这种方法,我们可以仅在组件里面处理自己的初始化逻辑就好了。

可能碰到的情况是组件的初始化有先有后,有的初始化依赖某个组件初始化完成了才可以初始化。这个问题暂时考虑会在 XApplication 里面通过一个优先级的参数来表示这个组件初始化的顺序,在 App 的 Application 方法回调的时候根据优先级来依次回调 XApplication 里面的方法。

3 便捷调试某个组件

前面提到了组件作为 library 库的时候和单独调试的时候,都需要修改编译脚本,不是很方便。所以我们给出的方法是,单独新建一个 app 模块,作为单独调试组件的入口,在需要单独调试的时候,我们直接运行这个入口 app 模块就好了。当我们需要对外发布的时候,我们编译主 App 就好了。

4 如何新增一个组件

新增一个组件的话,需要做如下的事情,

  1. core 里面定义这个组件,也就是继承 ICompService 接口,我们记为 ICompXService
  2. core 模块的 AppTool 类里面注册这个 ICompXService
  3. 新建一个 library ,依赖这个 core 模块,然后实现 ICompXService
  4. app 模块或者组件独立入口 app 模块依赖这个新建的 library

经过上面四步,新增一个组件完成了,中间不需要特别修改编译脚本,只需要修改一些引用库之间的关系。

一些需要注意的事项

  1. core 模块会通过 SPI 来加载组件的实现,也就是组件 library 里面实现 ICompXService 接口
    的类,所以组件 library 里面要按照 SPI 的要求新建入口文件。