深入理解 Kotlin 协程:从语法到 Kuikly 实战

35 阅读7分钟

本文面向 Kotlin 协程初学者,从底层原理到实战应用,全面讲解协程在 Kuikly 中的使用


引言:为什么需要协程

想象一个场景:你需要先获取用户信息,再根据用户信息获取订单列表,最后更新 UI。

传统回调方式:

// 回调地狱 😱
getUserInfo { user ->
    getOrderList(user.id) { orders ->
        getOrderDetail(orders[0].id) { detail ->
            // 终于拿到数据了...
            updateUI(detail)
        }
    }
}

这种代码的问题:

  1. 嵌套层级深:难以阅读和维护
  2. 错误处理困难:每层都需要处理错误
  3. 无法方便地取消:如果用户离开页面,这些回调可能仍在执行

协程方式:

// 清晰优雅 ✨
lifecycleScope.launch {
    try {
        val user = getUserInfo()
        val orders = getOrderList(user.id)
        val detail = getOrderDetail(orders[0].id)
        updateUI(detail)
    } catch (e: Exception) {
        showError(e)
    }
}

协程让异步代码看起来像同步代码,这就是它的魔力!


第一部分:协程的本质

什么是协程

协程(Coroutine)= Co(协作)+ Routine(例程/函数)

简单说,协程是一种可以暂停和恢复执行的函数。与普通函数不同,协程在执行过程中可以"暂停",把控制权交给其他代码,等条件满足后再"恢复"执行。

┌──────────────────────────────────────────────────────────┐
│                    普通函数执行流程                        │
├──────────────────────────────────────────────────────────┤
│                                                          │
│   开始 ──────────────────────────────────────────► 结束   │
│         (一口气执行完,不能中断)                           │
│                                                          │
└──────────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────────┐
│                    协程执行流程                            │
├──────────────────────────────────────────────────────────┤
│                                                          │
│   开始 ────► 挂起点1 ····暂停···· 恢复 ────► 挂起点2 ····  │
│                                                          │
│             ····暂停···· 恢复 ──────────────────► 结束    │
│                                                          │
└──────────────────────────────────────────────────────────┘

协程 vs 线程

特性线程协程
调度者操作系统程序自身
创建成本高(约 1MB 栈内存)低(约几十字节)
切换成本高(内核态切换)低(用户态切换)
数量限制通常几千个可以创建数十万个
阻塞影响阻塞整个线程只暂停当前协程

关键区别:阻塞 vs 挂起

// 线程阻塞:整个线程被占用,不能做其他事
Thread.sleep(1000)  // 线程在这 1 秒内什么都不能做

// 协程挂起:只是暂停协程,线程可以去执行其他任务
delay(1000)  // 协程暂停,线程可以去执行其他协程

挂起函数(suspend)的魔法

suspend 关键字是 Kotlin 协程的核心。它告诉编译器:这个函数可能会暂停执行。

// 普通函数
fun normalFunction(): String {
    return "Hello"
}

// 挂起函数
suspend fun suspendFunction(): String {
    delay(1000)  // 可以调用其他挂起函数
    return "Hello after 1 second"
}

suspend 的本质是什么?

编译器会将挂起函数转换为带有 Continuation 参数的函数:

// 你写的代码
suspend fun fetchData(): String {
    delay(1000)
    return "data"
}

// 编译器转换后(简化版)
fun fetchData(continuation: Continuation<String>): Any {
    // ... 状态机代码
}

Continuation(续体)就是"接下来要做什么"的封装。这是协程实现的关键,我们稍后会详细讲解。


第二部分:Kotlin 协程核心概念

Continuation:协程的灵魂

Continuation 是 Kotlin 标准库中定义的接口:

// Kotlin 标准库中的定义
public interface Continuation<in T> {
    // 协程的上下文
    public val context: CoroutineContext
    
    // 恢复协程执行,传入结果
    public fun resumeWith(result: Result<T>)
}

Continuation 做什么?

