Android 模块解耦和的实践

0. 背景

设想这样一个场景,作为一个研发人员,现在产品要在你们的App中添加一个新的功能,比如说是人脸识别登录的功能。

这个新加功能具有以下特点:

  1. 该功能较为独立;
  2. 仅在一部分较少的设备上支持;
  3. 该功能需要集成一些SDK,会对安装包的体积有较大的影响;

那么接下来,作为项目中的技术人员,在对这个新功能做研发的时候,将会面临以下的问题:

  1. 如何做到在不同的设备上的兼容性?
  2. 如果一个设备是不支持的,那么是否可以不添加该功能?

带着以上的问题,我们进入到下一个章节。

1. 问题分析

针对此种情况,作为研发,对该问题进行分析,发现这个问题的本质在于 功能解耦和 以及 功能按需分发

对于Android平台的研发来说,功能的解耦和以及按需分发,都有一些不错的方案可供选择:

  • 方案A:插件化方案 + 动态下载插件;
  • 方案B:模块化方案 + 多渠道构建分发;

考虑以上两种通用的方案,各有利弊。对于方案A来说,插件化方案能够动态管理,使用方便,但是由于缺少官方的支持,最近的更新是越来越慢,在兼容性方面也逐渐无法追上Android 官方的SDK、开发工具等一些列软件的更新速度。

所以,在这里,我们会选择方案B(当然要看具体的需求,也可以选择方案A,本文中暂且不讨论方案A)。会使用模块化的设计,加上多渠道的构建系统支持,来完成这个需求。

2. 解决方案

在选定方案B之后,核心的问题就成了如何解耦和的问题(多渠道的构建分发使用Gradle 的构建系统就可以方便实现),这也是本文要讨论的问题。

在以 Gradle 为基础的Android 开发系统中,模块(Module) 是一个很常见也很基础的概念,基于模块的概念来给App的功能进行划分,来把一个庞大的项目划分为多个子模块。最方便的解耦和的方案,就是把新功能作为一个模块来研发。

但是,这样带来的问题就是最终打包的App中将会包含新增的功能的资源和代码,App的尺寸也会因此增加。同时这个功能在很多设备上是不支持的,也就是说在很多设备上该功能是一些无用的东西放在这些用户的设备中,即便这些用户根本用不到。

那么,我们能不能做成,把模块设计成可选择的打包呢?这样就按照需求打包成包含该功能和不包含该功能的两个渠道版本。

要做到可选择的打包,就需要彻底剥离模块间的依赖,这样就不能使用Java中的直接引用了。这样的设计需要引入一个接口层,示意图如下:

graph TD
A[调用者] -->|调用| B(接口定义)
C[实现者] -->|实现| B

这样,就做到了调用者和功能实现着的解耦和,通过接口定义来隔离双方。

3. 方案实现

接下来,需要针对以上的方案确定工程上的实现。

首先,按照功能定义几个Android的模块:

  • app 模块:app的主体,也是功能调用者;
  • api 模块:是接口的定义模块,内部包含一些列接口的定义,主要为一些 interface 的定义,和一些辅助的数据结构定义类;
  • impl 模块:功能实现模块,会对api 模块提供接口的实现。app 不能直接调用 impl 中的类,只能是调用 api中的接口;

以上几个模块 gradle 的依赖关系如下:

graph TD
A[app] -->|依赖| B(api)
A[app] -->|可选择依赖| C(impl)
C -->|实现| B

按照这样的设计架构,调用者可以按照需要来决定是否要打包 impl 模块。

这样的设计,面临着一个关键的问题,那就是:app既然无法访问 impl 中的类,那么app中怎么获取到 impl 的实现对象呢?

对于这个问题,我们实现方法为使用反射机制 + 工厂模式 来获取 impl 的实现。

下面是各个关键类中的代码示例:

1
2
3
4
5
6
// 1. api 模块定义接口
// 人脸登录的接口定义
class API {
public void faceLogin();
}

1
2
3
4
5
6
7
8
9
// 2. api 中的静态工厂
class APIFactory {
public static API createAPIInstance() {
// 通过反射机制使用类名来创建对象
// 类名为 ApiImpl
}
}
1
2
3
4
5
6
7
// 3. impl 中的实现类
class ApiImpl extends API{
public void faceLogin() {
// 实现具体的逻辑
}
}
1
2
3
4
5
// 4. app 中的调用者
// 获取工厂中的对象,来进行调用
API api = APIFactory.createAPIInstance();
api.faceLogin();

4. 总结

以上,就是针对模块间解耦和的一个具体的实践,可以做到调用者和实现者之间的隔离,也能做到调用者对实现者之间没有强依赖。

当然,以上代码示例中只是描述大致打原理,具体到项目实施中,还需要添加很多诸如容错处理、API版本以及可用性管理、API创建的合理创建等等很多问题需要考虑。

坚持原创技术分享,您的支持将鼓励我继续创作!