日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

從 SQLlin 的更新看 Kotlin Multiplatform 技術(shù)更迭

來源: 責(zé)編: 時間:2024-01-19 17:27:38 294觀看
導(dǎo)讀作者簡介禹昂,攜程移動開發(fā)專家,Google 開發(fā)者專家(Android),上海 Kotlin User Group 組織者,圖書《Kotlin 編程實踐》譯者。2022 年底,我們在攜程的 Github organization 下開源了 SQLlin,SQLlin 是一款基于 Kotlin DSL 及 K

作者簡介kYg28資訊網(wǎng)——每日最新資訊28at.com

禹昂,攜程移動開發(fā)專家,Google 開發(fā)者專家(Android),上海 Kotlin User Group 組織者,圖書《Kotlin 編程實踐》譯者。kYg28資訊網(wǎng)——每日最新資訊28at.com

2022 年底,我們在攜程的 Github organization 下開源了 SQLlin,SQLlin 是一款基于 Kotlin DSL 及 KSP 技術(shù)的,支持眾多平臺的 Kotllin Multipllatform SQLite 數(shù)據(jù)庫框架。感興趣且不了解 SQLlin 的讀者可以參考:《攜程機(jī)票跨端 Kotlin DSL 數(shù)據(jù)庫框架 SQLlin》一文。kYg28資訊網(wǎng)——每日最新資訊28at.com

SQLlin作為攜程機(jī)票移動端團(tuán)隊最為完備的一款開源項目,在接近 1 年的時間內(nèi)經(jīng)歷了不少升級與換血式的更新,也見證了這一年 Kotlin Multiplatform 技術(shù)的演進(jìn)及社區(qū)生態(tài)的變化。本文將帶領(lǐng)大家梳理這些更新,并探求這些更新背后所涉及到的 Kotlin Multiplatform 技術(shù)棧在這一年來的更迭與進(jìn)化。kYg28資訊網(wǎng)——每日最新資訊28at.com

一、重寫 native 驅(qū)動層

我們先來回顧一下最初的 SQLlin 架構(gòu)圖:kYg28資訊網(wǎng)——每日最新資訊28at.com

圖片kYg28資訊網(wǎng)——每日最新資訊28at.com

最初,SQLlin 在 Kotlin/Native 平臺上基于開源項目 SQLiter(見參考鏈接 1),目的是避免重復(fù)造輪子。雖然 SQLliter 是來自 Touchlab的優(yōu)秀開源項目,但最近一年維護(hù)更新緩慢。在本文撰寫時,SQLiter 于 2023 年 11 月發(fā)布了 1.3.0 和 1.3.1 兩個版本(1.3.1升級到了 Kotlin 1.9.21,用于修復(fù) 1.9.20 的 Kotlin/Native 庫版本號相關(guān)的問題)。但在這之前的版本,即 1.2.1 發(fā)布于 2022年 8 月,基于 Kotlin 1.6.20,一年以上沒有更新。對于 2023 年的項目來說,1.6.20 過于老舊。老舊的版本導(dǎo)致了如下一些問題。kYg28資訊網(wǎng)——每日最新資訊28at.com

1.1 Targets 更新維護(hù)不及時kYg28資訊網(wǎng)——每日最新資訊28at.com

Kotlin 在 1.8.20 版本廢棄了一眾 32 位 Kotlin/Native targets(目標(biāo)平臺),包括:iosArm32、watchosX86、wasm32、mingwX86、linuxArm32Hfp、linuxMips32、linuxMipsel32。這些目標(biāo)平臺幾乎已經(jīng)完全被淘汰,市面上已經(jīng)極少有可以運(yùn)行這些targets 的設(shè)備,繼續(xù)支持已無意義。因此 Kotlin 決定將這些 targets 標(biāo)記為“deprecated”,并在 1.9.20 版本將它們完全移除。kYg28資訊網(wǎng)——每日最新資訊28at.com

這些即將被移除的 targets 中,iosArm32、watchosX86、mingwX86 受到 SQLiter 及 SQLlin 的支持。由于 SQLiter 不更新版本,所以這些 targets 將繼續(xù)存在于 SQLiter 當(dāng)中,雖然 sqllin-driver 可以在上層移除對這些平臺的支持,但長久來說由于編譯器版本的更迭,仍然不是最佳做法。kYg28資訊網(wǎng)——每日最新資訊28at.com

如果說在 sqllin-driver 中移除對舊編譯目標(biāo)的支持可以暫時解決“廢棄舊 targets 不及時”的問題,那么“對新 targets 的支持”則無計可施。kYg28資訊網(wǎng)——每日最新資訊28at.com

Kotlin 在 1.8.0 版本開始支持 watchosDeviceArm64 新目標(biāo)平臺,對應(yīng)于全新的 64 位 Apple Watch 設(shè)備。雖然可以預(yù)見使用 Kotlin Multiplatform 技術(shù)開發(fā) Apple Watch 應(yīng)用的開發(fā)者不會很多,但 SQLlin 原本支持所有的 watchOS 相關(guān) targets,不支持最新的 Arm64 架構(gòu)并不合理。由于 SQLiter 不支持 watchosDeviceArm64,因此 SQLlin 也無法支持。kYg28資訊網(wǎng)——每日最新資訊28at.com

