开源 | 从 SQLlin 的更新看 Kotlin Multiplatform 技术更迭

Kotlin & Compose Multiplatform 跨平台(Android端、桌面端)开发实践之使用 SQLDelight 将数据储存至数据库 首先,SQLDelight 是什么东西呢?先来看官方的介绍:简单说就是一个能让我们在 Kotlin 中使用 SQL 更方便的一个库。如果还是不太理解的话,你可以把它当成支持跨平台的 Room。虽然它俩好像就没有多少相似之处,哈哈哈。并且,不像 Room,提供了几个常用的查询语句(Insert、Update、Delete 等),可以直接使用,SQLDelight 的所有查询语句都需要自己手写。 阅读详情

作者简介

禹昂,携程移动开发专家,Google 开发者专家(Android),上海 Kotlin User Group 组织者,图书《Kotlin 编程实践》译者。

2022 年底,我们在携程的 Github organization 下开源了 SQLlin,SQLlin 是一款基于 Kotlin DSL 及 KSP 技术的,支持众多平台的 Kotllin Multipllatform SQLite 数据库框架。感兴趣且不了解 SQLlin 的读者可以参考:《携程机票跨端 Kotlin DSL 数据库框架 SQLlin》一文。

SQLlin作为携程机票移动端团队最为完备的一款开源项目,在接近 1 年的时间内经历了不少升级与换血式的更新,也见证了这一年 Kotlin Multiplatform 技术的演进及社区生态的变化。本文将带领大家梳理这些更新,并探求这些更新背后所涉及到的 Kotlin Multiplatform 技术栈在这一年来的更迭与进化。

一、重写 native 驱动层

我们先来回顾一下最初的 SQLlin 架构图:

5ec79f0e5e5c80ef1b7f57a3289d4af2.jpeg

最初,SQLlin 在 Kotlin/Native 平台上基于开源项目 SQLiter(见参考链接 1),目的是避免重复造轮子。虽然 SQLliter 是来自 Touchlab的优秀开源项目,但最近一年维护更新缓慢。在本文撰写时,SQLiter 于 2023 年 11 月发布了 1.3.0 和 1.3.1 两个版本(1.3.1升级到了 Kotlin 1.9.21,用于修复 1.9.20 的 Kotlin/Native 库版本号相关的问题)。但在这之前的版本,即 1.2.1 发布于 2022年 8 月,基于 Kotlin 1.6.20,一年以上没有更新。对于 2023 年的项目来说,1.6.20 过于老旧。老旧的版本导致了如下一些问题。

1.1 Targets 更新维护不及时

Kotlin 在 1.8.20 版本废弃了一众 32 位 Kotlin/Native targets(目标平台),包括:iosArm32、watchosX86、wasm32、mingwX86、linuxArm32Hfp、linuxMips32、linuxMipsel32。这些目标平台几乎已经完全被淘汰,市面上已经极少有可以运行这些targets 的设备,继续支持已无意义。因此 Kotlin 决定将这些 targets 标记为“deprecated”,并在 1.9.20 版本将它们完全移除。

这些即将被移除的 targets 中,iosArm32、watchosX86、mingwX86 受到 SQLiter 及 SQLlin 的支持。由于 SQLiter 不更新版本,所以这些 targets 将继续存在于 SQLiter 当中,虽然 sqllin-driver 可以在上层移除对这些平台的支持,但长久来说由于编译器版本的更迭,仍然不是最佳做法。

如果说在 sqllin-driver 中移除对旧编译目标的支持可以暂时解决“废弃旧 targets 不及时”的问题,那么“对新 targets 的支持”则无计可施。

Kotlin 在 1.8.0 版本开始支持 watchosDeviceArm64 新目标平台,对应于全新的 64 位 Apple Watch 设备。虽然可以预见使用 Kotlin Multiplatform 技术开发 Apple Watch 应用的开发者不会很多,但 SQLlin 原本支持所有的 watchOS 相关 targets,不支持最新的 Arm64 架构并不合理。由于 SQLiter 不支持 watchosDeviceArm64,因此 SQLlin 也无法支持。

1.2 Bug 无法及时修复

在 SQL 中我们会遇到一个常见的用法——join,在 join 查询时遇到两个表拥有相同名字的列也是常见现象。在 SQLiter的原始实现中,后查询出来的同名列值会覆盖掉先查询出来的同名列值:

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 以修复此问题(参考链接 2),但 SQLliter 的维护者没有任何回复,同样是直到 2023 年 11 月才合并该 PR。

无法支持的新平台导致有刚需的用户无法继续使用 SQLlin,而无法修复的问题导致了特定场景必定出错的硬伤。一年没有任何维护让我对 SQLiter 感到疑虑,此时自行实现已经变成了必然选择。

