⚠️本文为稀土技能社区首发签约文章,14天内制止转载,14天后未获授权制止转载,侵权必究!
前语
nowinandroid 项目是谷歌开源的示例项目,它遵从 Android 设计和开发的最佳实践,并旨在成为开发人员的有用参阅
这个项目在架构演进,模块化方案,单元测试,Jetpack Compose,发动优化等多个方面都做了很好的示例,的确是一个值得学习的好项目
今天咱们来学习一下 nowinandroid 项目的构建脚本,看一下都有哪些值得学习的地方
gradle.properties 中的装备
要看一个项目的构建脚本,咱们首要看一下 gradle.properties
# Enable configuration caching between builds.
org.gradle.unsafe.configuration-cache=true
android.useAndroidX=true
# Non-transitive R classes is recommended and is faster/smaller
android.nonTransitiveRClass=true
# Disable build features that are enabled by default,
# https://developer.android.com/studio/releases/gradle-plugin#buildFeatures
android.defaults.buildfeatures.buildconfig=false
android.defaults.buildfeatures.aidl=false
android.defaults.buildfeatures.renderscript=false
android.defaults.buildfeatures.resvalues=false
android.defaults.buildfeatures.shaders=false
能够看出,nowinandroid 项目首要做了以下几个装备
- 敞开装备阶段缓存
- 敞开
androidX
,而且移除了Jetifier
- 封闭
R
文件传递 - 封闭
build features
前面3个装备之前都介绍过,咱们来看一下封闭 build features
AGP 4.0.0 引入了一种新方法来控制您要启用和停用哪些构建功用,如ViewBinding
,BuildConfig
。
咱们能够在 gradle.properties 中大局敞开或封闭某些功用,也能够在模块级 build.gradle 文件中为每个模块设置相应的选项,如下所示:
android {
// The default value for each feature is shown below. You can change the value to
// override the default behavior.
buildFeatures {
// Determines whether to generate a BuildConfig class.
buildConfig = true
// Determines whether to support View Binding.
// Note that the viewBinding.enabled property is now deprecated.
viewBinding = false
// Determines whether to support Data Binding.
// Note that the dataBinding.enabled property is now deprecated.
}
}
经过停用不需求的构建或许,能够提高咱们的构建功能,比方咱们最了解的BuildConfig
,每个模块都会生成这样一个类,但其实咱们在绝大多数情况下是用不到的,因此其实能够将其默认封闭(在 AGP 8.0 中 BuildConfig 生成已经变成默认封闭了)
主动装置 git hook
有时咱们会添加一些 git hook,用于在代码提交或许 push 时做一些查看
但运用 git hook 的一个问题在于,每次拉取新项目之后,都需求手动装置一下 git hook,这一点常常容易被忘记
那么有没有什么办法能够主动装置 git hook 呢?nowinandroid 项目提供了一个示例
// settings.gradle.kts
val prePushHook = file(".git/hooks/pre-push")
val commitMsgHook = file(".git/hooks/commit-msg")
val hooksInstalled = commitMsgHook.exists()
&& prePushHook.exists()
&& prePushHook.readBytes().contentEquals(file("tools/pre-push").readBytes())
if (!hooksInstalled) {
exec {
commandLine("tools/setup.sh")
workingDir = rootProject.projectDir
}
}
其实原理很简单,在settings.gradle.kts
中添加以上代码,这样在 Gradle 同步时,就会主动判别 git hook 有没有被装置,假如没有被装置则主动装置
运用 includeBuild 而不是 buildSrc
pluginManagement {
includeBuild("build-logic")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
为了支撑在不同的模块间共享构建逻辑,此前咱们常常会添加一个 buildSrc 模块
可是 buildSrc 模块的问题在于每次产生修正都会导致项目的绝大多数缓存失效,然后导致构建速度变得极慢
因此官方现在更推荐咱们运用 includeBuild,比方 nowinandroid 的构建逻辑就经过 includeBuild 放在了 build-logic
目录
怎么复用 build.gradle 代码?
其实咱们项目中的各个模块的 build.gradle 中的代码,大部分是重复的,做的都是一些重复的装备,当要修正时就需求一个一个去修正了
nowinandroid 经过抽取重复装备的方法大幅度的减少了 build.gradle 中的代码,如下所示
plugins {
id("nowinandroid.android.feature")
id("nowinandroid.android.library.compose")
id("nowinandroid.android.library.jacoco")
}
android {
namespace = "com.google.samples.apps.nowinandroid.feature.author"
}
dependencies {
implementation(libs.kotlinx.datetime)
}
这是 nowinandroid 的一个 feature 模块,能够看出除了每个模块不同的namespace
与各个模块的依靠之外,其他的内容都抽取到nowinandroid.android.feature
等插件中去了,而这些插件的代码都存放在build-logic
目录中,经过 includeBuild 引入,大家可自行查看
总得来说,经过这种方法能够大幅减少重复装备代码,当装备需求迁移时也更加方便
运用 Version Catalog 办理依靠
在 build.gradle 中添加依靠有以下几个痛点
- 项目依靠一致办理,在独自文件中装备
- 不同Module中的依靠版本号一致
- 添加依靠时支撑代码提示
针对这几种需求,Gradle7.0 推出了一个新的特性,运用 Version Catalog 一致依靠版本,它支撑以下特性:
- 对一切 module 可见,可一致办理一切module的依靠
- 支撑声明依靠bundles,即总是一同运用的依靠能够组合在一同
- 支撑版本号与依靠名分离,能够在多个依靠间共享版本号
- 支撑在独自的libs.versions.toml文件中装备依靠
- 支撑代码提示(仅 kts)
noinandroid 中目前已经全面启用了 Version Catalog,如上所示,一致依靠版本,支撑代码提示,体验仍是不错的
关于 Version Catalog 的具体运用能够查看:【Gradle7.0】依靠一致办理的全新方法,了解一下~
代码格局查看
nowinandroid 作为一个开源项目,不可避免地会有第三方奉献一些代码,因此也需求在代码合并前做一些格局查看,确保代码风格的一致
nowinandroid 经过 spotless 来查看代码格局,首要是经过两种方法触发
- 经过上面提到的 git hook,在代码 push 时触发查看
- 经过 github workflow,在代码 push 到 main 分支时触发查看
上面两种方法都会调用以下指令
./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace
能够看出,这儿首要是执行 spotlessCheck 使命,而且指定了 init-script,咱们来看一下 init.gradle.kts 里面做了什么
// init.gradle.kts
rootProject {
subprojects {
apply<com.diffplug.gradle.spotless.SpotlessPlugin>()
extensions.configure<com.diffplug.gradle.spotless.SpotlessExtension> {
kotlin {
target("**/*.kt")
targetExclude("**/build/**/*.kt")
ktlint(ktlintVersion).userData(mapOf("android" to "true"))
licenseHeaderFile(rootProject.file("spotless/copyright.kt"))
}
format("kts") {
target("**/*.kts")
targetExclude("**/build/**/*.kts")
// Look for the first line that doesn't have a block comment (assumed to be the license)
licenseHeaderFile(rootProject.file("spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
}
format("xml") {
target("**/*.xml")
targetExclude("**/build/**/*.xml")
// Look for the first XML tag that isn't a comment (<!--) or the xml declaration (<?xml)
licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[^!?])")
}
}
}
}
能够看出,这儿指定了关于 kotlin , kts , xml 等文件的格局要求,比方 kotlin 代码需求恪守 ktlint 标准,而且文件开头必须是 license 声明
自定义 lint 查看
除了代码风格的一致,nowinandroid 项目还自定义了一些 lint 查看,跟 spoltess 一样,也是经过 git hook 与 github workflow 两种方法触发,两种方法都会触发以下代码
./gradlew lintDemoDebug --stacktrace
nowinandroid 中有一个自定义的 lint 模块,自定义 lint 规矩就定义在这儿,如下所示:
class DesignSystemDetector : Detector(), Detector.UastScanner {
override fun createUastHandler(context: JavaContext): UElementHandler {
return object : UElementHandler() {
override fun visitCallExpression(node: UCallExpression) {
val name = node.methodName ?: return
val preferredName = METHOD_NAMES[name] ?: return
reportIssue(context, node, name, preferredName)
}
override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression) {
val name = node.receiver.asRenderString()
val preferredName = RECEIVER_NAMES[name] ?: return
reportIssue(context, node, name, preferredName)
}
}
}
companion object {
@JvmField
val ISSUE: Issue = Issue.create(
id = "DesignSystem",
briefDescription = "Design system",
explanation = "This check highlights calls in code that use Compose Material " +
"composables instead of equivalents from the Now in Android design system " +
"module."
)
// Unfortunately :lint is a Java module and thus can't depend on the :core-designsystem
// Android module, so we can't use composable function references (eg. ::Button.name)
// instead of hardcoded names.
val METHOD_NAMES = mapOf(
"MaterialTheme" to "NiaTheme",
"Button" to "NiaFilledButton",
"OutlinedButton" to "NiaOutlinedButton",
// ...
)
val RECEIVER_NAMES = mapOf(
"Icons" to "NiaIcons"
)
fun reportIssue(
context: JavaContext, node: UElement, name: String, preferredName: String
) {
context.report(
ISSUE, node, context.getLocation(node),
"Using $name instead of $preferredName"
)
}
}
}
总得来说,这个自定义规矩是查看是否运用了 Compose 的默认 Material 组件而没有运用 nowinandroid 封装好的组件,假如查看不经过则会抛出异常,提醒开发者修正
总结
本文首要介绍了 nowinandroid 项目构建脚本中的一系列小技巧,具体包括以下内容
- gradle.properties 中的装备
- 主动装置 git hook
- 运用 includeBuild 而不是 buildSrc
- 怎么复用 build.gradle 代码?
- 运用 Version Catalog 办理依靠
- 代码格局查看
- 自定义 lint 查看
希望对你有所帮助~
项目地址
github.com/android/now…