Android開發(fā)設(shè)計(jì)nowinandroid構(gòu)建腳本學(xué)習(xí)
引言
nowinandroid 項(xiàng)目是谷歌開源的示例項(xiàng)目,它遵循 Android 設(shè)計(jì)和開發(fā)的最佳實(shí)踐,并旨在成為開發(fā)人員的有用參考
這個(gè)項(xiàng)目在架構(gòu)演進(jìn),模塊化方案,單元測(cè)試,Jetpack Compose,啟動(dòng)優(yōu)化等多個(gè)方面都做了很好的示例,的確是一個(gè)值得學(xué)習(xí)的好項(xiàng)目
今天我們來(lái)學(xué)習(xí)一下 nowinandroid 項(xiàng)目的構(gòu)建腳本,看一下都有哪些值得學(xué)習(xí)的地方
gradle.properties 中的配置
要看一個(gè)項(xiàng)目的構(gòu)建腳本,我們首先看一下 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 項(xiàng)目主要做了以下幾個(gè)配置
- 開啟配置階段緩存
- 開啟
androidX
,并且移除了Jetifier
- 關(guān)閉
R
文件傳遞 - 關(guān)閉
build features
前面3個(gè)配置之前都介紹過,我們來(lái)看一下關(guān)閉 build features
AGP 4.0.0 引入了一種新方法來(lái)控制您要啟用和停用哪些構(gòu)建功能,如ViewBinding
,BuildConfig
。
我們可以在 gradle.properties 中全局開啟或關(guān)閉某些功能,也可以在模塊級(jí) build.gradle 文件中為每個(gè)模塊設(shè)置相應(yīng)的選項(xiàng),如下所示:
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. } }
通過停用不需要的構(gòu)建可能,可以提升我們的構(gòu)建性能,比如我們最熟悉的BuildConfig
,每個(gè)模塊都會(huì)生成這樣一個(gè)類,但其實(shí)我們?cè)诮^大多數(shù)情況下是用不到的,因此其實(shí)可以將其默認(rèn)關(guān)閉(在 AGP 8.0 中 BuildConfig 生成已經(jīng)變成默認(rèn)關(guān)閉了)
自動(dòng)安裝 git hook
有時(shí)我們會(huì)添加一些 git hook,用于在代碼提交或者 push 時(shí)做一些檢查
但使用 git hook 的一個(gè)問題在于,每次拉取新項(xiàng)目之后,都需要手動(dòng)安裝一下 git hook,這一點(diǎn)常常容易被忘記
那么有沒有什么辦法可以自動(dòng)安裝 git hook 呢?nowinandroid 項(xiàng)目提供了一個(gè)示例
// 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 } }
其實(shí)原理很簡(jiǎn)單,在settings.gradle.kts
中添加以上代碼,這樣在 Gradle 同步時(shí),就會(huì)自動(dòng)判斷 git hook 有沒有被安裝,如果沒有被安裝則自動(dòng)安裝
使用 includeBuild 而不是 buildSrc
pluginManagement { includeBuild("build-logic") repositories { google() mavenCentral() gradlePluginPortal() } }
為了支持在不同的模塊間共享構(gòu)建邏輯,此前我們常常會(huì)添加一個(gè) buildSrc 模塊
但是 buildSrc 模塊的問題在于每次發(fā)生修改都會(huì)導(dǎo)致項(xiàng)目的絕大多數(shù)緩存失效,從而導(dǎo)致構(gòu)建速度變得極慢
因此官方現(xiàn)在更推薦我們使用 includeBuild,比如 nowinandroid 的構(gòu)建邏輯就通過 includeBuild 放在了 build-logic
目錄
如何復(fù)用 build.gradle 代碼?
其實(shí)我們項(xiàng)目中的各個(gè)模塊的 build.gradle 中的代碼,大部分是重復(fù)的,做的都是一些重復(fù)的配置,當(dāng)要修改時(shí)就需要一個(gè)一個(gè)去修改了
nowinandroid 通過抽取重復(fù)配置的方式大幅度的減少了 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 的一個(gè) feature 模塊,可以看出除了每個(gè)模塊不同的namespace
與各個(gè)模塊的依賴之外,其他的內(nèi)容都抽取到nowinandroid.android.feature
等插件中去了,而這些插件的代碼都存放在build-logic
目錄中,通過 includeBuild 引入,大家可自行查看
總得來(lái)說(shuō),通過這種方式可以大幅減少重復(fù)配置代碼,當(dāng)配置需要遷移時(shí)也更加方便
使用 Version Catalog 管理依賴
在 build.gradle 中添加依賴有以下幾個(gè)痛點(diǎn)
- 項(xiàng)目依賴統(tǒng)一管理,在單獨(dú)文件中配置
- 不同Module中的依賴版本號(hào)統(tǒng)一
- 添加依賴時(shí)支持代碼提示
針對(duì)這幾種需求,Gradle7.0 推出了一個(gè)新的特性,使用 Version Catalog 統(tǒng)一依賴版本,它支持以下特性:
- 對(duì)所有 module 可見,可統(tǒng)一管理所有module的依賴
- 支持聲明依賴bundles,即總是一起使用的依賴可以組合在一起
- 支持版本號(hào)與依賴名分離,可以在多個(gè)依賴間共享版本號(hào)
- 支持在單獨(dú)的libs.versions.toml文件中配置依賴
- 支持代碼提示(僅 kts)
noinandroid 中目前已經(jīng)全面啟用了 Version Catalog,如上所示,統(tǒng)一依賴版本,支持代碼提示,體驗(yàn)還是不錯(cuò)的
關(guān)于 Version Catalog 的具體使用可以查看:【Gradle7.0】依賴統(tǒng)一管理的全新方式,了解一下~
代碼格式檢查
nowinandroid 作為一個(gè)開源項(xiàng)目,不可避免地會(huì)有第三方貢獻(xiàn)一些代碼,因此也需要在代碼合并前做一些格式檢查,保證代碼風(fēng)格的統(tǒng)一
nowinandroid 通過 spotless 來(lái)檢查代碼格式,主要是通過兩種方式觸發(fā)
- 通過上面提到的 git hook,在代碼 push 時(shí)觸發(fā)檢查
- 通過 github workflow,在代碼 push 到 main 分支時(shí)觸發(fā)檢查
上面兩種方式都會(huì)調(diào)用以下命令
./gradlew spotlessCheck --init-script gradle/init.gradle.kts --no-configuration-cache --stacktrace
可以看出,這里主要是執(zhí)行 spotlessCheck 任務(wù),并且指定了 init-script,我們來(lái)看一下 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"), "(<[^!?])") } } } }
可以看出,這里指定了對(duì)于 kotlin , kts , xml 等文件的格式要求,比如 kotlin 代碼需要遵守 ktlint 規(guī)范,并且文件開頭必須是 license 聲明
自定義 lint 檢查
除了代碼風(fēng)格的統(tǒng)一,nowinandroid 項(xiàng)目還自定義了一些 lint 檢查,跟 spoltess 一樣,也是通過 git hook 與 github workflow 兩種方式觸發(fā),兩種方式都會(huì)觸發(fā)以下代碼
./gradlew lintDemoDebug --stacktrace
nowinandroid 中有一個(gè)自定義的 lint 模塊,自定義 lint 規(guī)則就定義在這里,如下所示:
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" ) } } }
總得來(lái)說(shuō),這個(gè)自定義規(guī)則是檢查是否使用了 Compose 的默認(rèn) Material 組件而沒有使用 nowinandroid 封裝好的組件,如果檢查不通過則會(huì)拋出異常,提醒開發(fā)者修改
總結(jié)
本文主要介紹了 nowinandroid 項(xiàng)目構(gòu)建腳本中的一系列小技巧,具體包括以下內(nèi)容
- gradle.properties 中的配置
- 自動(dòng)安裝 git hook
- 使用 includeBuild 而不是 buildSrc
- 如何復(fù)用 build.gradle 代碼?
- 使用 Version Catalog 管理依賴
- 代碼格式檢查
- 自定義 lint 檢查
希望對(duì)你有所幫助~
項(xiàng)目地址
以上就是Android開發(fā)設(shè)計(jì)nowinandroid構(gòu)建腳本學(xué)習(xí)的詳細(xì)內(nèi)容,更多關(guān)于Android nowinandroid 構(gòu)建腳本的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android獲取點(diǎn)擊屏幕的位置坐標(biāo)
這篇文章主要為大家詳細(xì)介紹了Android獲取點(diǎn)擊屏幕的位置坐標(biāo),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05設(shè)計(jì)簡(jiǎn)單的Android圖片加載框架
這篇文章主要為大家詳細(xì)介紹了Android圖片加載框架的簡(jiǎn)單設(shè)計(jì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Android使用MediaRecorder實(shí)現(xiàn)錄音及播放
這篇文章主要為大家詳細(xì)介紹了Android使用MediaRecorder實(shí)現(xiàn)錄音及播放,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android編程實(shí)現(xiàn)ViewPager多頁(yè)面滑動(dòng)切換及動(dòng)畫效果的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)ViewPager多頁(yè)面滑動(dòng)切換及動(dòng)畫效果的方法,以完整實(shí)例形式分析了ViewPager多頁(yè)面滑動(dòng)切換效果的布局及功能實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11Android開發(fā)應(yīng)用第一步 安裝及配置模擬器Genymotion
這篇文章主要介紹了Android開發(fā)應(yīng)用第一步,即安裝及配置模擬器Genymotion,感興趣的小伙伴們可以參考一下2015-12-12Android讀取手機(jī)通訊錄聯(lián)系人到自己項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了Android讀取手機(jī)通訊錄聯(lián)系人到自己項(xiàng)目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07Android 利用廣播監(jiān)聽usb連接狀態(tài)(變化情況)
這篇文章主要介紹了Android 利用廣播監(jiān)聽usb連接狀態(tài),需要的朋友可以參考下2017-06-0613問13答全面學(xué)習(xí)Android View繪制
這篇文章主要為大家詳細(xì)介紹了Android View繪制,13問13答幫助大家全面學(xué)習(xí)Android View繪制,感興趣的小伙伴們可以參考一下2016-03-03