背景

android 组件化的概念流行多时,目前已有的轮子中,AppJoint最为火热。

 1.理想中的项目结构:

  • 包含有一个 application 模块,以及一些技术组件的 library 模块(业务无关)。
  • 除了 application 模块以外,已经存在若干包含业务的 library 模块和技术的 library 模块。

2.需要解决的痛点

  • 业务模块单独编译,单独运行,而不是耗费大量时间全量编译整个 App
  • 跨模块的调用应该优雅,无论两个模块在依赖树中处于什么样的位置,都可以很简单的互相调用.
  • 不要有太多的学习成本,沿用目前已有的开发方式,避免代码和具体的组件化框架绑定
  • 组件化的过程可以是渐进的,立即拆分代码不是组件化的前置条件
  • 轻量级,不要引入过多中间层次(例如序列化反序列化)导致不必要的性能开销以及维护复杂度

APPJoint 的基础知识

极简 Android 组件化方案。仅包含3 个注解加 1 个 API,超低学习成本,支持渐进式组件化

1. 三个注解

  1. @ServiceProvider  : 需要为Router模块的接口的libary 模块实现类上 标记 @ServiceProvider 注解。我们可以在其他任何模块获得Router 接口的实例,调用里面的所有方法

    @ModuleSpec.:  如果您需要您的每个组件化模块可以独立运行,您可以为每个组件化的模块创建属于该模块的自定义 Application 对象。需要在模块的自定义 Application 上标记@ModuleSpec 注解

    @AppSpec:  在主 App 模块的自定义 Application 上标记 @AppSpec 注解。

AppJoint 可以保证,当标记了 @AppSpec 的类被系统回调属于 Application 的某个生命周期函数(例如 onCreate、 attachBaseContext)时,那些标记了 @ModuleSpec 的类也会被回调相同的生命周期方法。

2.一 个 API   AppJoint.service

AppJoint.service 就是 AppJoint 所有 API 里唯一的那个方法。

IModule1Router iModule1Router =AppJoint.service(IModule1Router.class);

实战

项目的结构

1.png
在上面这个项目结构图中,

  •  **app** 模块是全量编译的 application 模块入口
  •  **module1** 和 module2是两个业务 library 模块
  • module1Standalone 和 module2Standalone 是分别使用来独立启动 module1 和 module2 的 2 个 application 模块,这两个模块都被收录在 standalone 文件夹下面。
  • **router** 模块,我们在这个模块内定义 所有业务模块希望暴露给其它模块调用的方法(避免了各个模块的交叉依赖)

事实上,standalone 目录下的模块很少需要修改,所以这个目录大多数情况下是属于折叠状态,不会影响整个项目结构的美观。

build.gradle 文件

  • 在项目根目录的 build.gradle 文件中添加 AppJoint插件 依赖:

2.png

  • 主的App 模块(app 模块)的build.gradle 文件只需要依赖module1 和 module2,和公共模块router。并在主 App 模块应用 AppJoint插件:

app下的build.gradle 文件
3.png

  • module1业务模块下的build.gradle 文件,只需要对router模块有依赖:

4.png

  • router模块下的build.gradle文件,将app-joint-core依赖包引入

6666.png

  • standalone 模块只和各自对应的业务模块的独立启动有关,不需要依赖 app模块

module1Standalone 的build.gradle文件
6.png

注意:如何新建module1standalone模块(参照第四点)

在 Android Studio 中创建模块,默认模块是位于项目根目录之下的,如果希望把模块移动到某个文件夹下面,需要对模块右键,选择 "Refactor -- Move" 移动到指定目录之下。

注:推荐在 standalone 模块 内指定一个不同于主 App 的 applicationId,即模块单独启动的 App 与主 App 可以在手机内共存。

3.跨模块方法的调用

  • 理想的代码结构

    比较理想情况下的组件化的最终状态,App模块不承载任何业务逻辑,它的作用仅仅是作为一个application壳把Module1~Module(n)这个 n 个模块的功能都集成在一起成为一个完整的 App。Module1~Module(n)这 n 个模块互相之间不存在任何交叉依赖,它们各自仅包含各自的业务逻辑。

image

  • 现实的解决办法

新建一个router 模块,我们在这个模块内定义 所有业务模块希望暴露给其它模块调用的方法,如下图:

