Android 组件化探索与实践

2,072 阅读6分钟

Android 模块化工程结构

  • 在 AndroidStudio 开发 Android 项目时,使用 Gradle 来构建,Google 官方提供了对应的 Gradle 插件,方便构建过程
  • Android 工程往往会根据业务或功能划分为多个 Module(模块) ,一个主 Module,依赖 android application 插件来,该插件提供构建和打包 Android 应用等任务。主 Module 中通常标识了 Application 的入口及首个被启动的 Activity 等。
apply plugin: 'com.android.application'
  • 其他 Module 大多依赖 android library 插件,在应用打包时,依赖了 android library 插件的 Module 会被一同打到 apk 中。
apply plugin: 'com.android.library'

image

  • 之所以划分多个 Module,主要是利用 Android 开发中的 module 这个概念,将一个"功能"或"业务"作为一个 module,每个功能的代码都在自己所属的 module 中添加,module 之间在没有显示的声明依赖关系时是不能相互访问的,这样能够相对的降低耦合,易于管理。

  • 但这样的设计在各个功能相互比较独立的情况下是比较合理的,但在业务比较复杂,功能耦合严重的项目中将难以维护。其主要的缺点有:

    • Module 粒度不好掌控,划分不合理,组织很混乱

    • Module 间相互依赖,关系复杂,耦合严重,且不方便重构和迁移

    • 代码调试不够灵活,修改少量代码也需要运行整个项目查看结果,浪费时间;

组件化设计

粒度划分与组织

  • 业务 Module 划分太细,难以组织,依赖复杂;业务和基础库、公共库之间没有明显区分,组织混乱;

  • 分层

    • 基础层
      • baselib: 基础库
        • 职责:底层网络封装(network)、通用工具类、json解析、加密算法
        • 依赖:Gson、Glide、okHttp、Retrofit
      • framework
        • 职责: MVP、Router
        • 依赖:无
    • 公共层 - common
      • 职责: 业务相关工具类,业务相关常量、TitleBar、公共资源等
      • 依赖:api ':baselib' ':framework'
    • 业务层 - modules
      • 职责: 根据业务场景划分的多个模块,包括主 module 和多个业务 module,为了便于称呼,把一个业务 module 称为一个"组件"
      • 依赖: implementation ':common'
  • 如此一来,整个工程的结构被分为三层,日常开发基本只需要关注最后一层即可。工程架构变成了一个主模块挂载多个业务模块,共同依赖几个功能模块的模式,将这种结构称之为**"组件化"**结构。层次结构及依赖关系图:

    层次结构

  • 理想情况下,主模块承载的业务能力应该尽可能的少,可以只作为整个应用的"壳儿",除应用的初始化配置及主 activity 外,尽量不包含其他业务。

  • 主模块决定挂载哪些业务组件,在应用的初始化过程中进行配置。

组件间解耦

  • 组件间通信:接口

    • 业务组件间依赖通过接口发生,实现类不发生直接依赖关系

    • 把每个业务组件对外提供的接口统一存放,可以抽成一个单独模块,比如 service,其中 common 依赖 service

    • 在应用启动时将所有组件的接口注册到接口管理器中,以便于在任何一个业务组件中,都可以访问到其他组件的接口,进而与之通信。当然,接口的实现类在对应的业务组件内。

      带有service的工程结构

  • 组件间跳转:Router

    • 使用现有 Router 机制,放在 framework 中
    • Router 实现了从 String -> Class 的映射,也就实现了解耦

快速运行调试

  • 目标:日常开发中,业务组件可以单独运行。这样,在针对某个组件修改需求时,可以只运行该组件就可以看到结果或者单步调试,提高编译速度,节省时间。应用打包时,业务组件依然一并输出到 app 中。
  • 基本思想:既然业务组件即可以单独调试,又可以被主 Module 依赖,那么业务组件依赖的 Android 插件就需要动态配置。可以在 debug 模式下,每个组件不再依赖 'com.android.library',而是依赖 'com.android.application' 插件,release 模式下,组件依然依赖 'com.android.library' 插件。这样能够实现,在 debug 模式下,组件以 application 的形式存在,在 release 模式下,组件依然以 library 的方式打到同一个 apk 中。
  • 实现方式:自定义 gradle 插件,根据项目的配置文件(如 当前是否为debug模式、哪个 module 是主module,哪些是组件),动态选择依赖模式 'com.android.library' 或 'com.android.application'
  • 还要解决的问题
    • 应用初始化配置

      • 很多工具类都需要初始化才能使用,最常见的是传入 context,我们一般在 app 的 Application 的 onCreate 时对这些类进行初始化工作。
      • 同理,当组件以 application 方式运行时,我们也可以指定入口 Application,来进行一些初始化工作,注意,该 Application 只有在 debug 模式下才起作用
      • 可以通过 gradle 的 sourceSets 的配置,指定 debug 模式下 java 目录和 res 目录
    • 指定 launcher activity

      • 同样,通过 gradle 的 sourceSets 的配置,也可以指定 manifest 文件,就可以在 manifest 中配置 launcher activity

        sourceSets {
                main {
                    if (Boolean.valueOf(rootProject.getProperties().get("isDebug"))) {
                        manifest.srcFile 'src/main/debug/AndroidManifest.xml'
                        java.srcDirs = ['src/main/java', 'src/main/debug/java']
                        res.srcDirs = ['src/main/res', 'src/main/debug/res']
                    }
                }
            }
        

其他需要解决的问题

  • 组件与接口的注册
    • 挂载哪些业务组件应该由主模块决定,这样甚至可以实现动态下发配置,来挂载不同的业务组件
    • 接口的注册应该由每个组件自己决定,组件初始化时,决定要对外提供哪些接口
    • 业务组件和接口的注册应该尽可能早,以便于其他组件的访问。无疑在应用启动初期就要进行注册以及必要的初始化工作,可以选择在主业务的 application 的 onCreate 生命周期注册组件。而且主模块不能与其他业务组价产生依赖,一种比较好的方式是通过反射解耦。
    • 另外,业务组件以 library 运行时,是没有 application 的,需要给它制造一个类似的初始化时机。可以定义类似具有 onCreate 方法的接口,在主模块中依赖该接口,并通过反射,对业务组件进行初始化工作。

优点

  • 每个业务组件的修改或者重构相对于其他组件是完全独立的
  • 业务组件内可以使用更灵活的组织方式,也可以保持现有多 Module 的方式
  • 整个工程也比较方便后期扩展或者重构,即使进一步通过插件化重构也很方便
  • 可动态选择挂载哪些组件,线上可以通过下发配置文件,实现线上切换组件的功能
  • 可以使用模拟器运行调试工程

实践 - 纸上得来终觉浅,绝知此事要躬行

Demo

Article by Panxc