1.3 Native 驱动层重写

重写 Native 驱动层并不困难,我们可以参考 SQLiter 的不少设计理念。

首先,SQLite 在不同的 Native 平台上都提供相同的 C API,所以我们绝大部分代码是平台(这里特指 Kotlin/Native 的诸多目标平台)无关的。根据官方 KMP 工程的架构约定,这部分平台无关的代码可以全部放在 nativeMain source set 下。但由于我们构建了一套面向对象风格的 API,加上需要处理例如线程同步等问题,因此还是会依赖一些系统平台 API。比如说如果要在 nativeMain 中使用线程锁,需要用 expect 关键字定义待实现的API,在各平台相关 source set 中使用 actual 关键字定义相关实现。比如说在 Apple 平台上我们使用 Apple Foundation 中的 Objective-C 类 NSRecursiveLock,而在 Linux 和Windows 平台上则使用 Posix C 中的 pthread_mutex_xxx 系列 C API。

我们将 SQLite 的 C 库头文件放在 include 路径下(与 nativeMain 平级),然后编写 .def 文件并放在 nativeInterop 路径下(同样与 nativeMain 平级),然后在 build.gradle.kts 文件中配置头文件的路径以及 SQLite C 库的 linkerOpts(编译链接参数),即可在所有 native 相关的 sourceSet 中调用 SQLite C 函数,build.gradle.kts 中的配置如下:

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 目标平台可调用的扩展函数,使所有 native targets 都调用它即可。其中 linkerOpts 在 Linux 和 Windows 平台上都指向常见的 SQLite 安装路径(使用常见的包管理器),但为了确保 native 单元测试可以顺利在任何 Linux 或 Windows host 上运行,SQLlin 的源码目录中实际上附带了针对 Linux 及 Windows 的 SQLite .a 库,因此当链接过程无法在常见路径下找到 SQLite .a文件时,最终会链接到 SQLlin 源码路径下的版本。

但再次强调,以上场景仅限单元测试,如果你是使用 SQLlin 的应用开发者,且你的应用支持 Linux 和 Windows,需要确保用户的电脑安装了SQLite,或者在应用程序工程中附带 SQLite C 库,并自行添加 linkerOpts 链接到 SQLite .a 文件。至于 Apple 相关平台(iOS、macOS、watchOS、tvOS),系统框架中已经自带了SQLite,因此不必担心以上问题,sqllin-driver 中添加的编译链接参数可以正确链接到系统框架中自带的版本。最后我们来看一下 nativeMain 下的源码结构:

102cf8864fc6f144f96d44b11cb90c20.png

cinterop 包包含所有对 SQLite C 函数直接互操作的代码,通过单独的包将其与其它代码隔离;platform 包则存放所有待平台实现的相关代码,真正的实现则位于 appleMain、linuxMain、mingwMain 几个 source sets 中;其余代码是 sqllin-driver-native 的核心实现,都位于根目录包下。

二、JVM Target 支持

起初,根据预测,我认为使用 Kotlin Multiplatform 技术开发 JVM 桌面应用的人并不多。但由于 Compose Multiplatform 最初支持的平台便是 Android 与 JVM,因此吸引了大量 Kotlin Multiplatform 开发者将自己的多平台应用的支持范围扩展到 JVM。在部分用户提交了一些 issue(参考链接 3)后,我决定着手进行 JVM 平台的支持工作。而支持 JVM 平台也有助于调研将 SQLlin 支持的数据库扩展到 MySQL、H2、Oracle 等后端数据库的可能性,因为它们都基于 JDBC。

JVM 平台的实现基于 SQLite 官方的 JVM driver:sqlite-jdbc,库的使用者通过 JDBC 连接到 sqlite-jdbc,而 sqlite-jdbc 底层则通过 JNI 操作 SQLite C 库。由于 sqlite-jdbc本身就是 Java 库,因此 API 的抽象程度比 native 平台上直接调用 C API 高的多。所以 jvmMain 中的代码实现比 nativeMain 要简单很多。

但也有几个点值得一提:

首先,Windows平台上的文件路径分隔符是 ‘\’,而 Linux 和 macOS 上都是 ‘/’,因此在处理用户传入的路径参数时,即使是在 jvmMain 中也要判断当前运行的操作系统是不是 Windows。

其次,由于sqlite-jdbc 中没有对 sqlite3_config C 函数的调用,因此目前 lookasideSlotSize 和 lookasideSlotCount 两个参数在 JVM 平台上无法生效,后续我计划通过提交 PR 的方式参与sqlite-jdbc 的开发,使其支持 sqlite3_config,但目前还没有具体的时间表。

