安卓组件化之动态依赖功能模块

本文最后更新于:2025年3月3日 晚上

对于拥有不同渠道的 APP 来说,可能需要集成不同的功能模块,也可能同一个功能有不同实现。最简单的方法可能是通过接口、工厂模式、 flavorImplement 和渠道自定义代码的方法来实现了。但是,如果不想建那么多渠道代码目录,需要通过一套代码来初始化模块,那么上述方法就不行了。

那么如何在不依赖模块的前提下动态调用其功能呢?

有几种方法:

1、反射、接口隔离和工厂模式

2、动态功能模块(Dynamic Feature Module),国内不适用。

3、利用 Java 的 ServiceLoader 机制,通过配置文件声明接口实现类。

综合考虑后决定采用反射、接口隔离和工厂模式。

实现

环境:agp 8.7.0,grade:8.10.2

gradle:实现模块动态加载

采用yaml文件进行功能配置

1
2
3
4
5
6
7
8
9
10
#功能定义
features:
featureA:
#默认配置
enable: false
name: ':feature:featureA' #模块名字
#两个列表优先级最高,但不能同时包含一样的flavor
#如果enable为true,则建议使用disableFlavorsList。反之,亦然。
enableFlavorsList: [demo, companyA, companyB]
disableFlavorsList: []

libs.versions.toml

1
2
snakeyaml = "1.33"
snakeyaml = { module = "org.yaml:snakeyaml", version.ref = "snakeyaml" }

根build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
// 自定义 gradle 插件
dependencies {
//classpath(libs.plugin)
classpath libs.snakeyaml // 添加 YAML 解析库
}
}

plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.android.library) apply false
}

app build.gradle (根据配置动态依赖模块实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
//Yaml放在其他xxx.gradle import会报找不到
import org.yaml.snakeyaml.Yaml
ext {
loadYamlConfig = { String fileName ->
def configFile = file(fileName)
if (!configFile.exists()) {
throw new GradleException("YAML config file not found: ${configFile.absolutePath}")
}
def yaml = new Yaml()
def config
try {
configFile.withReader { reader ->
config = yaml.load(reader)
}
} catch (Exception e) {
throw new GradleException("Failed to parse YAML file: ${configFile.name}", e)
}
return config.asImmutable()
}
}


def printlnRed(String msg) {
def ANSI_RESET = "\u001B[0m"
def ANSI_RED = "\u001B[31m"
println("${ANSI_RED}${msg}${ANSI_RESET}")
}

def featureConfig = loadYamlConfig("feature_config.yaml")
println("featureConfig: ${featureConfig}")
afterEvaluate {
android.applicationVariants.configureEach { variant ->
def flavorCompanyName = variant.productFlavors.find { it.dimension == "COMPANY" }?.name
if (!flavorCompanyName) {
throw new GradleException("Variant ${variant.name} has no COMPANY flavor!")
}
println("variant.name:${variant.name} variant.flavorName:${variant.flavorName}")
featureConfig.features.each { featureKey, defaultConfig ->
def featureName = defaultConfig.name
boolean isEnabled = defaultConfig.enable
def enableFlavorsList = defaultConfig.enableFlavorsList
def disableFlavorsList = defaultConfig.disableFlavorsList
boolean hasFound = false
for (flavor in enableFlavorsList) {
if (flavor == flavorCompanyName) {
isEnabled = true
println("enable in enableFlavorsList")
hasFound = true
break
}
}
for (flavor in disableFlavorsList) {
if (flavor == flavorCompanyName) {
if (hasFound) {
throw new GradleException("Feature $featureKey in flavor $flavorCompanyName must not be in both enableFlavorsList and disableFlavorsList!")
}
isEnabled = false
printlnRed("disable in disableFlavorsList")
break
}
}
if (isEnabled) {
println("${flavorCompanyName}Implementation ${featureName}")
//动态依赖模块
dependencies.add("${flavorCompanyName}Implementation", project(featureName))
} else {
printlnRed("${variant.name} ${flavorCompanyName} Not Implementation ${featureName}")
}
}
}
}

模块实现

公共模块

定义接口

1
2
3
interface IFeature {
fun doSomething()
}

定义一个 Helper

1
2
3
4
5
6
7
8
9
10
11
object FeatureHelper : IFeature {
private var mFeature: IFeature? = null

fun setFeatureImpl(feature: IFeature?) {
mFeature = feature
}

override fun doSomething() {
mFeature?.doSomething()
}
}

功能模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Module {

/**
采用java类 方便使用 java 的方式进行反射
* 注意:包名和类名、方法名和参数不能随意修改,否则反射会找不到
* @param context Context
* @return IFeature
*/
public static IFeature load(Context context) {
//FeatureManager实现IFeature接口
FeatureManager manager = new FeatureManager(context);
manager.init();
return manager;
}
}

app 模块

Java反射的优雅使用 - Wesley’s Blog

1
2
3
4
5
6
7
8
9
10
11
12
private fun loadFeatureModule(application: Application): IFeature? {
return try {
Reflector.on("com.wesley.feature.Module")
.method("load", Context::class.java).call<IFeature>(application)
} catch (e: Exception) {
null // 模块未启用或未实现
}
}

fun initModules(application: Application) {
FeatureHelper.setFeatureImpl(loadFeatureModule(application))
}

参考

Android 模块解耦和的实践

Android 中使用 ServiceLoader、AutoService 摔坑记录


安卓组件化之动态依赖功能模块
https://iwesley.top/article/67ca89d4/
作者
Wesley
发布于
2025年3月3日
许可协议