它封装了两个东西:

  1. 协程的上下文:包含调度器、Job 等信息
  2. 恢复执行的能力:通过 resumeWith 让协程继续运行

让我们用一个比喻理解它:

想象你在读一本书,读到一半要去做饭:

普通函数:
  - 合上书,做完饭后忘了读到哪里了
  
协程 + Continuation:
  - 在书里夹一个书签(Continuation)
  - 书签记录了:当前页码、你的阅读进度、理解状态
  - 做完饭后,打开书签就能继续读

两个常用的扩展函数:

// 成功恢复
fun <T> Continuation<T>.resume(value: T) {
    resumeWith(Result.success(value))
}

// 异常恢复
fun <T> Continuation<T>.resumeWithException(exception: Throwable) {
    resumeWith(Result.failure(exception))
}

CoroutineScope:协程的作用域

CoroutineScope 定义了协程的生命周期范围:

// Kuikly 中的定义
interface CoroutineScope {
    val coroutineContext: CoroutineContext
}

为什么需要作用域?

作用域解决了协程的生命周期管理问题:

// 问题:用户离开页面后,协程还在运行
GlobalScope.launch {
    while (true) {
        delay(1000)
        updateUI()  // 页面都销毁了,还在更新 UI!
    }
}

// 解决:使用绑定生命周期的作用域
lifecycleScope.launch {
    while (true) {
        delay(1000)
        updateUI()  // 页面销毁时,协程自动取消
    }
}

Job:协程的生命周期

Job 是协程的句柄,用于管理协程的生命周期:

// Kuikly 中的定义
interface Job : CoroutineContext.Element {
    companion object Key : CoroutineContext.Key<Job>
}

launch 函数返回一个 Job,你可以用它来控制协程:

val job = GlobalScope.launch {
    repeat(1000) { i ->
        println("执行中: $i")
        delay(500)
    }
}

// 稍后取消协程
job.cancel()

Deferred:带返回值的协程

Deferred 继承自 Job,额外提供了获取返回值的能力:

// Kuikly 中的定义
interface Deferred<out T> : Job {
    suspend fun await(): T  // 等待结果
}

使用示例:

// async 返回 Deferred
val deferred: Deferred<String> = GlobalScope.async {
    delay(1000)
    "计算结果"
}

// 在其他地方等待结果
GlobalScope.launch {
    val result = deferred.await()  // 挂起直到结果可用
    println(result)  // 输出:计算结果
}

第三部分:Kuikly 协程实现剖析

为什么 Kuikly 要自己实现协程

Kuikly 实现了一套简化版的协程系统,而不是直接使用 kotlinx.coroutines 库。原因如下:

特性Kuikly 内建协程kotlinx.coroutines
包大小无额外依赖增加约 1MB
动态化支持✅ 支持❌ 不支持
线程安全单线程,自动保障需要开发者考虑
功能丰富度基础功能完整功能

Kuikly 协程的设计理念:

所有协程都在 Kuikly 线程中执行,没有线程切换,没有线程安全问题。

launch 函数源码解析

让我们逐行解析 launch 函数:

// 来自 Builders.kt
fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.ATOMIC,
    block: suspend CoroutineScope.() -> Unit
): Job {
    // 1. 创建一个独立协程对象
    val job = StandaloneCoroutine(context)
    
    // 2. 启动协程
    job.start(start, this) {
        try {
            // 3. 执行你传入的协程代码
            block.invoke(this)
        } catch (e: Throwable) {
            // 4. 协程内的异常需要特殊处理
            throwCoroutineScopeException(e)
        }
    }
    
    // 5. 返回 Job 供外部控制
    return job
}

参数详解:

  1. context: CoroutineContext

    • 协程上下文,可以携带额外信息
    • 默认是 EmptyCoroutineContext(空上下文)
  2. start: CoroutineStart

    • 启动模式,Kuikly 只支持 ATOMIC(立即执行)
    • 标准库还有 LAZY(延迟启动)等模式
  3. block: suspend CoroutineScope.() -> Unit

    • 这是你要执行的协程代码
    • suspend:表示这是挂起函数
    • CoroutineScope.():表示 Lambda 内部可以访问 CoroutineScope 的成员

