本文最后更新于: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' 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
| buildscript { dependencies { classpath libs.snakeyaml } }
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
| 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 {
public static IFeature load(Context context) { 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 摔坑记录