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

2 阅读2分钟

安卓组件化之动态依赖功能模块 - 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 模块

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

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 摔坑记录