Gradle插件开发:无需afterEvaluate自动注册NamedDomainObjectContainer实例
问题描述
我正在开发Gradle插件,定义了如下扩展:
abstract class MyExtension { abstract val inputFiles: ConfigurableFileCollection val names: Provider<List<String>> by lazy { // 读取输入文件以获取名称列表 inputFiles.elements.map { ... } } abstract val instances: NamedDomainObjectContainer<MyType> }
我希望确保names提供的每个名称,若未在instances中注册则自动注册,逻辑如下:
names.get().forEach { if (!instances.names.contains(it)) { instances.register(it) { // 此处添加额外配置 } } }
已知可使用project.afterEvaluate(),但该方式不被推荐,请问有什么替代实现方案?
解决方案
1. 利用Provider.onComplete监听值计算完成
Gradle 6.0+ 提供的Provider.onComplete可以在Provider的值首次计算完成后触发逻辑,结合NamedDomainObjectContainer.maybeCreate(自动处理已存在实例的情况),代码更简洁安全:
abstract class MyExtension { abstract val inputFiles: ConfigurableFileCollection abstract val instances: NamedDomainObjectContainer<MyType> val names: Provider<List<String>> = inputFiles.elements.map { elements -> // 读取输入文件解析名称列表的逻辑 elements.map { file -> /* 示例:file.nameWithoutExtension */ } }.also { provider -> provider.onComplete { namesResult -> namesResult.get().forEach { name -> instances.maybeCreate(name).apply { // 此处添加额外配置逻辑 // 例如:someProperty.set("default-value") } } } } }
onComplete仅在Provider的值成功计算后执行一次,避免重复触发。maybeCreate会自动返回已注册的实例(如果存在),无需手动判断contains。
2. 绑定到任务生命周期(推荐用于任务依赖场景)
如果你的插件中有依赖instances的任务,可将注册逻辑放在任务的doFirst阶段,确保在任务执行前完成实例注册,符合Gradle的增量构建原则:
// 定义插件中的任务类型 abstract class MyTask : DefaultTask() { @get:Input abstract val names: Provider<List<String>> @get:Internal abstract val instances: NamedDomainObjectContainer<MyType> @TaskAction fun execute() { // 任务执行逻辑,使用instances } } // 在插件中注册任务时绑定逻辑 class MyPlugin : Plugin<Project> { override fun apply(project: Project) { val myExtension = project.extensions.create<MyExtension>("myExtension") project.tasks.register<MyTask>("processInstances") { names = myExtension.names instances = myExtension.instances doFirst { names.get().forEach { name -> instances.maybeCreate(name).apply { // 额外配置 } } } } } }
这种方式确保逻辑仅在任务需要执行时触发,不会提前消耗构建资源。
3. 使用ProviderListener监听值读取事件
如果需要在names的值被任何地方读取时同步更新instances,可以注册ProviderListener:
abstract class MyExtension { abstract val inputFiles: ConfigurableFileCollection abstract val instances: NamedDomainObjectContainer<MyType> private val project: Project // 需在扩展初始化时注入Project实例 val names: Provider<List<String>> = inputFiles.elements.map { elements -> elements.map { /* 解析逻辑 */ } }.also { provider -> val listener = object : ProviderListener { override fun afterRead(provider: Provider<*>) { val currentNames = (provider as Provider<List<String>>).get() currentNames.forEach { name -> instances.maybeCreate(name).apply { // 额外配置 } } } } provider.addListener(listener) // 构建结束后移除监听器,避免内存泄漏 project.gradle.buildFinished { provider.removeListener(listener) } } }
注意:这种方式会在每次读取names值时触发,若names被多次读取需考虑重复执行的问题。
核心原则
- 避免使用
afterEvaluate:它会破坏Gradle的延迟配置特性,导致构建效率下降且难以调试。 - 优先使用Gradle提供的延迟API(
Provider、NamedDomainObjectContainer的标准方法),确保逻辑仅在必要时执行。 - 用
maybeCreate替代手动判断实例是否存在,简化代码同时保证一致性。
内容的提问来源于stack exchange,提问作者Kelvin Chung
相关产品推荐
相关产品推荐