1.2 Bug 無法及時修復(fù)kYg28資訊網(wǎng)——每日最新資訊28at.com

在 SQL 中我們會遇到一個常見的用法——join,在 join 查詢時遇到兩個表擁有相同名字的列也是常見現(xiàn)象。在 SQLiter的原始實現(xiàn)中,后查詢出來的同名列值會覆蓋掉先查詢出來的同名列值:kYg28資訊網(wǎng)——每日最新資訊28at.com

override val columnNames: Map<String, Int> by lazy {    val map = HashMap<String, Int>(this.columnCount)    for (i in 0 until columnCount) {        val key = columnName(i)        if (map.containsKey(key)) {            var index = 1            val basicKey = "$key&JOIN"            var finalKey = basicKey + index            while (map.containsKey(finalKey)) {                finalKey = basicKey + ++index            }            map[finalKey] = i        } else {            map[key] = i        }    }    map}

之后,我于 2022 年 12 月提交了一個 PR 以修復(fù)此問題(參考鏈接 2),但 SQLliter 的維護(hù)者沒有任何回復(fù),同樣是直到 2023 年 11 月才合并該 PR。kYg28資訊網(wǎng)——每日最新資訊28at.com

無法支持的新平臺導(dǎo)致有剛需的用戶無法繼續(xù)使用 SQLlin,而無法修復(fù)的問題導(dǎo)致了特定場景必定出錯的硬傷。一年沒有任何維護(hù)讓我對 SQLiter 感到疑慮,此時自行實現(xiàn)已經(jīng)變成了必然選擇。kYg28資訊網(wǎng)——每日最新資訊28at.com

1.3 Native 驅(qū)動層重寫kYg28資訊網(wǎng)——每日最新資訊28at.com

重寫 Native 驅(qū)動層并不困難,我們可以參考 SQLiter 的不少設(shè)計理念。kYg28資訊網(wǎng)——每日最新資訊28at.com

首先,SQLite 在不同的 Native 平臺上都提供相同的 C API,所以我們絕大部分代碼是平臺(這里特指 Kotlin/Native 的諸多目標(biāo)平臺)無關(guān)的。根據(jù)官方 KMP 工程的架構(gòu)約定,這部分平臺無關(guān)的代碼可以全部放在 nativeMain source set 下。但由于我們構(gòu)建了一套面向?qū)ο箫L(fēng)格的 API,加上需要處理例如線程同步等問題,因此還是會依賴一些系統(tǒng)平臺 API。比如說如果要在 nativeMain 中使用線程鎖,需要用 expect 關(guān)鍵字定義待實現(xiàn)的API,在各平臺相關(guān) source set 中使用 actual 關(guān)鍵字定義相關(guān)實現(xiàn)。比如說在 Apple 平臺上我們使用 Apple Foundation 中的 Objective-C 類 NSRecursiveLock,而在 Linux 和Windows 平臺上則使用 Posix C 中的 pthread_mutex_xxx 系列 C API。kYg28資訊網(wǎng)——每日最新資訊28at.com

我們將 SQLite 的 C 庫頭文件放在 include 路徑下(與 nativeMain 平級),然后編寫 .def 文件并放在 nativeInterop 路徑下(同樣與 nativeMain 平級),然后在 build.gradle.kts 文件中配置頭文件的路徑以及 SQLite C 庫的 linkerOpts(編譯鏈接參數(shù)),即可在所有 native 相關(guān)的 sourceSet 中調(diào)用 SQLite C 函數(shù),build.gradle.kts 中的配置如下:kYg28資訊網(wǎng)——每日最新資訊28at.com

fun KotlinNativeTarget.setupNativeConfig() {    val main by compilations.getting    val sqlite3 by main.cinterops.creating {        includeDirs("$projectDir/src/include")    }    binaries.all {        linkerOpts += when {            HostManager.hostIsLinux -> listOf("-lsqlite3", "-L$rootDir/libs/linux", "-L/usr/lib/x86_64-linux-gnu", "-L/usr/lib", "-L/usr/lib64")            HostManager.hostIsMingw -> listOf("-Lc://msys64//mingw64//lib", "-L$rootDir//libs//windows", "-lsqlite3")            else -> listOf("-lsqlite3")        }    }}

這是一個 native 目標(biāo)平臺可調(diào)用的擴(kuò)展函數(shù),使所有 native targets 都調(diào)用它即可。其中 linkerOpts 在 Linux 和 Windows 平臺上都指向常見的 SQLite 安裝路徑(使用常見的包管理器),但為了確保 native 單元測試可以順利在任何 Linux 或 Windows host 上運(yùn)行,SQLlin 的源碼目錄中實際上附帶了針對 Linux 及 Windows 的 SQLite .a 庫,因此當(dāng)鏈接過程無法在常見路徑下找到 SQLite .a文件時,最終會鏈接到 SQLlin 源碼路徑下的版本。kYg28資訊網(wǎng)——每日最新資訊28at.com

