应使用 project.logger.error() 而非 throw new GradleException(),并在 afterEvaluate 阶段校验;前者不中断构建、保留上下文并支持多问题一次性发现,后者避免因 DSL 延迟求值导致误判。

插件里用 project.logger.error() 而不是 throw new GradleException()
直接 throw 会中断整个构建流程,掩盖真实配置上下文;而 project.logger.error() 可以在不终止执行的前提下,把错误定位到具体行号和插件名。比如你在自定义插件中检测到 minSdkVersion 小于 21,应该这样写:
if (extension.minSdkVersion < 21) {
project.logger.error("❌ [MyPlugin] minSdkVersion must be ≥ 21, but got: {}", extension.minSdkVersion)
project.logger.warn("? Hint: update 'myplugin { minSdkVersion = 21 }' in build.gradle")
}
这样用户同步时能看到红字错误 + 黄字提示,且构建继续运行,方便一次性发现多个问题。
在 afterEvaluate 阶段做校验,别在 apply 里硬检查
Gradle 的 DSL 是延迟求值的,apply 执行时 android {} 块可能还没被解析。如果你在 apply 里就去读 project.extensions.findByType(…),大概率拿到 null 或默认值,导致误报或漏报。
正确做法是把校验逻辑挪到 project.afterEvaluate 中:
project.afterEvaluate {
val androidExt = project.extensions.findByType(AppExtension::class.java)
if (androidExt != null && androidExt.compileSdkVersion < 33) {
project.logger.error("❌ [MyPlugin] compileSdkVersion < 33 is not allowed")
}
}
这样能确保所有 DSL 配置已加载完毕,校验结果可靠。
用 project.reportError() 替代手拼字符串(AGP 8.0+)
Android Gradle Plugin 8.0 引入了 project.reportError(),它会自动关联源码位置(比如 build.gradle 第 42 行),并支持结构化错误码,比纯日志更利于 IDE 解析和跳转。
使用条件:
- 你的插件依赖了 AGP 8.0+ 的 API(如
com.android.tools.build:gradle:8.5.1) - 调用时传入一个
ConfigurationProblem实例
示例:
val problem = ConfigurationProblem.create(
"MYPLUGIN_INVALID_TARGET_SDK",
"targetSdkVersion must match compileSdkVersion",
ProblemReporter.Type.ERROR,
project.file("build.gradle")
)
project.reportError(problem)
注意:这个 API 不会终止构建,但会被 Android Studio 的 Problems 工具窗口捕获并高亮显示,对新手友好度明显提升。
避免在闭包里抛异常——闭包外校验 + 闭包内标记
常见陷阱:在 myplugin { … } 闭包内部直接 throw,结果堆栈指向 Groovy 解析器,而不是你写的插件代码。
推荐模式是两阶段处理:
- 闭包执行时只收集配置(如
extension.targetSdk = 34) - 在
afterEvaluate中统一校验,出错时用project.logger.error()输出带上下文的提示
这样既保留了 DSL 的可读性,又让错误可定位、可复现。尤其当用户写了 myplugin { targetSdk = "34" }(字符串而非整数)时,你能在日志里明确指出:“第 15 行:targetSdk 必须为整数,当前值类型为 String”。
真正难的不是抛错,而是让错误信息自带上下文、位置和修复路径。很多人卡在构建失败后反复改配置却不知哪一行触发了哪条规则——这往往是因为插件没把 project.buildFile 和实际配置块的位置绑定起来。


















