gradle迁到kts, 以及module管理

github blog
qq: 2383518170
wx: lzyprime

λ:

仓库地址: https://github.com/lzyprime/android_demos

本来想把compose版本分离成单独分支:dev_compose; 但是后来发现与dev分支除了view层不太一样,剩下的全是同样代码;甚至view层一些compose组件也全是一样的。

model层里,对数据组织和封装在频繁的改动,想找到更合理易用的方式,比如对DataStore的提供和使用方式,已经调整过好几版,目前的仍不是满意版本。

如果两个分支,这部分代码同步就很烦人。git sub module, git rebase, 手动复制。哪一个都不方便。

所以,把view以外公用的部分,抽成单独的gradle module, composeview部分也抽成一个module。然后在gradle脚本里配好依赖关系。

同时,将gradle脚本由 Groovy 迁到 KTS

gradle kts

gradle 官网文档

android 官网迁移文档

迁移完发现android官网文档居然也提了这事。

好处

  • 相比groovy, 对kotlin更熟悉。脚本易读性提高,对脚本中每一步在执行什么,什么意思更容易掌握,点开看源码和注释。
  • 更规范,去糖。groovy为了脚本编写便捷,提供了一堆简便写法,而很多其实是靠字符串解析,看是否符合规则,然后去调用真正的接口。
  • 接口废弃等提醒。gradle即将废弃接口,接口警告信息等等,都会向kotlin代码一样,直接突出显示。
  • KTS版本去学习gradle的用法。之后就算是groovy版本的,也能看个大概,看着官网文档和基础语法也能写的差不多。可能简便写法不怎么会,但是中规中矩的脚本能跑应该没问题。

坏处

  • 相比groovy, 肯定还是简陋,不完善。包括文档里,常常会有只支持groovy的提示。

迁移过程

gradle 迁移文档

gradle脚本文件名加上.kts后缀(如build.gradle -> build.gradle.kts), 然后sync一下, 解决所有报错。每次最好只改一个文件,否则报错难修。

  • 字符串必须全是双引号
  • 函数调用加括号。如classpath, implementation等等后面空格加字符串的,一般是函数调用。改成classpath(xxx)样式
  • 属性值,如 minSdk, targetSdk, versionCode等等被做成了属性。同时如果属性为bool类型,名字会变成isXXX的形式。
  • tasks, ext, extra, buildSrc

tasks

每个task, 包含name:Stringargs:MapconfigureClosure: Function. groovy提供了一堆简便写法,但最终肯定归到这三部分。以task clean为例。

// groovy
task clean(type: Delete) {
    delete rootProject.buildDir
}

如果点进去,会发现批到的是task(name:String),后边部分都会当字符串处理。这就是groovy提供便捷写法的方式之一,字符串解析。最后相当于:

// 伪代码
task(
    args: {"type": Delete::class}, 
    name: "clean", 
    configureClosure: { // Delete
        delete(rootProject.buildDir)
    },
)

的确简便写法够简洁形象,就像声明一个函数。可是不看源码之类的,谁知道是什么。

kotlin也提供了一堆简便写法,以incline function的形式,可以一层层点到最后。

gradle 任务文档

ext问题

KTS 也有 ext函数,但如果像之前在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)

也就是尝试把当前对象转为ExtensionAware。在groovy中,buildScriptProject的方法,Project实现了ExtensionAware接口。在KTS里,buildScript 来自KotlinBuildScript抽象类, 是个ProjectDelegate,用委托的方式访问Project, 往上找基类也的确是Project

但是buildScript函数接收的是操作ScriptHandlerScope类型。

@Suppress("unused")
open fun buildscript(@Suppress("unused_parameter") block: ScriptHandlerScope.() -> Unit): Unit =
        internalError()

// use:
buildscript { // this: ScriptHandlerScope
    ...
}

也就是说,代码块里的 this 是个 ScriptHandlerScope, 并没有实现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)
}

但这只是定义的时候,使用的话,同样因为这种限制,要看清楚作用域,是否能转为ExtensionAware,还要搞清楚是谁的。

同时受kotlin静态语言的限制,想直接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怎么处理的,怎么达到动态语言的效果。

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等仍然好使。额外添加了一堆委托属性和函数,方便创建获取变量。

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))
}

中规中矩的委托。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是根据变量类型做类型转换。所以要写类型,还要写对。

buildSrc

kotlin dsl plugin 文档

另外完成共享的方式。在rootProject目录下创建buildSrc文件夹,并创建build.gradle.kts

/
|-buildSrc
  |- src/main/kotlin/xxx.kt
  |- build.gradle.kts
//buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}

src/main/kotlin下的内容在工程内共享。所以可以把变量定义在这:

// src/main/kotlin/versions.kt
const val kotlinVersion = "1.5.30"
...

其他地方可以直接用。

好处是往gradle添加附加功能更方便,易于管理。

弊端就是变量如果放在这,IDE可视化的Project Structure识别失败,就会一直提示有内容可以更新。

module 管理

没什么可讲的。new 一个 module。 根据需要选择类型。然后就是build.gradle.kts处理好依赖和构建。settings.gradle.ktsinclude

当 A_module 依赖 B_module:

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

更多具体操作可以看文档。转成KTS不就是为了文档读着更容易。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容