github blog
qq: 2383518170
wx: lzyprime
λ:
倉庫地址: https://github.com/lzyprime/android_demos
本來想把compose版本分離成單獨分支:dev_compose; 但是后來發(fā)現(xiàn)與dev分支除了view層不太一樣,剩下的全是同樣代碼;甚至view層一些compose組件也全是一樣的。
model層里,對數(shù)據(jù)組織和封裝在頻繁的改動,想找到更合理易用的方式,比如對DataStore的提供和使用方式,已經(jīng)調(diào)整過好幾版,目前的仍不是滿意版本。
如果兩個分支,這部分代碼同步就很煩人。git sub module, git rebase, 手動復制。哪一個都不方便。
所以,把view以外公用的部分,抽成單獨的gradle module, compose的view部分也抽成一個module。然后在gradle腳本里配好依賴關系。
同時,將gradle腳本由 Groovy 遷到 KTS。
gradle kts
遷移完發(fā)現(xiàn)android官網(wǎng)文檔居然也提了這事。
好處
- 相比
groovy, 對kotlin更熟悉。腳本易讀性提高,對腳本中每一步在執(zhí)行什么,什么意思更容易掌握,點開看源碼和注釋。 - 更規(guī)范,去糖。
groovy為了腳本編寫便捷,提供了一堆簡便寫法,而很多其實是靠字符串解析,看是否符合規(guī)則,然后去調(diào)用真正的接口。 - 接口廢棄等提醒。
gradle即將廢棄接口,接口警告信息等等,都會向kotlin代碼一樣,直接突出顯示。 - 用
KTS版本去學習gradle的用法。之后就算是groovy版本的,也能看個大概,看著官網(wǎng)文檔和基礎語法也能寫的差不多??赡芎啽銓懛ú辉趺磿?,但是中規(guī)中矩的腳本能跑應該沒問題。
壞處
- 相比
groovy, 肯定還是簡陋,不完善。包括文檔里,常常會有只支持groovy的提示。
遷移過程
給gradle腳本文件名加上.kts后綴(如build.gradle -> build.gradle.kts), 然后sync一下, 解決所有報錯。每次最好只改一個文件,否則報錯難修。
- 字符串必須全是雙引號
- 函數(shù)調(diào)用加括號。如
classpath,implementation等等后面空格加字符串的,一般是函數(shù)調(diào)用。改成classpath(xxx)樣式 - 屬性值,如
minSdk,targetSdk,versionCode等等被做成了屬性。同時如果屬性為bool類型,名字會變成isXXX的形式。 -
tasks,ext,extra,buildSrc
tasks
每個task, 包含name:String, args:Map,configureClosure: Function. groovy提供了一堆簡便寫法,但最終肯定歸到這三部分。以task clean為例。
// groovy
task clean(type: Delete) {
delete rootProject.buildDir
}
如果點進去,會發(fā)現(xiàn)批到的是task(name:String),后邊部分都會當字符串處理。這就是groovy提供便捷寫法的方式之一,字符串解析。最后相當于:
// 偽代碼
task(
args: {"type": Delete::class},
name: "clean",
configureClosure: { // Delete
delete(rootProject.buildDir)
},
)
的確簡便寫法夠簡潔形象,就像聲明一個函數(shù)??墒遣豢丛创a之類的,誰知道是什么。
kotlin也提供了一堆簡便寫法,以incline function的形式,可以一層層點到最后。
ext問題
KTS 也有 ext函數(shù),但如果像之前在buildScript塊里寫,就會報錯。點進去就知道原因:
val org.gradle.api.Project.`ext`: org.gradle.api.plugins.ExtraPropertiesExtension get() =
(this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("ext") as org.gradle.api.plugins.ExtraPropertiesExtension
fun org.gradle.api.Project.`ext`(configure: Action<org.gradle.api.plugins.ExtraPropertiesExtension>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("ext", configure)
也就是嘗試把當前對象轉(zhuǎn)為ExtensionAware。在groovy中,buildScript是Project的方法,Project實現(xiàn)了ExtensionAware接口。在KTS里,buildScript 來自KotlinBuildScript抽象類, 是個ProjectDelegate,用委托的方式訪問Project, 往上找基類也的確是Project。
但是buildScript函數(shù)接收的是操作ScriptHandlerScope類型。
@Suppress("unused")
open fun buildscript(@Suppress("unused_parameter") block: ScriptHandlerScope.() -> Unit): Unit =
internalError()
// use:
buildscript { // this: ScriptHandlerScope
...
}
也就是說,代碼塊里的 this 是個 ScriptHandlerScope, 并沒有實現(xiàn)ExtensionAware。 所以ScriptHandlerScope as ExtensionAware失敗了。
這也是為什么ext在頂級塊里寫或者在allprojects塊里可以正常工作:
buildScript {...}
// this: ProjectDelegate
ext {
set("key", "value")
}
allprojects { // this: Project
ext {
set("k", "v")
}
}
tasks.register<Delete>("clean") {
rootProject.ext["key1"] // 指定Project
delete(rootProject.buildDir)
}
但這只是定義的時候,使用的話,同樣因為這種限制,要看清楚作用域,是否能轉(zhuǎn)為ExtensionAware,還要搞清楚是誰的。
同時受kotlin靜態(tài)語言的限制,想直接Project.ext.key1, 甚至Project.key1使用,是不可能的。就得Project.ext["key1"]。
tasks.register<Delete>("clean") {
val key1 = rootProject.ext["key1"] // 指定Project
delete(rootProject.buildDir)
}
但是在buildScript里這么寫又過不去。此時通過ExtensionAware.extentions.getByName("ext")還拿不到。其實在groovy中也是點不進去的,可以看看groovy怎么處理的,怎么達到動態(tài)語言的效果。
ext -> extra
所以這東西基本就廢了。然后提供了extra。
buildscript {
val gradleVersion by extra("7.0.1")
val kotlinVersion by extra{ "1.5.21" }
extra["activityVersion"] = "1.3.1"
extra["lifecycleVersion"] = "2.3.1"
}
// module project
val kotlinVersion: String by rootProject.extra
val activityVersion: String by rootProject.extra
val lifecycleVersion: String by rootProject.extra
如果通過委托屬性的方式獲取值。需要顯式聲明類型。源碼:
val ExtensionAware.extra: ExtraPropertiesExtension
get() = extensions.extraProperties
也就是說,其實和ext拿到的是一樣的,Project.ext其實就是在把ExtensionAware.extensions.extraProperties拋出去。
所以基礎的set get等仍然好使。額外添加了一堆委托屬性和函數(shù),方便創(chuàng)建獲取變量。
val kkk by extra(vvv):
// val kkk by extra(vvv)
operator fun <T> ExtraPropertiesExtension.invoke(initialValue: T): InitialValueExtraPropertyDelegateProvider<T> =
InitialValueExtraPropertyDelegateProvider.of(this, initialValue)
// InitialValueExtraPropertyDelegateProvider(extra, vvv)
class InitialValueExtraPropertyDelegateProvider<T>
private constructor(
private val extra: ExtraPropertiesExtension,
private val initialValue: T
) {
companion object {
fun <T> of(extra: ExtraPropertiesExtension, initialValue: T) =
InitialValueExtraPropertyDelegateProvider(extra, initialValue)
}
operator fun provideDelegate(thisRef: Any?, property: kotlin.reflect.KProperty<*>): InitialValueExtraPropertyDelegate<T> {
// 插入, 變量名(kkk) 作為key
extra.set(property.name, initialValue)
return InitialValueExtraPropertyDelegate.of(extra)
// InitialValueExtraPropertyDelegate(extra)
}
}
class InitialValueExtraPropertyDelegate<T>
private constructor(
private val extra: ExtraPropertiesExtension
) {
companion object {
fun <T> of(extra: ExtraPropertiesExtension) =
InitialValueExtraPropertyDelegate<T>(extra)
}
// 賦值操作。 kkk = nvvv -> extra.set(kkk, nvvv)
operator fun setValue(receiver: Any?, property: kotlin.reflect.KProperty<*>, value: T) =
extra.set(property.name, value)
// 取值操作。val nk = kkk -> val nk = extra.get(kkk)
@Suppress("unchecked_cast")
operator fun getValue(receiver: Any?, property: kotlin.reflect.KProperty<*>): T =
uncheckedCast(extra.get(property.name))
}
中規(guī)中矩的委托。val kkk: T by extra也是一樣:
operator fun ExtraPropertiesExtension.provideDelegate(receiver: Any?, property: KProperty<*>): MutablePropertyDelegate =
if (property.returnType.isMarkedNullable) NullableExtraPropertyDelegate(this, property.name)
else NonNullExtraPropertyDelegate(this, property.name)
private
class NonNullExtraPropertyDelegate(
private val extra: ExtraPropertiesExtension,
private val name: String
) : MutablePropertyDelegate {
override fun <T> getValue(receiver: Any?, property: KProperty<*>): T =
if (!extra.has(name)) cannotGetExtraProperty("does not exist")
else uncheckedCast(extra.get(name) ?: cannotGetExtraProperty("is null"))
override fun <T> setValue(receiver: Any?, property: KProperty<*>, value: T) =
extra.set(property.name, value)
private
fun cannotGetExtraProperty(reason: String): Nothing =
throw InvalidUserCodeException("Cannot get non-null extra property '$name' as it $reason")
}
getValue, setValue是根據(jù)變量類型做類型轉(zhuǎn)換。所以要寫類型,還要寫對。
buildSrc
另外完成共享的方式。在rootProject目錄下創(chuàng)建buildSrc文件夾,并創(chuàng)建build.gradle.kts。
/
|-buildSrc
|- src/main/kotlin/xxx.kt
|- build.gradle.kts
//buildSrc/build.gradle.kts
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
src/main/kotlin下的內(nèi)容在工程內(nèi)共享。所以可以把變量定義在這:
// src/main/kotlin/versions.kt
const val kotlinVersion = "1.5.30"
...
其他地方可以直接用。
好處是往gradle添加附加功能更方便,易于管理。
弊端就是變量如果放在這,IDE可視化的Project Structure識別失敗,就會一直提示有內(nèi)容可以更新。
module 管理
沒什么可講的。new 一個 module。 根據(jù)需要選擇類型。然后就是build.gradle.kts處理好依賴和構(gòu)建。settings.gradle.kts中include。
當 A_module 依賴 B_module:
// A_module build.gradle.kts
dependencies {
implementation(project(":B_module"))
更多具體操作可以看文檔。轉(zhuǎn)成KTS不就是為了文檔讀著更容易。