gradle遷到kts, 以及module管理

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, composeview部分也抽成一個module。然后在gradle腳本里配好依賴關系。

同時,將gradle腳本由 Groovy 遷到 KTS。

gradle kts

gradle 官網(wǎng)文檔

android 官網(wǎng)遷移文檔

遷移完發(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 遷移文檔

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:Stringargs: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的形式,可以一層層點到最后。

gradle 任務文檔

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中,buildScriptProject的方法,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

kotlin dsl plugin 文檔

另外完成共享的方式。在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.ktsinclude。

當 A_module 依賴 B_module:

// A_module build.gradle.kts
dependencies {
    implementation(project(":B_module"))

更多具體操作可以看文檔。轉(zhuǎn)成KTS不就是為了文檔讀著更容易。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容