CoroutineStart 的 invoke 操作符:

// 来自 CoroutineStart.kt
enum class CoroutineStart {
    ATOMIC;

    operator fun <R, T> invoke(
        block: suspend R.() -> T,
        receiver: R,
        completion: Continuation<T>
    ): Unit =
        when (this) {
            ATOMIC -> block.startCoroutine(receiver, completion)
        }
}

block.startCoroutine(receiver, completion) 是 Kotlin 标准库提供的函数,它:

  1. 创建协程的状态机
  2. receiver 作为接收者执行 block
  3. 执行完成后调用 completion.resumeWith(result)

async/await 机制解析

asynclaunch 多了返回值的处理:

fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.ATOMIC,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    // 使用 DeferredCoroutine 而不是 StandaloneCoroutine
    val job = DeferredCoroutine<T>(context)
    job.start(start, this) {
        try {
            block.invoke(this)  // 返回值会被 DeferredCoroutine 捕获
        } catch (e: Throwable) {
            throwCoroutineScopeException(e)
            val res: T? = null
            res!!  // 这行只是为了满足编译器,异常已经抛出了
        }
    }
    return job
}

DeferredCoroutine 的实现(重点!):

internal class DeferredCoroutine<T>(
    parentContext: CoroutineContext
) : AbstractCoroutine<T>(parentContext), Deferred<T> {
    
    // 存储等待结果的回调列表
    private var suspendCoroutineResumeTasks = fastArrayListOf<(T) -> Unit>()
    
    // 标记是否已经有结果
    private var didSetResultValue = false
    
    // 存储结果值
    private var resumeResultValue: T? = null
        set(value) {
            field = value
            didSetResultValue = true
        }

    // await 的入口
    override suspend fun await(): T = awaitInternal()

    // 当协程完成时被调用(来自 Continuation 接口)
    override fun resumeWith(result: Result<T>) {
        if (result.isSuccess) {
            // 保存结果
            resumeResultValue = result.getOrNull()
            // 通知所有等待者
            suspendCoroutineResumeTasks.forEach { callback ->
                callback.invoke(resumeResultValue as T)
            }
            suspendCoroutineResumeTasks.clear()
        } else {
            throw RuntimeException("result failure:" + result.exceptionOrNull())
        }
    }

    // 内部实现:快速路径和慢速路径
    private suspend fun awaitInternal(): T {
        if (didSetResultValue) {
            // 快速路径:结果已经有了,直接返回
            return resumeResultValue as T
        }
        // 慢速路径:需要挂起等待
        return awaitSuspend()
    }

    // 慢速路径:挂起当前协程,等待结果
    private suspend fun awaitSuspend(): T = suspendCoroutine { continuation ->
        // 把恢复协程的回调加入等待列表
        this.suspendCoroutineResumeTasks.add { value ->
            continuation.resume(value)
        }
    }
}

工作流程图解:

                    async { ... }
                         │
                         ▼
              ┌─────────────────────┐
              │  DeferredCoroutine  │
              │  创建并开始执行      │
              └─────────────────────┘
                         │
         ┌───────────────┴───────────────┐
         │                               │
         ▼                               ▼
   协程还在执行...                  协程执行完成
         │                               │
         │                               ▼
         │                    resumeWith(result)
         │                               │
         │                               ▼
         │                    didSetResultValue = true
         │                    resumeResultValue = 结果值
         │                               │
         ▼                               │
    await() 被调用                        │
         │                               │
         ▼                               │
   didSetResultValue?                    │
         │                               │
    ┌────┴────┐                          │
    │         │                          │
   true     false                        │
    │         │                          │
    ▼         ▼                          │
  直接返回   awaitSuspend()               │
  结果值         │                        │
                ▼                        │
          挂起,加入等待列表 ◄─────────────┘
                │                   当结果可用时
                ▼                   通知所有等待者
          恢复,返回结果值

delay 的实现原理

delay 函数让协程暂停指定时间:

