10分钟速览Android开发者需要关注的 Kotlin 更新

1,212 阅读6分钟

2_0.png

Kotlin 2.3.0 带来了稳定的时间 API、显式幕后字段、改进的 Swift 互操作性以及更完善的工具链。

下面我们来一起浏览那些对于 Android 开发者需要关注的新特性。

稳定的时间 API:Clock 与 Instant

kotlin.time.Clockkotlin.time.Instant API 现已正式稳定,无需再添加 @OptIn 注解即可使用。

import kotlin.time.*

// 获取当前时间
val now: Instant = Clock.System.now()

// 计算时间差
val eventTime = Instant.parse("2025-12-25T10:00:00Z")
val timeUntilEvent: Duration = eventTime - now
println("距离活动开始还有:${timeUntilEvent.inWholeHours} 小时")

// 通过时间分量创建 Instant
val deadline = Instant.fromEpochSeconds(1735084800)

// 比较时间
if (now > deadline) {
    println("截止时间已过!")
}

Android 场景建议:用它替代 System.currentTimeMillis(),实现更简洁、类型安全的时间处理。

class SessionManager {
    private var sessionStart: Instant? = null

    fun startSession() {
        sessionStart = Clock.System.now()
    }

    fun getSessionDuration(): Duration {
        val start = sessionStart ?: return Duration.ZERO
        return Clock.System.now() - start
    }
}

显式幕后字段(实验性)

我终于可以摆脱命名两个变量的困局了!

你可以定义与暴露属性类型不同的幕后字段,无需再单独声明私有属性来实现这一点:

@OptIn(ExperimentalBackingFields::class)
class UserPreferences {
    // 幕后字段是可变列表,但对外暴露为不可变列表
    val recentSearches: List<String>
        field = mutableListOf()

    fun addSearch(query: String) {
        recentSearches.add(query) // 直接修改幕后字段
        if (recentSearches.size > 10) {
            recentSearches.removeAt(0)
        }
    }
}

// 使用示例
val prefs = UserPreferences()
prefs.addSearch("kotlin 2.3")
prefs.addSearch("coroutines")
// 外头是无法使用幕后字段的
// prefs.recentSearches.add("flow")
println(prefs.recentSearches) // [kotlin 2.3, coroutines]

Kotlin 2.3 之前你需要这样做(我确实对这个感到厌烦):

class UserPreferences {
    private val _recentSearches = mutableListOf<String>()
    val recentSearches: List<String> get() = _recentSearches.toList()

    fun addSearch(query: String) {
        _recentSearches.add(query)
    }
}

如果你对实现方法感到好奇:

2_2.jpg

其实就是增加了一个 private 的字段用于类的内部使用。

当然,作为实验性功能,你需要添加额外的编译条件:

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xexplicit-backing-fields")
    }
}

未使用返回值检查器(实验性)

当你忘记使用某个接口的返回值时,该功能可以帮你捕获这类不易察觉的潜在 Bug。


// 有潜在问题的用法
@OptIn(ExperimentalUnusedReturnValueChecker::class)
fun badprocess() {
    val list = mutableListOf(1, 2, 3)

    // 警告:'remove' 的返回值未被使用
    list.remove(2) // 你是否需要检查删除是否成功?

    // 警告:'plus' 的返回值未被使用
    list.plus(4) // Bug!此操作不会修改原列表,而是返回一个新列表

   // ...
}

// 正确用法
fun goodprocess() {
    
    val removed = list.remove(2)
    if (!removed) println("元素未找到")

    val newList = list + 4 // 使用返回的新列表
}

当一个表达式返回的不是 UnitNothing 类型的值,且没有被传递给其他函数、用于条件判断或以其他方式使用时,它就会发出警告。

该功能会帮助开发者检查可能存在的 Bug:

表达式Bug修复方案
list.plus(item)返回新列表,不会修改原列表list = list + itemlist.add(item)
string.replace("a", "b")返回新字符串,不会修改原字符串val result = string.replace(...)
map.minus(key)返回新映射,不会修改原映射map = map - key

额外的编译条件:

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xreturn-value-checker=check")
    }
}

增强的 UUID 支持(实验性)

原生支持生成 v4(随机)和 v7(基于时间)类型的 UUID

import kotlin.uuid.*

@OptIn(ExperimentalUuidApi::class)
fun generateIds() {
    // 随机 UUID(v4)
    val randomId = Uuid.random()
    println(randomId) // 示例:550e8400-e29b-41d4-a716-446655440000

    // 基于时间的 UUID(v7)- 可按创建时间排序
    val timeBasedId = Uuid.randomV7()

    // 基于特定时间戳生成 v7 UUID
    val instant = Clock.System.now()
    val historicalId = Uuid.randomV7(instant)

    // 安全解析(无效时返回 null 而非抛出异常)
    val parsed: Uuid? = Uuid.parseOrNull("invalid-uuid")

    // 按时间顺序比较 v7 UUID
    val id1 = Uuid.randomV7()
    delay(100)
    val id2 = Uuid.randomV7()
    println(id1 < id2) // true - v7 UUID 支持按时间排序
}

