以下文章来源于程序员江同学 ,作者程序员江同学
专注于Android及大前端技术
nowinandroid 项目是谷歌开源的示例项目,它遵循 Android 设计和开发的最佳实践,并旨在成为开发人员的有用参考。
https://github.com/android/nowinandroid
今天我们来学习一下 nowinandroid 项目的构建脚本,看一下都有哪些值得学习的地方。
# 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
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 生成已经变成默认关闭了)。
// 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 有没有被安装,如果没有被安装则自动安装。
pluginManagement {
includeBuild("build-logic")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
因此官方现在更推荐我们使用 includeBuild,比如 nowinandroid 的构建逻辑就通过 includeBuild 放在了 build-logic 目录。
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)
}
总得来说,通过这种方式可以大幅减少重复配置代码,当配置需要迁移时也更加方便。
./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace
// 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 声明。
除了代码风格的统一,nowinandroid 项目还自定义了一些 lint 检查,跟 spoltess 一样,也是通过 git hook 与 github workflow 两种方式触发,两种方式都会触发以下代码。
./gradlew lintDemoDebug --stacktraceclass 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 封装好的组件,如果检查不通过则会抛出异常,提醒开发者修改。
https://github.com/android/nowinandroid
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!