suspend fun CoroutineScope.delay(timeMs: Int) {
    // 1. 确定 pagerId(用于定时器管理)
    val pagerId = if (this is LifecycleScope) {
        pagerScope.pagerId
    } else {
        BridgeManager.currentPageId.ifEmpty { return }
    }
    
    // 2. 使用 suspendCoroutine 挂起协程
    suspendCoroutine<Unit> { continuation ->
        // 3. 设置定时器
        setTimeout(pagerId, timeMs) {
            try {
                // 4. 定时器到期后恢复协程
                continuation.resume(Unit)
            } catch (e: Throwable) {
                throwCoroutineScopeException(e)
            }
        }
    }
}

核心要点:

  1. suspendCoroutine 挂起当前协程,拿到 continuation
  2. setTimeout 设置原生定时器
  3. 定时器回调中调用 continuation.resume(Unit) 恢复协程

第四部分:suspendCoroutine 深度解析

回调地狱问题

假设我们有一个回调式的网络请求 API:

fun requestGet(
    url: String, 
    params: JSONObject, 
    callback: (data: JSONObject, success: Boolean, error: String?) -> Unit
) {
    // 原生实现...
}

如果需要顺序执行多个请求:

// 回调地狱 😱
requestGet(url1, params1) { data1, success1, error1 ->
    if (success1) {
        requestGet(url2, JSONObject().put("id", data1.getString("id"))) { data2, success2, error2 ->
            if (success2) {
                requestGet(url3, JSONObject().put("token", data2.getString("token"))) { data3, success3, error3 ->
                    if (success3) {
                        // 终于拿到最终数据了...
                        updateUI(data3)
                    } else {
                        showError(error3)
                    }
                }
            } else {
                showError(error2)
            }
        }
    } else {
        showError(error1)
    }
}

suspendCoroutine 的工作原理

suspendCoroutine 是桥接回调式 API 和协程的关键函数:

// Kotlin 标准库中的定义
public suspend inline fun <T> suspendCoroutine(
    crossinline block: (Continuation<T>) -> Unit
): T

它做了什么?

  1. 挂起当前协程
  2. Continuation 对象传给你的 block
  3. 你在 block 中决定何时调用 continuation.resume(value) 恢复协程

图解:

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│   协程执行中...                                               │
│        │                                                    │
│        ▼                                                    │
│   遇到 suspendCoroutine { continuation -> ... }              │
│        │                                                    │
│        ▼                                                    │
│   ┌────────────────────────────────────┐                   │
│   │  协程挂起!                         │                   │
│   │  把 Continuation 交给你的 block     │                   │
│   └────────────────────────────────────┘                   │
│        │                                                    │
│        ▼                                                    │
│   你在 block 中做异步操作...                                  │
│   (比如发起网络请求)                                         │
│        │                                                    │
│        ▼                                                    │
│   异步操作完成,调用 continuation.resume(result)              │
│        │                                                    │
│        ▼                                                    │
│   ┌────────────────────────────────────┐                   │
│   │  协程恢复!                         │                   │
│   │  suspendCoroutine 返回 result       │                   │
│   └────────────────────────────────────┘                   │
│        │                                                    │
│        ▼                                                    │
│   协程继续执行...                                             │
│                                                             │
└─────────────────────────────────────────────────────────────┘

实战:封装网络请求

将回调式 API 转换为挂起函数的标准模式:

// 原始的回调式 API
fun requestGet(
    url: String,
    params: JSONObject,
    callback: (data: JSONObject, success: Boolean, errorMsg: String?, code: Int) -> Unit
)

// 封装为挂起函数
private suspend fun requestGet(url: String, params: JSONObject): JSONObject =
    suspendCoroutine { continuation ->
        // 调用原始 API
        acquireModule<NetworkModule>(NetworkModule.MODULE_NAME).requestGet(
            url,
            params
        ) { data, success, errorMsg, _ ->
            if (success) {
                // 成功:恢复协程并返回数据
                continuation.resume(data)
            } else {
                // 失败:恢复协程并抛出异常
                continuation.resumeWithException(
                    IllegalStateException("请求失败: $errorMsg")
                )
            }
        }
    }