但再次強(qiáng)調(diào),以上場景僅限單元測試,如果你是使用 SQLlin 的應(yīng)用開發(fā)者,且你的應(yīng)用支持 Linux 和 Windows,需要確保用戶的電腦安裝了SQLite,或者在應(yīng)用程序工程中附帶 SQLite C 庫,并自行添加 linkerOpts 鏈接到 SQLite .a 文件。至于 Apple 相關(guān)平臺(iOS、macOS、watchOS、tvOS),系統(tǒng)框架中已經(jīng)自帶了SQLite,因此不必?fù)?dān)心以上問題,sqllin-driver 中添加的編譯鏈接參數(shù)可以正確鏈接到系統(tǒng)框架中自帶的版本。最后我們來看一下 nativeMain 下的源碼結(jié)構(gòu):kYg28資訊網(wǎng)——每日最新資訊28at.com

圖片kYg28資訊網(wǎng)——每日最新資訊28at.com

cinterop 包包含所有對 SQLite C 函數(shù)直接互操作的代碼,通過單獨(dú)的包將其與其它代碼隔離;platform 包則存放所有待平臺實現(xiàn)的相關(guān)代碼,真正的實現(xiàn)則位于 appleMain、linuxMain、mingwMain 幾個 source sets 中;其余代碼是 sqllin-driver-native 的核心實現(xiàn),都位于根目錄包下。kYg28資訊網(wǎng)——每日最新資訊28at.com

二、JVM Target 支持

起初,根據(jù)預(yù)測,我認(rèn)為使用 Kotlin Multiplatform 技術(shù)開發(fā) JVM 桌面應(yīng)用的人并不多。但由于 Compose Multiplatform 最初支持的平臺便是 Android 與 JVM,因此吸引了大量 Kotlin Multiplatform 開發(fā)者將自己的多平臺應(yīng)用的支持范圍擴(kuò)展到 JVM。在部分用戶提交了一些 issue(參考鏈接 3)后,我決定著手進(jìn)行 JVM 平臺的支持工作。而支持 JVM 平臺也有助于調(diào)研將 SQLlin 支持的數(shù)據(jù)庫擴(kuò)展到 MySQL、H2、Oracle 等后端數(shù)據(jù)庫的可能性,因為它們都基于 JDBC。kYg28資訊網(wǎng)——每日最新資訊28at.com

JVM 平臺的實現(xiàn)基于 SQLite 官方的 JVM driver:sqlite-jdbc,庫的使用者通過 JDBC 連接到 sqlite-jdbc,而 sqlite-jdbc 底層則通過 JNI 操作 SQLite C 庫。由于 sqlite-jdbc本身就是 Java 庫,因此 API 的抽象程度比 native 平臺上直接調(diào)用 C API 高的多。所以 jvmMain 中的代碼實現(xiàn)比 nativeMain 要簡單很多。kYg28資訊網(wǎng)——每日最新資訊28at.com

但也有幾個點(diǎn)值得一提:kYg28資訊網(wǎng)——每日最新資訊28at.com

首先,Windows平臺上的文件路徑分隔符是 ‘/’,而 Linux 和 macOS 上都是 ‘/’,因此在處理用戶傳入的路徑參數(shù)時,即使是在 jvmMain 中也要判斷當(dāng)前運(yùn)行的操作系統(tǒng)是不是 Windows。kYg28資訊網(wǎng)——每日最新資訊28at.com

其次,由于sqlite-jdbc 中沒有對 sqlite3_config C 函數(shù)的調(diào)用,因此目前 lookasideSlotSize 和 lookasideSlotCount 兩個參數(shù)在 JVM 平臺上無法生效,后續(xù)我計劃通過提交 PR 的方式參與sqlite-jdbc 的開發(fā),使其支持 sqlite3_config,但目前還沒有具體的時間表。kYg28資訊網(wǎng)——每日最新資訊28at.com

當(dāng)然,支持 JVM 平臺的開發(fā)過程還遇到過其他的細(xì)節(jié)問題,例如表示查詢結(jié)果集的 java.sql.ResultSet 類型起始下標(biāo)是 1 而不是 Android 平臺 android.database.Cursor 和 Native 平臺 C API 中的 0。不過這類問題都較為容易處理,在此不多做贅述。kYg28資訊網(wǎng)——每日最新資訊28at.com

在重寫了 native 平臺的 driver 和支持了 JVM 平臺后,SQLlin 的架構(gòu)圖如下所示:kYg28資訊網(wǎng)——每日最新資訊28at.com

圖片kYg28資訊網(wǎng)——每日最新資訊28at.com

目前 SQLlin 支持的完整目標(biāo)平臺列表如下:kYg28資訊網(wǎng)——每日最新資訊28at.com

  • Multiplatform Common
  • Android (6.0+)
  • JVM (Java 11+, since 1.2.0)
  • iOS (x64, arm64, simulatorArm64)
  • macOS (x64, arm64)
  • watchOS (x64, arm32, arm64, simulatorArm64, deviceArm64)
  • tvOS (x64, arm64, simulatorArm64)
  • Linux (x64, arm64)
  • Windows (mingwX64)

三、sqllin-dsl 并發(fā)安全