当然,支持 JVM 平台的开发过程还遇到过其他的细节问题,例如表示查询结果集的 java.sql.ResultSet 类型起始下标是 1 而不是 Android 平台 android.database.Cursor 和 Native 平台 C API 中的 0。不过这类问题都较为容易处理,在此不多做赘述。

在重写了 native 平台的 driver 和支持了 JVM 平台后,SQLlin 的架构图如下所示:

07cf1e47298b1a74e80a76254de54e6d.png

目前 SQLlin 支持的完整目标平台列表如下:

  • 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 并发安全

sqllin-driver 作为低阶 SQLite 框架,可以通过 SQLite 本身的线程安全机制来实现一定程度上的线程安全,我写过一篇文章《关于 SQLite 多线程行为的结论》讨论过相关知识。

简而言之,在多数情况下 SQLite 的默认线程模式都是:Multi-thread,在单连接多线程的情况下是可以保证线程安全的。因此我们只需尽量避免多连接多线程的情形即可,将同一个连接在多个线程间共享是个好方法。

现在我们来回顾一下 sqllin-dsl 的基本用法,以便理解本节接下来的内容:

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 对象中只会建立一个数据库链接。但上述示例中如果我们将对象 db(类型为 Database)在多个线程(或运行在不同线程上的协程)中共享,几乎必然会出现问题。

原因在于 Database 对象内部使用一个双向链表来进行一组 SQL 语句的构建,一个 Database 对象持有一个双向链表,每次子句的连接都会直接拼接到链表头部的 SQL语句上,而当 SQL 语句组执行完毕后链表会被清空。

如果在多个线程/协程中同事使用 db 对象,可以想象这可能会出现 SQL 语句拼接混乱的问题,例如线程 A 和 线程 B 都在构建自己的SQL 语句,由于没有同步机制,线程 B 中的子句可能被拼接到线程 A 中已经创建出的 SQL 语句后面,造成 SQL 语法错误。也有可能出现线程 A 还在构建 SQL 语句,但线程 B 已经进入SQL 语句执行阶段,线程 B 很可能会将还未构建完成的 SQL 语句传给 SQLite,造成运行错误。

SQLlin 最初之所以没有设计线程同步机制主要是基于 Kotlin 版本的考量。在 SQLlin 第一个版本发布的 Kotlin 1.7.20 时期,Kotlin/Native new Memory Management(新内存管理器,后文简称 new MM)还未进入正式版,不少开发者还在使用旧内存管理器。在 Kotlin/Native 的旧内存模型中,对象是不能直接跨线程访问的,必须要手动进行对象子图分离和再绑定操作,对象才能将自己的所有权转移到另一个线程,这种设计其实是强制开发者在编译期就保证对象在同一时刻只能被一个线程访问。

关于旧内存模型在本人以往的文章中讨论过很多次,并且在当下 Kotlin 1.9.20 时代已经被彻底淘汰,这里也不再过多讨论。基于以上的时代背景,在不能确定用户是否使用新内存管理器的情况下,做线程同步的设计非常困难,因此最好的方式就是不处理,并且建议用户不要在多线程间共享 Database 对象。但如今 2023 年末,在 Kotlin 1.9.2x 版本作为最新版本的背景下,new MM早已经被绝大部分开发者所使用,因此此时基于 new MM 的设计进行线程同步机制的开发非常合适。

在 sqllin-dsl 新版本的设计中,新增了挂起函数 API suspendScope,用于在并发环境下取代 operator 函数 invoke,并且管理 SQL 语句构建的双向链表被改成成员变量,只有在每次invoke 或 suspendScope 函数被调用时才创建,在 SQL 语句执行完毕后会被就会被抛弃。由于函数调用栈是线程私有的,因此这样的设计可以在不同的线程同时构建 SQL语句时隔离运行,既提高效率又保证了线程安全。

在 SQL 语句运行阶段,由于每次 SQL 语句构建完毕后执行的都是一组 SQL,为了避免不同线程同时执行 SQL语句时的顺序的不确定性,例如线程 A 需要执行 SQL 语句 a、b、c,线程 B 需要执行 SQL 语句 d、e、f,不加任何同步机制同时执行可能会导致 a、b、c、d、e、f的执行顺序不确定,从而导致不可预知的问题,因此 SQL 语句执行阶段必须加入协程锁 Mutex 来保证并发安全,suspendScope 的实现如下:

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
}

由于使用了协程锁 Mutex,因此自 1.2.2 版本起, sqllin-dsl 依赖 Kotlin 官方协程框架 kotlinx.coroutines。

四、Android 低版本向下兼容