使用封装后的挂起函数:

// 现在可以像同步代码一样使用了!
lifecycleScope.launch {
    try {
        val user = requestGet("https://api.example.com/user", JSONObject())
        val orders = requestGet(
            "https://api.example.com/orders",
            JSONObject().put("userId", user.getString("id"))
        )
        val detail = requestGet(
            "https://api.example.com/order/detail",
            JSONObject().put("orderId", orders.getJSONArray("list").getJSONObject(0).getString("id"))
        )
        
        // 更新 UI
        orderDetail = detail
    } catch (e: Exception) {
        errorMessage = e.message ?: "未知错误"
    }
}

通用封装模板:

/**
 * 将任何回调式 API 转换为挂起函数的通用模板
 * @param T 成功时的返回类型
 */
suspend fun <T> suspendCallback(
    block: (onSuccess: (T) -> Unit, onError: (Exception) -> Unit) -> Unit
): T = suspendCoroutine { continuation ->
    block(
        // 成功回调
        { result -> continuation.resume(result) },
        // 失败回调
        { error -> continuation.resumeWithException(error) }
    )
}

// 使用模板
suspend fun fetchUserInfo(): User = suspendCallback { onSuccess, onError ->
    userApi.getInfo(
        onSuccess = { onSuccess(it) },
        onError = { onError(it) }
    )
}

第五部分:Kuikly 中的协程实战

GlobalScope vs LifecycleScope

Kuikly 提供了两种协程作用域:

1. GlobalScope(全局作用域)

object GlobalScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

特点:

  • 不与任何生命周期绑定
  • 协程会一直运行直到完成或手动取消
  • ⚠️ 使用不当可能导致内存泄漏

2. LifecycleScope(生命周期作用域)

class LifecycleScope(internal val pagerScope: PagerScope) : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

// 在 Pager 中使用
abstract class Pager {
    override val lifecycleScope: LifecycleScope by lazy(LazyThreadSafetyMode.NONE) {
        LifecycleScope(this)
    }
}

特点:

  • 与页面生命周期绑定
  • 页面销毁时自动清理资源
  • ✅ 推荐在页面中使用

使用建议:

// ❌ 不推荐:GlobalScope 可能导致泄漏
GlobalScope.launch {
    while (true) {
        delay(1000)
        updateUI()  // 页面销毁后还在执行!
    }
}

// ✅ 推荐:lifecycleScope 自动管理
lifecycleScope.launch {
    while (true) {
        delay(1000)
        updateUI()  // 页面销毁时自动停止
    }
}

网络请求最佳实践

完整示例:

@Page("UserProfilePage")
internal class UserProfilePage : BasePager() {
    // 响应式状态
    private var isLoading by observable(false)
    private var userData by observable<UserData?>(null)
    private var errorMsg by observable<String?>(null)

    override fun body(): ViewBuilder {
        val ctx = this
        return {
            // ... UI 代码
            vif({ ctx.isLoading }) {
                LoadingView { }
            }
            vif({ ctx.userData != null }) {
                UserInfoCard {
                    attr { data = ctx.userData }
                }
            }
            vif({ ctx.errorMsg != null }) {
                ErrorView {
                    attr { message = ctx.errorMsg }
                    event {
                        onRetry { ctx.fetchUserData() }
                    }
                }
            }
        }
    }

    override fun created() {
        super.created()
        fetchUserData()
    }

    private fun fetchUserData() {
        lifecycleScope.launch {
            isLoading = true
            errorMsg = null
            
            try {
                // 并行请求用户基本信息和统计数据
                val userInfoDeferred = GlobalScope.async { fetchUserInfo() }
                val statsDeferred = GlobalScope.async { fetchUserStats() }
                
                // 等待两个请求都完成
                val userInfo = userInfoDeferred.await()
                val stats = statsDeferred.await()
                
                // 合并数据
                userData = UserData(userInfo, stats)
            } catch (e: Exception) {
                errorMsg = "加载失败: ${e.message}"
            } finally {
                isLoading = false
            }
        }
    }