sqllin-driver 作為低階 SQLite 框架,可以通過 SQLite 本身的線程安全機(jī)制來實現(xiàn)一定程度上的線程安全,我寫過一篇文章《關(guān)于 SQLite 多線程行為的結(jié)論》討論過相關(guān)知識。kYg28資訊網(wǎng)——每日最新資訊28at.com

簡而言之,在多數(shù)情況下 SQLite 的默認(rèn)線程模式都是:Multi-thread,在單連接多線程的情況下是可以保證線程安全的。因此我們只需盡量避免多連接多線程的情形即可,將同一個連接在多個線程間共享是個好方法。kYg28資訊網(wǎng)——每日最新資訊28at.com

現(xiàn)在我們來回顧一下 sqllin-dsl 的基本用法,以便理解本節(jié)接下來的內(nèi)容:kYg28資訊網(wǎng)——每日最新資訊28at.com

private val db by lazy { Database(name = "person.db", path = path, version = 1) }fun sample() {    val tom = Person(age = 4, name = "Tom")    val jerry = Person(age = 3, name = "Jerry")    val jack = Person(age = 8, name = "Jack")    val selectStatement: SelectStatement<Person> = db {        PersonTable { table ->            table INSERT listOf(tom, jerry, jack)            table UPDATE SET { age = 5; name = "Tom" } WHERE ((age LTE 5) AND (name NEQ "Tom"))            table DELETE WHERE ((age GTE 10) OR (name NEQ "Jerry"))            table SELECT WHERE (age LTE 5) GROUP_BY age HAVING (upper(name) EQ "TOM") ORDER_BY (age to DESC) LIMIT 2 OFFSET 1        }    }    selectStatement.getResult().forEach { person ->        println(person.name)    }}

在 sqllin-dsl 中,一個 Database 對象中只會建立一個數(shù)據(jù)庫鏈接。但上述示例中如果我們將對象 db(類型為 Database)在多個線程(或運(yùn)行在不同線程上的協(xié)程)中共享,幾乎必然會出現(xiàn)問題。kYg28資訊網(wǎng)——每日最新資訊28at.com

原因在于 Database 對象內(nèi)部使用一個雙向鏈表來進(jìn)行一組 SQL 語句的構(gòu)建,一個 Database 對象持有一個雙向鏈表,每次子句的連接都會直接拼接到鏈表頭部的 SQL語句上,而當(dāng) SQL 語句組執(zhí)行完畢后鏈表會被清空。kYg28資訊網(wǎng)——每日最新資訊28at.com

如果在多個線程/協(xié)程中同事使用 db 對象,可以想象這可能會出現(xiàn) SQL 語句拼接混亂的問題,例如線程 A 和 線程 B 都在構(gòu)建自己的SQL 語句,由于沒有同步機(jī)制,線程 B 中的子句可能被拼接到線程 A 中已經(jīng)創(chuàng)建出的 SQL 語句后面,造成 SQL 語法錯誤。也有可能出現(xiàn)線程 A 還在構(gòu)建 SQL 語句,但線程 B 已經(jīng)進(jìn)入SQL 語句執(zhí)行階段,線程 B 很可能會將還未構(gòu)建完成的 SQL 語句傳給 SQLite,造成運(yùn)行錯誤。kYg28資訊網(wǎng)——每日最新資訊28at.com

SQLlin 最初之所以沒有設(shè)計線程同步機(jī)制主要是基于 Kotlin 版本的考量。在 SQLlin 第一個版本發(fā)布的 Kotlin 1.7.20 時期,Kotlin/Native new Memory Management(新內(nèi)存管理器,后文簡稱 new MM)還未進(jìn)入正式版,不少開發(fā)者還在使用舊內(nèi)存管理器。在 Kotlin/Native 的舊內(nèi)存模型中,對象是不能直接跨線程訪問的,必須要手動進(jìn)行對象子圖分離和再綁定操作,對象才能將自己的所有權(quán)轉(zhuǎn)移到另一個線程,這種設(shè)計其實是強(qiáng)制開發(fā)者在編譯期就保證對象在同一時刻只能被一個線程訪問。kYg28資訊網(wǎng)——每日最新資訊28at.com

關(guān)于舊內(nèi)存模型在本人以往的文章中討論過很多次,并且在當(dāng)下 Kotlin 1.9.20 時代已經(jīng)被徹底淘汰,這里也不再過多討論。基于以上的時代背景,在不能確定用戶是否使用新內(nèi)存管理器的情況下,做線程同步的設(shè)計非常困難,因此最好的方式就是不處理,并且建議用戶不要在多線程間共享 Database 對象。但如今 2023 年末,在 Kotlin 1.9.2x 版本作為最新版本的背景下,new MM早已經(jīng)被絕大部分開發(fā)者所使用,因此此時基于 new MM 的設(shè)計進(jìn)行線程同步機(jī)制的開發(fā)非常合適。kYg28資訊網(wǎng)——每日最新資訊28at.com

在 sqllin-dsl 新版本的設(shè)計中,新增了掛起函數(shù) API suspendScope,用于在并發(fā)環(huán)境下取代 operator 函數(shù) invoke,并且管理 SQL 語句構(gòu)建的雙向鏈表被改成成員變量,只有在每次invoke 或 suspendScope 函數(shù)被調(diào)用時才創(chuàng)建,在 SQL 語句執(zhí)行完畢后會被就會被拋棄。由于函數(shù)調(diào)用棧是線程私有的,因此這樣的設(shè)計可以在不同的線程同時構(gòu)建 SQL語句時隔離運(yùn)行,既提高效率又保證了線程安全。kYg28資訊網(wǎng)——每日最新資訊28at.com