下面提供一个速查表帮助你如何选择合适的 UUID 版本:

UUID 版本使用场景
v4 (random())通用唯一 ID,无需排序的场景
v7 (randomV7())数据库主键(可排序)、事件 ID 等需要按创建时间排序的场景

额外的编译条件:

kotlin {
    compilerOptions {
        freeCompilerArgs.add("-opt-in=kotlin.uuid.ExperimentalUuidApi")
    }
}

表达式体函数中支持 return

在具有显式返回类型的单表达式函数中,可以使用 return 语句。


// userId 如果为空,函数返回 "default"
fun getDisplayNameOrDefault(userId: String?): String = getDisplayName(userId ?: return "default")

// 复杂校验场景
fun validateEmail(email: String): ValidationResult =
    when {
        email.isBlank() -> return ValidationResult.Error("Email required")
        !email.contains("@") -> return ValidationResult.Error("Invalid format")
        else -> ValidationResult.Success
    }

嵌套类型别名

现在类型别名可以引用其他类型别名。

// 基础类型别名
typealias UserId = String
typealias ProductId = String

// 嵌套类型别名
typealias UserProducts = Map<UserId, List<ProductId>>
typealias UserProductsCallback = (UserProducts) -> Unit

// 使用案例
class ProductRepository {
    fun getUserProducts(callback: UserProductsCallback) {
        val products: UserProducts = mapOf(
            "user_1" to listOf("prod_a", "prod_b"),
            "user_2" to listOf("prod_c")
        )
        callback(products)
    }
}

改进的 Swift 互操作性

Kotlin 枚举现在会导出为原生 Swift 枚举,并且可变参数(vararg)函数可以正常工作。

// Kotlin 代码
enum class PaymentStatus {
    PENDING, COMPLETED, FAILED, REFUNDED
}

fun processPayments(vararg payments: Payment): List<Receipt> {
    return payments.map { process(it) }
}
// Swift 代码 - 现在可以自然调用
let status: PaymentStatus = .completed
switch status {
    case .pending: print("Waiting...")
    case .completed: print("Done!")
    case .failed: print("Error")
    case .refunded: print("Money back")
}

// 可变参数也能直接使用
let receipts = processPayments(payment1, payment2, payment3)

Compose 编译器:Release 版本支持清晰堆栈追踪

借助反混淆的堆栈信息,你可以在生产环境中调试 Compose 的崩溃问题。

// build.gradle.kts
composeCompiler {
    // 启用组键格式的堆栈追踪(2.3 版本默认开启)
    includeSourceInformation = true
}

优化前:Release 包中的崩溃日志是混淆后的,难以定位问题。

优化后:生成清晰的 Compose 组键,可通过 R8 映射文件关联到你的源代码,大幅提升调试效率。

Gradle 与 AGP 更新

我现在看到 AGP 的更新,只想喊田文镜!

版本要求:

工具最低版本最高版本
Gradle7.6.39.0.0
AGP(Android Gradle 插件)8.2.28.13.0
Java825

AGP 9.0+ 的一个重要变更:

// 旧写法 - 在 AGP 9.0+ 中不再生效
plugins {
    id("com.android.application")
    id("kotlin-android") // 需要移除这一行!
}

// 新写法 - kotlin-android 已内置到 AGP 9.0+
plugins {
    id("com.android.application")
    // Kotlin 支持已自动启用
}

数据流的穷举 when 表达式

更智能的穷举检查,能够理解你的代码执行流程。

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

fun handleResult(result: Result<String>) {
    // 编译器能识别所有分支已被覆盖
    when (result) {
        is Result.Success -> println(result.data)
        is Result.Error -> println(result.message)
        Result.Loading -> println("Loading...")
        // 无需写 'else' 分支 — 编译器会验证穷举性
    }
}

// 具备数据流感知能力
fun process(result: Result<String>?) {
    if (result == null) return

    // 编译器知道此处 result 不为 null
    when (result) {
        is Result.Success -> {}
        is Result.Error -> {}
        Result.Loading -> {}
    }
}

fun getPermissionLevel(role: UserRole): Int {
    // 覆盖了 when 表达式之外的 Admin 场景
    if (role == UserRole.ADMIN) return 99

    return when (role) {
        UserRole.MEMBER -> 10
        UserRole.GUEST -> 1
        // 你不再需要编写这个 else 分支了 
        // else -> throw IllegalStateException()
    }
}

总结

Kotlin 2.3.0 主要致力于稳定实验性特性,并改进多平台互操作性。

其中,稳定的时间 API 和显式幕后字段是对 Android 开发者而言最亮眼的更新。