    // 封装的挂起函数
    private suspend fun fetchUserInfo(): JSONObject = suspendCoroutine { cont ->
        acquireModule<NetworkModule>(NetworkModule.MODULE_NAME)
            .requestGet("https://api.example.com/user", JSONObject()) { data, success, error, _ ->
                if (success) cont.resume(data)
                else cont.resumeWithException(Exception(error))
            }
    }

    private suspend fun fetchUserStats(): JSONObject = suspendCoroutine { cont ->
        acquireModule<NetworkModule>(NetworkModule.MODULE_NAME)
            .requestGet("https://api.example.com/user/stats", JSONObject()) { data, success, error, _ ->
                if (success) cont.resume(data)
                else cont.resumeWithException(Exception(error))
            }
    }
}

定时任务与动画

实现定时器:

class Timer {
    private var isRunning = false
    private lateinit var action: () -> Unit
    private var delay: Int = 0
    private var period: Int = 0

    fun schedule(delay: Int, period: Int, action: () -> Unit) {
        this.delay = delay
        this.period = period
        this.action = action
        start()
    }

    fun cancel() {
        isRunning = false
    }

    private fun start() {
        if (isRunning) return
        isRunning = true
        GlobalScope.launch {
            delay(delay)  // 初始延迟
            while (isRunning) {
                action()
                delay(period)  // 周期延迟
            }
        }
    }
}

// 使用示例
val timer = Timer()
timer.schedule(delay = 0, period = 1000) {
    count++  // 每秒增加
}

// 停止定时器
timer.cancel()

实现倒计时:

@Page("CountdownPage")
internal class CountdownPage : BasePager() {
    private var seconds by observable(60)

    override fun created() {
        super.created()
        lifecycleScope.launch {
            while (seconds > 0) {
                delay(1000)
                seconds--
            }
            // 倒计时结束
            onCountdownFinished()
        }
    }
}

实现流式文字效果:

@Page("TypewriterPage")
internal class TypewriterPage : BasePager() {
    private var displayText by observable("")
    private val fullText = "这是一段会逐字显示的文本..."

    override fun created() {
        super.created()
        lifecycleScope.launch {
            for (i in fullText.indices) {
                displayText = fullText.substring(0, i + 1)
                delay(50)  // 每 50ms 显示一个字
            }
        }
    }
}

多协程协作模式

模式 1:顺序执行

lifecycleScope.launch {
    val step1Result = doStep1()
    val step2Result = doStep2(step1Result)
    val step3Result = doStep3(step2Result)
    updateUI(step3Result)
}

模式 2:并行执行

lifecycleScope.launch {
    // 同时启动多个异步任务
    val deferred1 = GlobalScope.async { fetchData1() }
    val deferred2 = GlobalScope.async { fetchData2() }
    val deferred3 = GlobalScope.async { fetchData3() }
    
    // 等待所有任务完成
    val result1 = deferred1.await()
    val result2 = deferred2.await()
    val result3 = deferred3.await()
    
    // 合并结果
    updateUI(result1, result2, result3)
}

模式 3:多协程等待同一结果

// 只执行一次的初始化任务
private val initDeferred: Deferred<Config> by lazy {
    GlobalScope.async {
        delay(2000)  // 模拟耗时初始化
        loadConfig()
    }
}

// 多个地方可以等待同一个结果
fun useConfig1() {
    lifecycleScope.launch {
        val config = initDeferred.await()  // 等待初始化
        doSomething1(config)
    }
}

fun useConfig2() {
    lifecycleScope.launch {
        val config = initDeferred.await()  // 复用同一个结果
        doSomething2(config)
    }
}

Kuikly 示例代码中的多协程等待:

@Page("CoroutineExamplePage")
internal class CoroutineExamplePage : BasePager() {
    private var count by observable(0)