在 SQL 語句運(yùn)行階段,由于每次 SQL 語句構(gòu)建完畢后執(zhí)行的都是一組 SQL,為了避免不同線程同時執(zhí)行 SQL語句時的順序的不確定性,例如線程 A 需要執(zhí)行 SQL 語句 a、b、c,線程 B 需要執(zhí)行 SQL 語句 d、e、f,不加任何同步機(jī)制同時執(zhí)行可能會導(dǎo)致 a、b、c、d、e、f的執(zhí)行順序不確定,從而導(dǎo)致不可預(yù)知的問題,因此 SQL 語句執(zhí)行階段必須加入?yún)f(xié)程鎖 Mutex 來保證并發(fā)安全,suspendScope 的實現(xiàn)如下:kYg28資訊網(wǎng)——每日最新資訊28at.com

private val executiveMutex by lazy { Mutex() }public suspend infix fun <T> suspendedScope(block: suspend DatabaseScope.() -> T): T {    val databaseScope = DatabaseScope(databaseConnection, enableSimpleSQLLog)    val result = databaseScope.block()    executiveMutex.withLock {        databaseScope.executeAllStatements()    }    return result}

由于使用了協(xié)程鎖 Mutex,因此自 1.2.2 版本起, sqllin-dsl 依賴 Kotlin 官方協(xié)程框架 kotlinx.coroutines。kYg28資訊網(wǎng)——每日最新資訊28at.com

四、Android 低版本向下兼容

Android 系統(tǒng)曾在 API 28(Android 9)版本對 framework 中的 SQLite Java APIs 進(jìn)行了一次升級,這次升級提供了許多新 API 可以讓開發(fā)者對 SQLite進(jìn)行具體的參數(shù)配置,這些參數(shù)包括:日志模式、同步模式、連接超時時間、lookaside memory,這在之前的版本都是不可以的。由于 SQLlin 最低支持的Android 版本是 API 23(Android 6),因此在 Android 9 以下的設(shè)備上,以上提到的參數(shù)都無法生效。kYg28資訊網(wǎng)——每日最新資訊28at.com

但最初的認(rèn)知并不準(zhǔn)確,因為日志模式、同步模式兩個參數(shù)都使用 PRAGMA 語句配置,因此只需要在 sqllin_driver 內(nèi)自行構(gòu)建 PRAGMA 語句并執(zhí)行,即可在舊Android 系統(tǒng)上也能進(jìn)行日志模式與同步模式的設(shè)置。因此,自 1.2.0 版本起,SQLlin 在舊 Android 設(shè)備上也支持設(shè)置日志模式與同步模式。但基于 SQLite C API才能配置的連接超時時間和 lookaside memory 仍然無法在舊設(shè)備上生效。kYg28資訊網(wǎng)——每日最新資訊28at.com

五、CI/CD 優(yōu)化

在 SQLlin 開源之初沒有進(jìn)行 CI/CD 環(huán)境的搭建。CI/CD 對于驗證 push、PR 的準(zhǔn)確性,保證版本發(fā)布的 bug 率等方面具有重要意義。同時也是向 MavenCentral發(fā)布新版本的最佳途徑。起初的發(fā)布都在本人的工作電腦上進(jìn)行(Macbook Pro),由于 Mac 電腦的 Kotlin/Native 編譯器不支持編譯 Windows 平臺的產(chǎn)物,導(dǎo)致1.0 版本的 SQLlin 不支持 MinGW 目標(biāo)平臺。kYg28資訊網(wǎng)——每日最新資訊28at.com

在 2023 年 1 月,SQLlin 第一個版本的 CI/CD pipeline 上線。此后經(jīng)過持續(xù)的優(yōu)化,如今已經(jīng)進(jìn)入較為完備的體系和狀態(tài)。在搭建、優(yōu)化的過程中,我認(rèn)為以下幾點(diǎn)內(nèi)容頗為重要:kYg28資訊網(wǎng)——每日最新資訊28at.com

5.1 單元測試/儀器測試原則kYg28資訊網(wǎng)——每日最新資訊28at.com

單元測試對任何項目都具有重要意義,可以在一定程度上驗證代碼的修改不會導(dǎo)致原有預(yù)期行為的改變,因此單元測試是 CI/CD 流程中的關(guān)鍵步驟。我們可以先回看“二. JVMTarget 支持”一節(jié)中的 SQLlin 最終架構(gòu)設(shè)計圖,SQLlin 在任何一個平臺上運(yùn)行在底層都會涉及平臺相關(guān)代碼,因此單元測試必須覆蓋所有平臺相關(guān)代碼。kYg28資訊網(wǎng)——每日最新資訊28at.com

