安卓组件化之动态依赖功能模块 - Wesley's Blog
对于拥有不同渠道的 APP 来说,可能需要集成不同的功能模块,也可能同一个功能有不同实现。最简单的方法可能是通过接口、工厂模式、 flavorImplement 和渠道自定义代码的方法来实现了。但是,如果不想建那么多渠道代码目录,需要通过一套代码来初始化模块,那么上述方法就不行了。
那么如何在不依赖模块的前提下动态调用其功能呢?
有几种方法:
1、反射、接口隔离和工厂模式
2、动态功能模块(Dynamic Feature Module),国内不适用。
3、利用 Java 的 ServiceLoader
机制,通过配置文件声明接口实现类。
综合考虑后决定采用反射、接口隔离和工厂模式。
实现
环境:agp 8.7.0,grade:8.10.2
gradle:实现模块动态加载
采用yaml文件进行功能配置
#功能定义
features:
featureA:
#默认配置
enable: false
name: ':feature:featureA' #模块名字
#两个列表优先级最高,但不能同时包含一样的flavor
#如果enable为true,则建议使用disableFlavorsList。反之,亦然。
enableFlavorsList: [demo, companyA, companyB]
disableFlavorsList: []
libs.versions.toml
snakeyaml = "1.33"
snakeyaml = { module = "org.yaml:snakeyaml", version.ref = "snakeyaml" }
根build.gradle
// 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 (根据配置动态依赖模块实现)
//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}")
}
}
}
}
模块实现
公共模块
定义接口
interface IFeature {
fun doSomething()
}
定义一个 Helper
object FeatureHelper : IFeature {
private var mFeature: IFeature? = null
fun setFeatureImpl(feature: IFeature?) {
mFeature = feature
}
override fun doSomething() {
mFeature?.doSomething()
}
}
功能模块
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 模块
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))
}