Android 系统曾在 API 28(Android 9)版本对 framework 中的 SQLite Java APIs 进行了一次升级,这次升级提供了许多新 API 可以让开发者对 SQLite进行具体的参数配置,这些参数包括:日志模式、同步模式、连接超时时间、lookaside memory,这在之前的版本都是不可以的。由于 SQLlin 最低支持的Android 版本是 API 23(Android 6),因此在 Android 9 以下的设备上,以上提到的参数都无法生效。

但最初的认知并不准确,因为日志模式、同步模式两个参数都使用 PRAGMA 语句配置,因此只需要在 sqllin_driver 内自行构建 PRAGMA 语句并执行,即可在旧Android 系统上也能进行日志模式与同步模式的设置。因此,自 1.2.0 版本起,SQLlin 在旧 Android 设备上也支持设置日志模式与同步模式。但基于 SQLite C API才能配置的连接超时时间和 lookaside memory 仍然无法在旧设备上生效。

五、CI/CD 优化

在 SQLlin 开源之初没有进行 CI/CD 环境的搭建。CI/CD 对于验证 push、PR 的准确性,保证版本发布的 bug 率等方面具有重要意义。同时也是向 MavenCentral发布新版本的最佳途径。起初的发布都在本人的工作电脑上进行(Macbook Pro),由于 Mac 电脑的 Kotlin/Native 编译器不支持编译 Windows 平台的产物,导致1.0 版本的 SQLlin 不支持 MinGW 目标平台。

在 2023 年 1 月,SQLlin 第一个版本的 CI/CD pipeline 上线。此后经过持续的优化,如今已经进入较为完备的体系和状态。在搭建、优化的过程中,我认为以下几点内容颇为重要:

5.1 单元测试/仪器测试原则

单元测试对任何项目都具有重要意义,可以在一定程度上验证代码的修改不会导致原有预期行为的改变,因此单元测试是 CI/CD 流程中的关键步骤。我们可以先回看“二. JVMTarget 支持”一节中的 SQLlin 最终架构设计图,SQLlin 在任何一个平台上运行在底层都会涉及平台相关代码,因此单元测试必须覆盖所有平台相关代码。

例如,如果我们只在 macOS机器上执行单元测试,可以保证平台无关代码(sqllin-dsl、sqllin-processor、sqllin-driver(commonMain))以及 macOS 平台相关代码(sqllin-driver(nativeMain、appleMain))的正确性,但是无法验证其他平台相关的代码,例如 sqllin-driver 中的 androidMain、jvmMain、linuxMain、mingwMain。

所以我们有必要在 Linux 和 Mac 机器上同时执行Kotlin/Native 单元测试,但没有必要分别在 iOS 和 macOS 上执行 Kotlin/Native 单元测试,因为所有 Apple 平台的相关代码都在 appleMain source set 下,iOS 和 macOS上运行的 SQLlin 代码没有任何区别,保证相同的代码在 iOS 和 macOS 运行得到相同的结果是 Kotlin 编译器需要保证的事情,而不是库开发者。JVM 单元测试比较特殊,需要在三台机器上都运行,因为文件路径在三种不同的操作系统上的表示不同,这部分代码的区别可能就几个字符,但既然不是 100% 相同,那么就还是需要分别测试。

根据以上原则,我们需要执行的单元测试如下:

  • 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 分配

Kotlin 支持众多平台,这里的平台是广义的,其中既包括操作系统原生产物,又包括一些非原生开发环境。比如 WASM、JavaScript、JVM、Android就属于非原生开发环境。WASM、JavaScript、JVM 这些技术的出现本身就是为了跨平台(这里是狭义的“平台”,特指各操作系统),而 Android 的 ART则是一个“非标准”的 JVM,这些编译产物的运行能力由其相对应的平台本身提供,不依赖特定 CPU 架构或操作系统 API,因此在任何机器上都能编译构建。

但Kotlin/Native 编译出的操作系统原生产物则不同,首先,所有的 Apple 平台(iOS、macOS、watchOS、tvOS)的编译构建都依赖 Xcode 命令行工具,而Apple 只提供 macOS 版本的 Xcode,因此,一个 Kotlin Multiplatform 应用或库如果要支持 Apple 平台,必须使用 Mac 电脑开发和构建;其次,由于Kotlin/Native 在 Windows 平台上依赖 MinGW,至少 Kotlin 1.7.20 之前的版本如果要构建 Windows 产物就必须使用 Windows 电脑,但在 1.7.20之后的某个版本开始,官方悄无声息的支持了 Mac 电脑编译 mingwx64 产物;而 Linux 系统的产物 Mac 电脑一直可以构建。SQLlin 支持的全部平台已经在“二. JVMTarget 支持”一节中详细列出。因此看似只需一台 Mac 电脑即可完成全部的 CI/CD 任务。