例如,如果我們只在 macOS機(jī)器上執(zhí)行單元測試,可以保證平臺無關(guān)代碼(sqllin-dsl、sqllin-processor、sqllin-driver(commonMain))以及 macOS 平臺相關(guān)代碼(sqllin-driver(nativeMain、appleMain))的正確性,但是無法驗證其他平臺相關(guān)的代碼,例如 sqllin-driver 中的 androidMain、jvmMain、linuxMain、mingwMain。kYg28資訊網(wǎng)——每日最新資訊28at.com

所以我們有必要在 Linux 和 Mac 機(jī)器上同時執(zhí)行Kotlin/Native 單元測試,但沒有必要分別在 iOS 和 macOS 上執(zhí)行 Kotlin/Native 單元測試,因為所有 Apple 平臺的相關(guān)代碼都在 appleMain source set 下,iOS 和 macOS上運(yùn)行的 SQLlin 代碼沒有任何區(qū)別,保證相同的代碼在 iOS 和 macOS 運(yùn)行得到相同的結(jié)果是 Kotlin 編譯器需要保證的事情,而不是庫開發(fā)者。JVM 單元測試比較特殊,需要在三臺機(jī)器上都運(yùn)行,因為文件路徑在三種不同的操作系統(tǒng)上的表示不同,這部分代碼的區(qū)別可能就幾個字符,但既然不是 100% 相同,那么就還是需要分別測試。kYg28資訊網(wǎng)——每日最新資訊28at.com

根據(jù)以上原則,我們需要執(zhí)行的單元測試如下:kYg28資訊網(wǎng)——每日最新資訊28at.com

  • Kotlin/JVM: JVM Unit Tests (Mac, Linux, Windows), Android Instrumented Tests (Android 9 以下版本,及最新 Android 版本)
  • Kotlin/Native: macOS x64 Unit Tests, Linux x64 Unit Tests, MinGW x64 Unit Tests

5.2 合理的 Host 分配kYg28資訊網(wǎng)——每日最新資訊28at.com

Kotlin 支持眾多平臺,這里的平臺是廣義的,其中既包括操作系統(tǒng)原生產(chǎn)物,又包括一些非原生開發(fā)環(huán)境。比如 WASM、JavaScript、JVM、Android就屬于非原生開發(fā)環(huán)境。WASM、JavaScript、JVM 這些技術(shù)的出現(xiàn)本身就是為了跨平臺(這里是狹義的“平臺”,特指各操作系統(tǒng)),而 Android 的 ART則是一個“非標(biāo)準(zhǔn)”的 JVM,這些編譯產(chǎn)物的運(yùn)行能力由其相對應(yīng)的平臺本身提供,不依賴特定 CPU 架構(gòu)或操作系統(tǒng) API,因此在任何機(jī)器上都能編譯構(gòu)建。kYg28資訊網(wǎng)——每日最新資訊28at.com

但Kotlin/Native 編譯出的操作系統(tǒng)原生產(chǎn)物則不同,首先,所有的 Apple 平臺(iOS、macOS、watchOS、tvOS)的編譯構(gòu)建都依賴 Xcode 命令行工具,而Apple 只提供 macOS 版本的 Xcode,因此,一個 Kotlin Multiplatform 應(yīng)用或庫如果要支持 Apple 平臺,必須使用 Mac 電腦開發(fā)和構(gòu)建;其次,由于Kotlin/Native 在 Windows 平臺上依賴 MinGW,至少 Kotlin 1.7.20 之前的版本如果要構(gòu)建 Windows 產(chǎn)物就必須使用 Windows 電腦,但在 1.7.20之后的某個版本開始,官方悄無聲息的支持了 Mac 電腦編譯 mingwx64 產(chǎn)物;而 Linux 系統(tǒng)的產(chǎn)物 Mac 電腦一直可以構(gòu)建。SQLlin 支持的全部平臺已經(jīng)在“二. JVMTarget 支持”一節(jié)中詳細(xì)列出。因此看似只需一臺 Mac 電腦即可完成全部的 CI/CD 任務(wù)。kYg28資訊網(wǎng)——每日最新資訊28at.com

但我們必須確保 CI/CD 中的單元測試可以符合 5.1 小節(jié)中的原則。macOS 雖然可以編譯構(gòu)建 Linux 和 Windows 平臺產(chǎn)物,但是無法執(zhí)行這些平臺的單元測試。所以我們至少需要Mac、Windows、Linux 三臺機(jī)器來完成整個 CI/CD 過程。三臺機(jī)器需要構(gòu)建的產(chǎn)物如下:kYg28資訊網(wǎng)——每日最新資訊28at.com

  • Mac:Android, JVM, iosX64, iosArm64, iosSimulatorArm64, macosX64, macosArm64, wachosX64, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosDeviceArm64, tvosX64, tvosX64, tvosArm64, tvosSimulatorArm64
  • Windows: JVM, mingwX64
  • Linux: Android, JVM, linuxX64, linuxArm64

僅從編譯構(gòu)建來看,Mac 的任務(wù)最重,Windows 的任務(wù)最輕。但沒有辦法,所有的 Apple 產(chǎn)物都只能在 Mac 上構(gòu)建。為了盡量縮短各平臺的 CI/CD pipeline運(yùn)行過程的時間差以節(jié)省總時間,我們盡量合理分配一下單元測試任務(wù)。各平臺執(zhí)行的單元測試任務(wù)如下所示:kYg28資訊網(wǎng)——每日最新資訊28at.com

  • Mac: macOS x64 Unit Tests, JVM Unit Tests, Android Instrumented Tests (Android 13)
  • Windows: MinGW x64 Unit Tests, JVM Unit Tests
  • Linux: Linux x64 Unit Tests, JVM Unit Tests, Android Instrumented Tests (Android 8)