8.png
们在新建的router模块下定义了 3 个接口

  • IAppRouter接口声明了app模块暴露给module1module2的方法的定义。
  • IModule1Router接口声明了module1模块暴露给appmodule2的方法的定义。
  • IModule2Router接口声明了module2模块暴露给module1app的方法的定义。

router 模块下的  IModule1Router 接口为例,接口定义
9.png
module1模块下的 IModule1Router 接口实现类Module1Router的定义
10.png
注意:

  1.  在IModule1Router 实现类上方标记了一个@ServiceProvider注解,这个注解的作用是用来通知AppJoint框架在IModule1Router 和 Module1Router 之间建立联系,这样其它模块就可以通过AppJoint找到一个AppRouter的实例并调用里面的方法了。
  2. AppJoint.service 就是 AppJoint 所有 API 里唯一的那个方法。
  3. 如果一个模块需要提供方法供其他模块调用,需要做以下步骤:

    • 把接口声明在router模块中
    • 在自己模块内部实现上一步中声明的接口,同时在实现类上标记@ServiceProvider注解

完成这两步以后就可以在其它模块中使用以下方式获取该模块声明的接口的实例,并调用里面的方法

IAppRouter iAppRouter =AppJoint.service(IAppRouter.class);

4.为每个模块准备 Application

1.说明

在组件化之前,我们常常把项目中需要在启动时完成的初始化行为,放在自定义的 Application 中,根据本人的项目经验,初始化行为可以分为以下两类:

  • 业务相关的初始化(在各个业务模块中的自定义Application中)。例如服务器推送长连接建立,数据库的准备,从服务器拉取 CMS 配置信息等。
  • 与业务无关的技术组件的初始化(在主APP模块中的自定义Application中)。例如日志工具、统计工具、性能监控、崩溃收集、兼容性方案等。

新建的standalone模块,还不能单独运行,需要为模块增加初始化工作的Application类,用来实现该业务模块的初始化逻辑。

2.实践

  • 主的app模块中新建的Application实例(在AndroidManifest.xml中注册):

11.png12.png

  • module1, module2 中新建自定义 Application 为例:

13.png14.png

  • 运行主APP模块的结果:先运行APP下的applcation, 再依次运行各个模块下的自定义的Application。

15.png

5.模块独立编译运行模式下跨模块方法的调用

1.说明

在standalone模块(以module1为例)单独编译运行期间,其它的模块(app和module2)是不参与编译的,它们的代码也不会打包进用于模块独立运行的 standalaone 模块,我们如何解决在模块单独编译运行模式下,跨模块调用的代码依然有效呢?

 2.实践

  • module1模块下定义一个 RouterServices, 用于存放其它模块的接口的实例。

16.png
注意:module1独立编译运行的情况,即启动的application模块是module1Standalone, 那么RouterServices.sAppRouterRouterServices.sModule2Router这两个对象的值均为null,这是因为appmodule2这两个模块此时是没有被编译进来的。

  • module1Standalone  模块下新建两个IAppRouter  和 IModule2Router 的两个mock实现类。同样要加 @ServiceProvider

17.png

  • 在Module1Standalone模块下新建Module1StandaloneApplication 模块,将RouterServices.sAppRouter 和 RouterServices.sModule2Router 重新赋值。

18.png

  • 即可单独编译,不会报错

四、如何新建各个模块

1.新建一个application模块(app或者各个standalone模块),并将其移至standalone文件夹下

  • 后面直接next就好了,新建module3standalone如下图

*19.png20.png21.png

  • 将module3standalone移至standalone文件夹下。

22.png23.png24.png

2.如何新建module模块

25.png26.png27.png28.png

3.最后将新建的module模块和standalone模块加到项目的 settings.gradle配置文件下

29.png

4.模块名如何修改

  • 右键module名称 -- Refactor --Rename 对module进行改名
    30.png31.png32.png
  • 在module列表中点击”Edit Configurations...“,进入设置页面后再Name中输入步骤1填的名称,并且在Module中选择对应的module,点击OK即可修改完毕
    33.png34.png

    • 重新编译下

五、总结

  1. 慢慢积累,逐渐成长
  2. 遇到问题,用科学的方法定位到问题的源头,再寻找措施去解决问题。
  3. 成长的道路上,多思考,多总结,多输出
  4. 分享自己的独到见解,也是其人生意义的一部分

参照文档
回归初心:极简 Android 组件化方案 — AppJoint

AppJoint项目readme


still_shan
42 声望4 粉丝

前端攻城狮一枚