但我们必须确保 CI/CD 中的单元测试可以符合 5.1 小节中的原则。macOS 虽然可以编译构建 Linux 和 Windows 平台产物,但是无法执行这些平台的单元测试。所以我们至少需要Mac、Windows、Linux 三台机器来完成整个 CI/CD 过程。三台机器需要构建的产物如下:

  • 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

仅从编译构建来看,Mac 的任务最重,Windows 的任务最轻。但没有办法,所有的 Apple 产物都只能在 Mac 上构建。为了尽量缩短各平台的 CI/CD pipeline运行过程的时间差以节省总时间,我们尽量合理分配一下单元测试任务。各平台执行的单元测试任务如下所示:

  • 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 流程耗时的一半),因为准备(没有缓存的话要创建)Android 模拟器非常耗时,连接Android 模拟器的测试过程也非常耗时,因此将两个不同版本的 Android 仪器测试分配到不同的机器上是非常有必要的,这也是为什么 Linux 机器上也要构建一次 Android 产物的原因。

5.3 缓存

由于每次执行 CI/CD 时,Github Actions 总是分配空闲的机器给你的项目运行 pipeline,因此每次 pipeline 执行完毕后,流程中下载的构建工具、依赖库、编译产物,以及创建的 Android模拟器都会被清除。在没有任何缓存的情况下每次重新运行 pipeline 会浪费大量时间。因此配置缓存策略是节省 CI/CD 运行时间的诀窍之一。

我们主要需要缓存的东西有三个:下载的构建工具、创建好的 Android 模拟器、Gradle 构建产物。一些和缓存有关的 yml 脚本中的 steps 代码如下:

- 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 流程执行结束可能需要 26 分钟以上,使用缓存后降低至 10 分钟出头。其实可以想象每次我们在电脑上下载 Android 模拟器所需的镜像,然后再创建模拟器要花多长时间,就知道缓存是多么有用的时间优化手段。

六、社区推广

2022 年 SQLlin 刚开源之际,我在 2022 Kotlin 中文开发者大会上分享了 SQLlin 相关的内容:以 SQLlin 为例,分享如何构建自己的 KMP 库的经验。收效较好,SQLlin 在 Kotlin Multiplatform 中文社区内拥有了一定知名度。目前在 Github 上拥有 190 个 stars(2024.01.18),从 starts 数量上来看也许并不高,但Kotlin Multiplatform 开发者群体绝对数量目前仍然较低,与 Android、Java 等技术栈相比不在一个数量级,因此该成绩算是可以接受。

相较于国内的环境,英文社区对新技术的接受速度普遍更高,Kotlin Multiplatform 开发者的数量更大,因此将 SQLlin 的影响力扩大到英文社区是一个好的选择。

SQLlin 自诞生之初就拥有全套的英文文档,在这一整年的维护升级过程中,我发现国外开发者的 issue/PR 数量大概占一半,维护过程中我与过来自希腊、英国、巴西的开发者在issue 或 PR 中互动过。Stars 的来源也有大量国外开发者,包括美国、德国、韩国、俄罗斯等等。与国外开发者在 Github 合作、沟通是一种极为有趣的体验。

此外,一家美国初创的语言学习类 App 公司——Migaku 在生产环境使用 SQLlin,这是我发现的第一例在生产环境使用 SQLlin 的国外商业公司。他们的员工曾帮助提交PR(参考链接 4)协助修复了一个 Native 平台与 Android 平台行为不一致的问题,并请求我尽快发布新版,因为他们希望在 App 发布新版时可以使用问题修复后的新版SQLlin。

我也将 SQLlin 作为讲题内容申请成为哥本哈根 KotlinConf 2024 大会的 speaker,KotlinConf 是世界性质的行业大会,由 Kotlin 的开发商 JetBrains 举办。如果讲题被 JetBrains选中,这将是一个扩大 SQLlin 在世界范围内影响力的绝佳机会,同时也是向英文社区分享中国 Kotlin Multiplatform 开发经验、贡献知识的机会,还是一个能收获许多世界优秀开发者的反馈,提升个人技能、公司在相关领域技术实力的机会。

从 2022.11 ~ 2024.1,近一年的时间 Kotlin Multiplatform 技术迎来许多重要的变革。这其中包括 new MM 从实验性阶段转入稳定,也包括 Kotlin/Native 编译器支持的 targets 的更迭,其他的小更新及优化更是数不胜数。