    override fun created() {
        super.created()
        
        // 创建一个异步任务
        val deferred = GlobalScope.async {
            delay(3000)  // 延迟 3 秒
        }
        
        // 多个协程等待同一个 deferred
        GlobalScope.launch {
            deferred.await()
            count += 1  // 3 秒后执行
        }
        GlobalScope.launch {
            deferred.await()
            count += 1  // 同时执行
        }
        GlobalScope.launch {
            deferred.await()
            delay(1000)  // 再等 1 秒
            count += 1  // 4 秒后执行
        }
        
        // 最终效果:
        // t=3s: count 变为 2
        // t=4s: count 变为 3
    }
}

第六部分:线程安全与最佳实践

Kuikly 的单线程模型

Kuikly 内建协程的一个重要特性是:所有协程都在 Kuikly 线程中执行

这意味着:

  • ✅ 不需要考虑线程同步
  • ✅ 不需要使用锁
  • ✅ 可以直接更新响应式状态
// 这样写是安全的!
lifecycleScope.launch {
    val data = fetchData()
    userData = data  // 直接更新响应式属性,无需担心线程安全
}

线程安全验证机制

Kuikly 提供了验证机制来帮助发现问题:

@Page("DebugPage")
internal class DebugPage : BasePager() {
    
    override fun willInit() {
        super.willInit()
        
        // 开启线程验证
        Pager.VERIFY_THREAD = true
        
        // 开启响应式观察者验证
        Pager.VERIFY_REACTIVE_OBSERVER = true
        
        // 自定义验证失败处理
        Pager.verifyFailed { exception ->
            // 记录日志
            println("线程安全验证失败: ${exception.message}")
            // 在调试模式下抛出异常
            throw exception
        }
    }
}

最佳实践清单

1. 优先使用 lifecycleScope

// ✅ 推荐
lifecycleScope.launch { ... }

// ⚠️ 谨慎使用
GlobalScope.launch { ... }

2. 将回调式 API 封装为挂起函数

// ✅ 封装后
private suspend fun fetchData(): Data = suspendCoroutine { cont ->
    api.fetch { data, success, error ->
        if (success) cont.resume(data)
        else cont.resumeWithException(Exception(error))
    }
}

3. 使用 try-catch 处理异常

lifecycleScope.launch {
    try {
        val data = fetchData()
        processData(data)
    } catch (e: Exception) {
        showError(e.message)
    }
}

4. 并行请求提升性能

// ❌ 顺序执行(耗时长)
val data1 = fetch1()  // 1s
val data2 = fetch2()  // 1s
val data3 = fetch3()  // 1s
// 总耗时 3s

// ✅ 并行执行(耗时短)
val d1 = async { fetch1() }
val d2 = async { fetch2() }
val d3 = async { fetch3() }
val data1 = d1.await()
val data2 = d2.await()
val data3 = d3.await()
// 总耗时 1s

5. 在 created() 中启动协程

override fun created() {
    super.created()
    // 在这里启动数据加载协程
    lifecycleScope.launch {
        loadData()
    }
}

总结

核心概念回顾

概念说明
suspend标记函数可以挂起,是协程的基础
Continuation协程的"书签",封装了恢复执行的能力
CoroutineScope协程的作用域,管理协程生命周期
Job协程的句柄,可用于取消协程
Deferred带返回值的 Job,通过 await() 获取结果
suspendCoroutine桥接回调 API 和协程的关键函数

Kuikly 协程特点

  1. 轻量级:不依赖 kotlinx.coroutines,包大小无额外增加
  2. 单线程:所有协程在 Kuikly 线程执行,无线程安全问题
  3. 支持动态化:可在动态化场景中使用
  4. API 兼容:提供 launchasyncdelayawait 等常用 API

使用建议

  1. 页面中优先使用 lifecycleScope:自动管理生命周期
  2. 封装回调为挂起函数:使用 suspendCoroutine 消除回调地狱
  3. 并行请求提升性能:使用 async/await 并行执行独立任务
  4. 正确处理异常:使用 try-catch 包裹协程代码
  5. 开启验证机制:在开发阶段使用 VERIFY_THREAD 发现问题