實際上 native 和 JVM 單元測試的流程都非常快,但 Android 儀器測試的流程非常耗時(耗時甚至可能接近整個 CI/CD 流程耗時的一半),因為準(zhǔn)備(沒有緩存的話要創(chuàng)建)Android 模擬器非常耗時,連接Android 模擬器的測試過程也非常耗時,因此將兩個不同版本的 Android 儀器測試分配到不同的機(jī)器上是非常有必要的,這也是為什么 Linux 機(jī)器上也要構(gòu)建一次 Android 產(chǎn)物的原因。kYg28資訊網(wǎng)——每日最新資訊28at.com

5.3 緩存kYg28資訊網(wǎng)——每日最新資訊28at.com

由于每次執(zhí)行 CI/CD 時,Github Actions 總是分配空閑的機(jī)器給你的項目運(yùn)行 pipeline,因此每次 pipeline 執(zhí)行完畢后,流程中下載的構(gòu)建工具、依賴庫、編譯產(chǎn)物,以及創(chuàng)建的 Android模擬器都會被清除。在沒有任何緩存的情況下每次重新運(yùn)行 pipeline 會浪費(fèi)大量時間。因此配置緩存策略是節(jié)省 CI/CD 運(yùn)行時間的訣竅之一。kYg28資訊網(wǎng)——每日最新資訊28at.com

我們主要需要緩存的東西有三個:下載的構(gòu)建工具、創(chuàng)建好的 Android 模擬器、Gradle 構(gòu)建產(chǎn)物。一些和緩存有關(guān)的 yml 腳本中的 steps 代碼如下:kYg28資訊網(wǎng)——每日最新資訊28at.com