事实上最近几个版本的 Kotlin 在新功能的迭代速度上已经放缓,其主要原因是官方最近将主要精力放在了 Kotlin 新编译器 K2 的优化上,2024 年 K2 正式版将会随 Kotlin 2.0 一起到来。目前 SQLlin 1.2.4 版本基于 Kotlin 1.9.22,1.9.22 应该会是 Kotlin 1.x 的最后一个发行版,而当 Kotlin 2.0 发布后,SQLlin 也会积极进行升级。随着 Kotlin 语言特性、标准库、生态环境的逐步提升,SQLlin 也会对内部实现进行重构和迭代,以求在性能和代码结构等方面带来更多的提升。

SQLlin 在未来还有众多的发展空间,例如更改表结构的 SQL 语句 DSL 化还没有实现,Join 子查询的 DSL 化也还没有实现,这些都已经规划到了未来的开发计划中。希望在未来 SQLlin 可以在携程机票及整个 Kotlin Multiplatform 技术社区中有更广泛的应用场景。

七、参考链接

开源项目 SQLiter:

https://github.com/touchlab/SQLiter

修复 SQliter Join 语句问题的 PR:

https://github.com/touchlab/SQLiter/pull/89

SQLlin 支持 JVM 相关的 issue:

https://github.com/ctripcorp/SQLlin/issues/15

Migaku 提交的修复 SQLlin bug 的 PR:

https://github.com/ctripcorp/SQLlin/pull/51

PS:团队目前有Kotlin Multiplatform的岗位开放,如果你希望和我们一起共事,欢迎点这里了解。

【推荐阅读】

d2dda54426e21b927b441cc649ef3f11.jpeg

 “携程技术”公众号

  分享,交流,成长

OSDU(Open Subsurface Data Universe,开放地下数据空间 / 开源地下数据生态系统) OSDU是由油气行业与云厂商共建的开源、云原生数据平台规范,旨在解决勘探开发领域数据孤岛、格式不统一、应用绑定等问题。其核心是“数据与应用解耦”,通过统一元数据模型、API及权限体系,实现跨云、跨项目的数据互通。平台支持地震、井筒、储层等专业数据管理,配套OpenVDS、ETP等标准,适配PB级体数据,支撑AI训练与数字孪生。虽为参考实现,非即用系统,但已成国际油气数字化底座趋势,国内三桶油正推进试点与国产化适配。 阅读详情

相关推荐

multiplatform-settings:跨平台键值数据存储解决方案

本文还有配套的精品资源,点击获取 简介:multiplatform-settings是一个专门针对Kotlin Multiplatform开发的库,允许开发者在Android、iOS、JavaScript和JVM等平台上以一致的API持久化键值数据。这个库简化了跨平台应用的数据存储,提供了一个简单的API接口,支持多种数据类型,并具备良好的线程安全和版本兼容性。文档齐全且拥...

weixin_28888459的博客 1025

SQLlin 项目常见问题解决方案

SQLlin 项目常见问题解决方案 1. 项目基础介绍和主要编程语言 SQLlin 是一个为 Kotlin Multiplatform 设计的基于 DSL (Domain-Specific Language) 和 KSP (Kotlin Symbol Processing) 的 ORM (Object-Relational Mapping) 库。它使用 SQLite 作为底层数据库,允许开发者在 ...

gitblog_01057的博客 675

Migaku-Japanese-Addon:Migaku Japanese是Anki的附加组件,可帮助您更轻松地学习日语。 如果您有问题,请在“问题”标签中报告。 在GitHub上对问题的响应可能很慢,要获得更快的支持,请考虑通过我们的Patreon页面加入我们的Discord服务器

Migaku Japanese Addon 向日语单词添加读数和音高音信息,允许在单击单词时播放音频,导出音高音调图表,转换为片假名或历史汉字和假名等等! 目录 安装 安装AnkiWeb附加的“ Migaku开发周期”部分下列出的Anki版本。 从下载最新版本的插件 文献资料 文档即将发布 贡献 欢迎捐款! 请查看有关以下方面的贡献准则: 报告问题 文件拉取请求 以非开发人员的身份支持项目 许可和积分 日本的Migaku是 *版权所有:copyright:2020 Migaku ltd。 Migaku Japanese是免费的开源软件。 在Anki中运行的附加代码是根据GNU AGPLv3许可证发布的,并通过许多附加条款进行了扩展。 有关更多信息,请参见该程序随附的LICENSE文件。 分发该程序是希望它会有用,但没有任何保证。

SQLlin 开源项目教程

SQLlin 开源项目教程 1. 项目目录结构及介绍 SQLlin 是一个专为 Kotlin 多平台设计的 ORM 库,它利用 DSL 和 KSP(Kotlin Symbol Processing)为基础,旨在使SQLite数据库操作更加便捷且类型安全。以下是其核心目录结构概述: sqllin-dsl: 包含用于构建SQL语句的DSL实现。 sqllin-driver: 一组多平台通用的SQLi...

gitblog_00302的博客 417

Zabbix Trigger actions访问出现500

本篇博客详细介绍了Zabbix访问'Trigger actions'出现的错误状况,访问界面出现500,并提供了解决方案。

格子的博客 562

Oracle数据库安装及配置(Oracle19c)

Windows 10安装Oracle 19c教程

weixin_69200200的博客 2万+

探索SQLlin:Kotlin Multiplatform的SQLite框架新星

探索SQLlin:Kotlin Multiplatform的SQLite框架新星 在移动和跨平台开发领域,SQLite数据库的使用极为广泛。然而,编写和维护SQL语句往往是一项繁琐且容易出错的任务。今天,我们要介绍的是一款革命性的Kotlin Multiplatform SQLite框架——SQLlin,它通过DSL(领域特定语言)和Kotlin符号处理器(KSP)的结合,为开发者提供了一种全新的...

gitblog_01156的博客 945

什么是无人机电源管理模块?电源管理模块有什么用处?

我来描述一下我的问题情景,电源管理模块的定义是,这里给出这个PX4官方对于电源管理模块的一些介绍,补充一下锂电池的放电原理大家就可以更好理解为什么需要电源管理模块

HRWVY0725的博客 2272

【亲测免费】 SQL Lint 使用教程

SQL Lint 是一个用于检查 SQL 语句的工具,它可以帮助开发者发现 SQL 查询中的错误和潜在问题。SQL Lint 支持多种编辑器,包括 Neovim、Vim 和 VS Code,并且可以通过插件或配置直接在这些编辑器中使用。此外,SQL Lint 还提供了丰富的检查功能,包括但不限于未匹配的括号、无效的 LIMIT 语句、匈牙利命名法等。 ## 项目快速启动 ### 安装 你可以...

gitblog_00622的博客 1373

windows使用jupyter notebook读取文件路径的几种方法和设置默认打开路径

简单分享小虎这段时间以来学到的jupyter notebook操作技巧。

狂小虎/Anthony Dave的博客 KuangXiaoHu's Blogs 1万+

第81篇:Vibe Coding时代:LangGraph + Eval评估体系实战,解决 Agent 优化全靠感觉的问题

本文介绍了一个为LangGraph Coding Agent设计的评估体系解决方案。文章首先指出了当前AI Coding Agent开发中的常见问题:优化过程缺乏客观评估标准,导致改进效果难以量化。随后提出了一个完整的评估框架,包括评估指标(任务成功率、质量指标、安全指标、成本指标和流程指标)、项目结构设计和评估案例集。 核心内容包括: 设计了JSON格式的评估案例集,覆盖正常任务、边界任务和高风险场景 实现了模拟Agent输出的演示代码 构建了多维度评分系统,包括关键词匹配、禁用词检测和成本评估 提供了评

sundong_81的博客 47

推荐开源项目:SQLLine 1.12.0 - 简洁高效的SQL命令行工具

推荐开源项目:SQLLine 1.12.0 - 简洁高效的SQL命令行工具 项目地址:https://gitcode.com/gh_mirrors/sq/sqlline 1、项目介绍 SQLLine是一款轻量级且功能强大的SQL命令行工具,版本1.12.0带来了更多改进和优化。它旨在为数据库操作提供一个简单易用的接口,让数据查询、管理变得轻松快捷。无论你是开发人员、数据分析师或是DBA,SQLLi...

gitblog_00062的博客 1035

情感分析在金融领域的应用:舆情影响股价预测

我们都知道,股价的波动受到多种因素的影响,而大众的情绪和看法就是其中很重要的一部分。我们在日常生活中都有根据别人的评价来判断事物好坏的经验,在金融市场里,舆情就像是大众对某家公司的集体评价,它能反映出市场参与者的情绪和预期。学习这方面的知识,能帮助投资者更好地把握市场动态,做出更明智的投资决策,无论是个人投资者还是金融机构的分析师,都能从中受益。有一天,你看到某家知名企业被曝光了严重的产品质量问题,新闻铺天盖地,股民们也都在热议纷纷,负面情绪弥漫。这时,你会不会觉得这家公司的股价可能要下跌了呢?

AI 原生应用开发的博客 1147

Migaku Japanese Addon 使用教程

Migaku Japanese Addon 使用教程 1. 项目介绍 Migaku Japanese Addon 是一款专为学习日语设计的 Anki 插件。它可以为日语单词添加读音和音调信息,支持点击单词播放音频,导出音调图,转换为片假名,或历史性的汉字和假名等多种功能。该插件旨在帮助用户更轻松地学习日语。 2. 项目快速启动 安装 Anki 首先,您需要安装 Anki 学习软件。请确保下载与 M...