- name: Cache Build Tooling    uses: actions/cache@v2    with:      path: |        ~/.gradle/caches        ~/.konan      key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }}- name: Gradle Cache    uses: gradle/gradle-build-action@v2- name: AVD Cache   uses: actions/cache@v3   id: avd-cache   with:      path: |        ~/.android/avd/*        ~/.android/adb*       key: avd-33- name: Create AVD and Generate Snapshot for Caching   if: steps.avd-cache.outputs.cache-hit != 'true'   uses: reactivecircus/android-emulator-runner@v2   with:     api-level: 33     target: google_apis     arch: x86_64     profile: pixel_6     force-avd-creation: false     emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none     disable-animations: false     script: echo "Generated AVD snapshot for caching."

實際效果也非常好,使用緩存之前整個 CI/CD 流程執(zhí)行結(jié)束可能需要 26 分鐘以上,使用緩存后降低至 10 分鐘出頭。其實可以想象每次我們在電腦上下載 Android 模擬器所需的鏡像,然后再創(chuàng)建模擬器要花多長時間,就知道緩存是多么有用的時間優(yōu)化手段。kYg28資訊網(wǎng)——每日最新資訊28at.com

六、社區(qū)推廣

2022 年 SQLlin 剛開源之際,我在 2022 Kotlin 中文開發(fā)者大會上分享了 SQLlin 相關(guān)的內(nèi)容:以 SQLlin 為例,分享如何構(gòu)建自己的 KMP 庫的經(jīng)驗。收效較好,SQLlin 在 Kotlin Multiplatform 中文社區(qū)內(nèi)擁有了一定知名度。目前在 Github 上擁有 190 個 stars(2024.01.18),從 starts 數(shù)量上來看也許并不高,但Kotlin Multiplatform 開發(fā)者群體絕對數(shù)量目前仍然較低,與 Android、Java 等技術(shù)棧相比不在一個數(shù)量級,因此該成績算是可以接受。kYg28資訊網(wǎng)——每日最新資訊28at.com

相較于國內(nèi)的環(huán)境,英文社區(qū)對新技術(shù)的接受速度普遍更高,Kotlin Multiplatform 開發(fā)者的數(shù)量更大,因此將 SQLlin 的影響力擴(kuò)大到英文社區(qū)是一個好的選擇。kYg28資訊網(wǎng)——每日最新資訊28at.com

SQLlin 自誕生之初就擁有全套的英文文檔,在這一整年的維護(hù)升級過程中,我發(fā)現(xiàn)國外開發(fā)者的 issue/PR 數(shù)量大概占一半,維護(hù)過程中我與過來自希臘、英國、巴西的開發(fā)者在issue 或 PR 中互動過。Stars 的來源也有大量國外開發(fā)者,包括美國、德國、韓國、俄羅斯等等。與國外開發(fā)者在 Github 合作、溝通是一種極為有趣的體驗。kYg28資訊網(wǎng)——每日最新資訊28at.com

此外,一家美國初創(chuàng)的語言學(xué)習(xí)類 App 公司——Migaku 在生產(chǎn)環(huán)境使用 SQLlin,這是我發(fā)現(xiàn)的第一例在生產(chǎn)環(huán)境使用 SQLlin 的國外商業(yè)公司。他們的員工曾幫助提交PR(參考鏈接 4)協(xié)助修復(fù)了一個 Native 平臺與 Android 平臺行為不一致的問題,并請求我盡快發(fā)布新版,因為他們希望在 App 發(fā)布新版時可以使用問題修復(fù)后的新版SQLlin。kYg28資訊網(wǎng)——每日最新資訊28at.com

我也將 SQLlin 作為講題內(nèi)容申請成為哥本哈根 KotlinConf 2024 大會的 speaker,KotlinConf 是世界性質(zhì)的行業(yè)大會,由 Kotlin 的開發(fā)商 JetBrains 舉辦。如果講題被 JetBrains選中,這將是一個擴(kuò)大 SQLlin 在世界范圍內(nèi)影響力的絕佳機(jī)會,同時也是向英文社區(qū)分享中國 Kotlin Multiplatform 開發(fā)經(jīng)驗、貢獻(xiàn)知識的機(jī)會,還是一個能收獲許多世界優(yōu)秀開發(fā)者的反饋,提升個人技能、公司在相關(guān)領(lǐng)域技術(shù)實力的機(jī)會。kYg28資訊網(wǎng)——每日最新資訊28at.com

從 2022.11 ~ 2024.1,近一年的時間 Kotlin Multiplatform 技術(shù)迎來許多重要的變革。這其中包括 new MM 從實驗性階段轉(zhuǎn)入穩(wěn)定,也包括 Kotlin/Native 編譯器支持的 targets 的更迭,其他的小更新及優(yōu)化更是數(shù)不勝數(shù)。kYg28資訊網(wǎng)——每日最新資訊28at.com

事實上最近幾個版本的 Kotlin 在新功能的迭代速度上已經(jīng)放緩,其主要原因是官方最近將主要精力放在了 Kotlin 新編譯器 K2 的優(yōu)化上,2024 年 K2 正式版將會隨 Kotlin 2.0 一起到來。目前 SQLlin 1.2.4 版本基于 Kotlin 1.9.22,1.9.22 應(yīng)該會是 Kotlin 1.x 的最后一個發(fā)行版,而當(dāng) Kotlin 2.0 發(fā)布后,SQLlin 也會積極進(jìn)行升級。隨著 Kotlin 語言特性、標(biāo)準(zhǔn)庫、生態(tài)環(huán)境的逐步提升,SQLlin 也會對內(nèi)部實現(xiàn)進(jìn)行重構(gòu)和迭代,以求在性能和代碼結(jié)構(gòu)等方面帶來更多的提升。kYg28資訊網(wǎng)——每日最新資訊28at.com

SQLlin 在未來還有眾多的發(fā)展空間,例如更改表結(jié)構(gòu)的 SQL 語句 DSL 化還沒有實現(xiàn),Join 子查詢的 DSL 化也還沒有實現(xiàn),這些都已經(jīng)規(guī)劃到了未來的開發(fā)計劃中。希望在未來 SQLlin 可以在攜程機(jī)票及整個 Kotlin Multiplatform 技術(shù)社區(qū)中有更廣泛的應(yīng)用場景。kYg28資訊網(wǎng)——每日最新資訊28at.com

七、參考鏈接

開源項目 SQLiter:kYg28資訊網(wǎng)——每日最新資訊28at.com

https://github.com/touchlab/SQLiterkYg28資訊網(wǎng)——每日最新資訊28at.com

修復(fù) SQliter Join 語句問題的 PR:kYg28資訊網(wǎng)——每日最新資訊28at.com

https://github.com/touchlab/SQLiter/pull/89kYg28資訊網(wǎng)——每日最新資訊28at.com

SQLlin 支持 JVM 相關(guān)的 issue:kYg28資訊網(wǎng)——每日最新資訊28at.com

https://github.com/ctripcorp/SQLlin/issues/15kYg28資訊網(wǎng)——每日最新資訊28at.com

Migaku 提交的修復(fù) SQLlin bug 的 PR:kYg28資訊網(wǎng)——每日最新資訊28at.com

https://github.com/ctripcorp/SQLlin/pull/51kYg28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-65367-0.html從 SQLlin 的更新看 Kotlin Multiplatform 技術(shù)更迭

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 單體架構(gòu)、微服務(wù)和無服務(wù)器架構(gòu)

下一篇: React高手都善于使用useImprativeHandle

標(biāo)簽:
  • 熱門焦點(diǎn)
Top 主站蜘蛛池模板: 保山市| 阳西县| 隆子县| 无为县| 河北区| 柳林县| 太白县| 丹巴县| 天峨县| 岳西县| 吉隆县| 东海县| 普定县| 旬邑县| 芷江| 松溪县| 裕民县| 孝感市| 巩留县| 休宁县| 赤壁市| 托里县| 尉犁县| 昌平区| 桐柏县| 封开县| 轮台县| 永宁县| 财经| 临泽县| 辽中县| 临漳县| 阜城县| 连城县| 伊金霍洛旗| 满洲里市| 盐亭县| 紫阳县| 荥阳市| 翁牛特旗| 长海县|