gitblog_00169的博客 1185

UniRx之基本语法格式

想要更好的去学习UniRx,我们最好是先理解UniRx 的语法格式。就像我们去学习英语或者汉语一样,理解了基本的语法规则后,再学其他的就信手拈来了。

TxNet 924

基于STM32设计的计算器(实现基本运算)

当前文章介绍的是STM32+LCD触摸屏设计的一个触摸计算器功能,实现基本的加减乘除,二进制转换显示等功能。LCD屏使用的是3.5寸带触摸屏的显示屏,方便操作屏幕,MCU采用STM32F103ZET6。

8817

Kotlin Multiplatform超详细介绍,一篇就够了

本文对 KMP 做了详细介绍,包含它的基础能力以及编译过程。并从开发者最关注的性能、灵活性、生态、可靠性几个角度,简述了 KMP 的优势。包含了多个大厂的实战案例,和 Kotlin,KMP 的发展历史,以佐证其可靠性。阅读此文你将全方位了解 KMP 技术。

qq_40630019的博客 8327

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

告别多端适配烦恼:用Compose-Multiplatform构建跨平台SQL工具UI

你是否还在为数据库管理工具的多平台适配而头疼?既要保证Android端的流畅操作,又要兼顾iOS的界面规范,开发成本翻倍却效果不佳?本文将带你用JetBrains Compose-Multiplatform框架,从零开始打造一套跨平台的SQL工具UI,一次编码即可运行在Android、iOS和桌面端,彻底解决多端适配难题。 读完本文你将掌握: - 用Compose-Multiplatform构建...

gitblog_00149的博客 445

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

Compose也能跨平台?Compose Multiplatform是啥?KMM又是什么?

现在的跨平台框架真是跟打了鸡血似的,跟生产队的驴一样玩命的更新啊,一会儿功夫就遍地开花,开发者尤其是Androiders们还能学得过来吗?

川峰的博客 3123

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

Sql Line操作使用Hbase

应用场景 按照 完全分布式Hadoop集群安装Phoenix博文,安装了phoenix后,可以通过普通的SQL方式,操作HBASE,使hbase分布式非关系型数据库,更加易于用户的使用。 操作步骤 1. 启动phoenix # sqlline.py hadoop0...

weixin_33847182的博客 1114

通用的防止SQL注入代码

新建sqllin.asp网页文件,编写以下代码检查POST和GET方式提交的所有数据,如果发现有过滤字符,则显示弹出对话框,并中断程序的运行Dim   SQL_Post,SQL_Get,strFilter,aFilter,istrFilter=" |;|and|(|)|exec|insert|select|delete|update|count|*|%|chr|mid|master|trunc

思索的蜗牛的专栏 747

SQL LINK

SQL LINK 操作符    LINK操作符用于在WHERE子句中搜索列中的指定模式 SQL LIKE语法 SELECT column_name(s) FROM table_name WHERE column_name LIKE pattern;   实例   SELECT * FROM Websites WHERE name LIKE 'G%'; //选取name以“G”开始...

冷月醉雪的博客 6601

如何使用 Kotlin Multiplatform 构建跨平台应用程序

KMM 或 Kotlin Multiplatform Mobile 是用于开发跨平台应用程序的 SDK。可以创建多平台应用程序并与 android 和 iOS 平台共享它们。然而,Kotlin Multiplatform Mobile 的常见用例包含原生 UI 的实现或使用特定于平台的 API。开发和维护移动应用程序需要投入大量精力和精力。但是,在为不同平台(例如(iOS、Android 等)创建和测试跨平台移动应用程序时)存在各种挑战。在当今的数字世界中,跨平台构建应用程序是最苛刻的应用程序开发过程。

xiaokangss的博客 2144

百度开源文心5.5 Flash Base:280B MoE 只激活 14B,4 步部署 + 3 个坑,微调还是等指令版

百度开源文心 5.5 Flash 基座权重:280B MoE 每 token 激活 14B,1M 上下文,同尺寸开源 Base 30 项基准多数领先。4 步部署(vLLM/SGLang completions 链路)+ 3 个高频坑 + 动手前 6 点核对清单,并给出微调 Base 还是等指令版的判断线。

weixin_50937681的博客 259

开源密码管理与双因素认证工具离线镜像合集

本集合汇总等开源密码管理与双因素认证相关离线包。侧重密码箱与 2FA,与「安全审计/渗透扫描」合集错开。

229
上一篇: 干货 | 携程代码分析平台,快速实现精准测试与应用瘦身
下一篇: 干货 | 大语言模型插件功能在携程的python实践
携程技术
博客等级 码龄10年 3716粉丝 324原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值