【Kotlin 协程修仙录 · 渡劫境 · 后阶】 | 飞升大典:九境归一,铸就工业级协程网络框架

22 阅读7分钟

image_11.png

前言

渡劫中阶已过,你已执掌造化,能亲手锻造优先级调度、令牌桶限流、线程亲和等神兵利器。九境修为——从炼气的挂起初啼,到渡劫的调度器定制——已全部汇聚于你一身。

今日,是你飞升前的最终大典。我们将不再学习新的 API,而是将九境所学熔于一炉,从零开始,铸就一把可以在生产环境中开疆拓土的工业级神兵:一个基于 Kotlin 协程的网络请求框架。

这把神兵将具备以下威能:

  • 自定义调度器:限制并发、支持优先级。
  • Flow 重试与降级:自动重试、指数退避、失败降级。
  • Channel 任务队列:串行化请求,支持取消。
  • StateFlow UI 状态:生命周期安全、粘性状态。
  • 异常处理与日志:统一异常捕获、结构化日志。
  • 完整的单元测试runTest 虚拟时间、TestDispatcher 控制。

这不仅仅是一次代码实战,更是对九境修为的最终检验。当你完成这一讲,你将不再是协程的学习者,而是协程的造物主

准备好飞升了吗?我们开始。

千曲而后晓声,观千剑而后识器。虐它千百遍方能通晓其真意


神兵蓝图:框架架构总览

我们将构建一个名为 CoroutineNetworkFramework 的轻量级网络框架,核心架构如下:

flowchart LR
    subgraph UI[UI 层]
        Compose[Compose Screen]
        Collect[collectAsState]
    end
    
    subgraph ViewModel[ViewModel 层]
        VM[NetworkViewModel]
        State[StateFlow UiState]
        Scope[viewModelScope]
    end
    
    subgraph Framework[框架核心]
        Dispatcher[PriorityLimitedDispatcher]
        Retry[retryWhen 重试]
        Queue[Channel 请求队列]
        Interceptor[日志拦截器]
    end
    
    subgraph Network[网络层]
        Retrofit[Retrofit suspend]
        OkHttp[OkHttp]
    end
    
    UI --> ViewModel --> Framework --> Network
    
    style UI fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style ViewModel fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
    style Framework fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style Network fill:#ffcdd2,stroke:#b71c1c,stroke-width:2px

框架的核心设计原则:

  1. 单一职责:调度、重试、队列、状态各司其职。
  2. 可组合:各模块通过协程的 CoroutineContextFlow 组合。
  3. 可测试:所有依赖均可注入,支持 runTest 虚拟时间。

第一重锻造:自定义优先级限流调度器

我们从调度器开始。我们需要一个既能限制最大并发数,又能支持任务优先级的调度器。这可以通过组合 limitedParallelism 和自定义 PriorityDispatcher 实现。

// 优先级任务包装类
data class PrioritizedRunnable(
    val priority: Int,
    val block: Runnable
) : Runnable, Comparable<PrioritizedRunnable> {
    override fun run() = block.run()
    override fun compareTo(other: PrioritizedRunnable): Int = 
        priority.compareTo(other.priority)
}

// 优先级调度器(单线程串行,按优先级执行)
class PriorityDispatcher : CoroutineDispatcher() {
    private val queue = PriorityBlockingQueue<PrioritizedRunnable>()
    private val worker = Thread {
        while (!Thread.interrupted()) {
            queue.take().run()
        }
    }.apply {
        name = "PriorityWorker"
        start()
    }

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        queue.put(PrioritizedRunnable(Int.MAX_VALUE, block))
    }

    fun dispatchWithPriority(priority: Int, block: Runnable) {
        queue.put(PrioritizedRunnable(priority, block))
    }

    fun close() {
        worker.interrupt()
    }
}

// 组合:优先级 + 限流(最多 4 个并发)
class PriorityLimitedDispatcher(
    private val maxParallelism: Int = 4
) : CoroutineDispatcher() {
    private val priorityDispatcher = PriorityDispatcher()
    private val limitedDispatcher = priorityDispatcher.limitedParallelism(maxParallelism)
    
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        limitedDispatcher.dispatch(context, block)
    }
    
    fun dispatchWithPriority(priority: Int, context: CoroutineContext, block: Runnable) {
        priorityDispatcher.dispatchWithPriority(priority) {
            // 通过 limitedDispatcher 执行,确保并发限制
            limitedDispatcher.dispatch(context, block)
        }
    }
    
    fun close() {
        priorityDispatcher.close()
    }
}
flowchart LR
    subgraph Submit[任务提交]
        H[高优先级] --> Q[PriorityBlockingQueue]
        L[低优先级] --> Q
    end
    
    subgraph Worker[单线程 Worker]
        W[按优先级取出]
        Q --> W
    end
    
    subgraph Limited[limitedParallelism 信号量]
        S[最大并发 4]
    end
    
    subgraph Execute[实际执行]
        E[协程体]
    end
    
    W --> S --> E
    
    style Submit fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Worker fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style Limited fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style Execute fill:#e8f5e9,stroke:#388e3c

第二重锻造:Flow 重试与指数退避

网络请求必然面临失败。我们使用 retryWhen 实现指数退避重试策略。

fun <T> retryWithExponentialBackoff(
    maxRetries: Int = 3,
    initialDelayMs: Long = 1000,
    maxDelayMs: Long = 10000,
    factor: Double = 2.0,
    shouldRetry: suspend (Throwable) -> Boolean = { it is IOException }
): Flow<T>.(suspend () -> Flow<T>) -> Flow<T> = { block ->
    var attempt = 0
    block().retryWhen { cause, _ ->
        if (attempt < maxRetries && shouldRetry(cause)) {
            val delayMs = (initialDelayMs * factor.pow(attempt.toDouble())).toLong()
                .coerceAtMost(maxDelayMs)
            delay(delayMs)
            attempt++
            true
        } else {
            false
        }
    }
}

使用示例:

fun fetchUserWithRetry(id: String): Flow<User> = flow {
    emit(api.getUser(id))
}.let { retryBlock ->
    retryWithExponentialBackoff(maxRetries = 3) { retryBlock() }
}.catch { e ->
    emit(User.empty()) // 降级为空用户
}
flowchart TD
    Start[发起请求] --> Request[执行网络调用]
    Request -->|成功| Emit[发射结果]
    Request -->|失败| Retry{重试条件判断}
    Retry -->|满足| Delay[指数退避延迟]
    Delay --> Request
    Retry -->|不满足| Catch[catch 降级处理]
    Catch --> EmitEmpty[发射默认值]
    
    style Start fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Request fill:#c8e6c9,stroke:#2e7d32
    style Retry fill:#fff9c4,stroke:#f9a825
    style Catch fill:#ffcdd2,stroke:#b71c1c

第三重锻造:Channel 请求队列与取消支持

对于需要串行化的请求(如订单提交、支付),我们使用 Channel 构建任务队列。

class RequestQueue(
    private val dispatcher: CoroutineDispatcher = Dispatchers.IO.limitedParallelism(1)
) {
    private val queue = Channel<suspend () -> Unit>(Channel.UNLIMITED)
    private val scope = CoroutineScope(SupervisorJob() + dispatcher)
    
    init {
        scope.launch {
            for (task in queue) {
                task()
            }
        }
    }
    
    fun <T> enqueue(
        priority: Int = Int.MAX_VALUE,
        block: suspend () -> T
    ): Deferred<T> {
        val deferred = CompletableDeferred<T>()
        val task: suspend () -> Unit = {
            try {
                val result = block()
                deferred.complete(result)
            } catch (e: Exception) {
                deferred.completeExceptionally(e)
            }
        }
        // 提交到优先级调度器(需结合上一节的 PriorityLimitedDispatcher)
        if (dispatcher is PriorityLimitedDispatcher) {
            dispatcher.dispatchWithPriority(priority, EmptyCoroutineContext) {
                scope.launch { queue.send(task) }
            }
        } else {
            scope.launch { queue.send(task) }
        }
        return deferred
    }
    
    fun cancelAll() {
        scope.cancel()
    }
}
flowchart LR
    subgraph Enqueue[入队]
        T1[请求1] --> Q[Channel]
        T2[请求2] --> Q
    end
    
    subgraph Worker[队列消费者]
        W[串行处理]
        Q --> W
    end
    
    subgraph Execute[执行]
        W --> E1[网络请求]
        W --> E2[返回 Deferred]
    end
    
    style Enqueue fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Worker fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
    style Execute fill:#e8f5e9,stroke:#388e3c

第四重锻造:StateFlow UI 状态与生命周期安全

将网络请求的结果暴露为 StateFlow,并与 viewModelScope 结合,确保生命周期安全。

class NetworkViewModel(
    private val repository: NetworkRepository
) : ViewModel() {
    
    sealed class UiState {
        object Idle : UiState()
        object Loading : UiState()
        data class Success<T>(val data: T) : UiState()
        data class Error(val message: String, val canRetry: Boolean = true) : UiState()
    }
    
    private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    
    fun <T> execute(
        priority: Int = Int.MAX_VALUE,
        block: suspend () -> T
    ) {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            _uiState.value = try {
                val result = block()
                UiState.Success(result)
            } catch (e: Exception) {
                UiState.Error(e.message ?: "未知错误")
            }
        }
    }
}

配合 Compose UI:

@Composable
fun NetworkScreen(viewModel: NetworkViewModel) {
    val uiState by viewModel.uiState.collectAsState()
    
    when (val state = uiState) {
        UiState.Idle -> Text("等待操作")
        UiState.Loading -> CircularProgressIndicator()
        is UiState.Success<*> -> Text("成功:${state.data}")
        is UiState.Error -> {
            Text("错误:${state.message}")
            if (state.canRetry) {
                Button(onClick = { /* 重试逻辑 */ }) {
                    Text("重试")
                }
            }
        }
    }
}
stateDiagram-v2
    [*] --> Idle
    Idle --> Loading : execute
    Loading --> Success : 请求成功
    Loading --> Error : 请求失败
    Success --> Idle : 用户操作
    Error --> Loading : 重试
    Error --> Idle : 取消

第五重锻造:统一异常处理与日志拦截

通过自定义 CoroutineContext.ElementFlowonEachcatch 实现统一日志。

class LoggingInterceptor : CoroutineContext.Element {
    companion object Key : CoroutineContext.Key<LoggingInterceptor>
    override val key = Key
    
    fun log(level: String, message: String) {
        println("[$level] $message")
    }
}

fun <T> Flow<T>.withLogging(name: String): Flow<T> = this
    .onStart { 
        currentCoroutineContext()[LoggingInterceptor]?.log("INFO", "$name started")
    }
    .onEach { value ->
        currentCoroutineContext()[LoggingInterceptor]?.log("DEBUG", "$name emitted $value")
    }
    .catch { e ->
        currentCoroutineContext()[LoggingInterceptor]?.log("ERROR", "$name failed: ${e.message}")
        throw e
    }
    .onCompletion { cause ->
        val level = if (cause == null) "INFO" else "WARN"
        currentCoroutineContext()[LoggingInterceptor]?.log(level, "$name completed")
    }

// 使用
val loggingContext = LoggingInterceptor()
val flow = networkFlow.withLogging("UserRequest").flowOn(loggingContext)

第六重锻造:完整的单元测试

@OptIn(ExperimentalCoroutinesApi::class)
class NetworkViewModelTest {
    private lateinit var repository: FakeRepository
    private lateinit var viewModel: NetworkViewModel
    private val testDispatcher = StandardTestDispatcher()
    
    @Before
    fun setUp() {
        Dispatchers.setMain(testDispatcher)
        repository = FakeRepository()
        viewModel = NetworkViewModel(repository)
    }
    
    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }
    
    @Test
    fun `execute success updates state to Success`() = runTest {
        repository.setResult("Hello")
        
        viewModel.execute { repository.fetch() }
        advanceUntilIdle()
        
        assertTrue(viewModel.uiState.value is UiState.Success)
    }
    
    @Test
    fun `execute error updates state to Error`() = runTest {
        repository.setError(IOException("Network error"))
        
        viewModel.execute { repository.fetch() }
        advanceUntilIdle()
        
        assertTrue(viewModel.uiState.value is UiState.Error)
    }
    
    @Test
    fun `retry with exponential backoff works`() = runTest {
        var attempts = 0
        val flow = flow {
            attempts++
            if (attempts < 3) throw IOException() else emit("Success")
        }.let { retryBlock ->
            retryWithExponentialBackoff(initialDelayMs = 100) { retryBlock() }
        }
        
        val result = flow.first()
        assertEquals("Success", result)
        assertEquals(3, attempts)
        // 虚拟时间,测试瞬间完成
    }
}
flowchart TD
    subgraph Setup[测试准备]
        SetMain[setMain TestDispatcher]
        CreateFake[创建 Fake 依赖]
    end
    
    subgraph Execute[执行与推进]
        Call[调用被测方法]
        Advance[advanceUntilIdle]
    end
    
    subgraph Assert[断言]
        Verify[验证 StateFlow 值]
    end
    
    Setup --> Execute --> Assert
    
    style Setup fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style Execute fill:#fff3e0,stroke:#f57c00,stroke-width:2px
    style Assert fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px

最终章:九境归一,框架整合示例

// 应用入口:配置全局调度器与日志
val appDispatcher = PriorityLimitedDispatcher(maxParallelism = 4)
val loggingInterceptor = LoggingInterceptor()
val appCoroutineContext = appDispatcher + loggingInterceptor + SupervisorJob()

class MyApplication : Application() {
    val appScope = CoroutineScope(appCoroutineContext)
    
    override fun onTerminate() {
        appDispatcher.close()
        super.onTerminate()
    }
}

// Repository 层使用框架
class UserRepository(
    private val api: UserApi,
    private val appScope: CoroutineScope
) {
    fun getUser(id: String): Flow<User> = flow {
        emit(api.getUser(id))
    }.retryWithExponentialBackoff { 
        // 重试逻辑
    }.withLogging("GetUser").flowOn(appScope.coroutineContext)
}

// ViewModel 层使用队列与状态
class UserViewModel(
    private val repository: UserRepository,
    private val requestQueue: RequestQueue
) : NetworkViewModel() {
    fun loadUser(id: String, isUrgent: Boolean = false) {
        val priority = if (isUrgent) 1 else 100
        requestQueue.enqueue(priority) {
            repository.getUser(id).first()
        }.invokeOnCompletion { cause ->
            if (cause != null) {
                _uiState.value = UiState.Error(cause.message ?: "Failed")
            }
        }
    }
}

飞升结语:九境修仙路,协程大道成

道友,你已走完了从炼气到渡劫的九重境界。我们一同回顾这条修仙之路:

境界核心修为法器
炼气境挂起函数、launchJob、结构化并发、CPS 原理suspend 关键字、viewModelScope
筑基境CoroutineContextDispatcherwithContext、异常处理Job 树、SupervisorJob
金丹境async/awaitCoroutineStartsupervisorScopeDeferred、并发组合
元婴境Flow 冷流、操作符、背压flow {}bufferconflate
化神境StateFlowSharedFlowstateIn/shareIn热流、事件总线
炼虚境Channelselectactorproduce协程间通信
合体境WorkManagerRoomRetrofitCompose 集成Android 架构融合
大乘境调试、测试、线程池、limitedParallelism、泄漏排查runTestMutex
渡劫境CPS 字节码、自定义调度器、工业级框架调度神兵、九境归一

你已不再是协程的初学者,而是能够看穿字节码、锻造调度神兵、构建工业级框架的协程剑仙

协程的大道,不在于记忆 API,而在于理解其设计哲学:结构化并发、挂起不阻塞、冷流热流、通信顺序进程。当你领悟了这些哲学,无论未来出现什么新的异步框架,你都能一眼看穿其本质。

飞升之后,并非终点。愿你将这份对协程的深刻理解,应用到日常的每一行代码中,写出安全、优雅、高效的 Kotlin 程序。

协程修仙录,至此完结。道友,江湖再见。


【最终境界修为面板】

当前境界修炼技能最终称号修炼心得
九境大圆满
飞升成仙
工业级网络框架
九境归一总纲
协程剑仙九境修为熔于一炉:调度重试队列状态异常测试,六位一体方为工业级框架。

【本讲思考题】

  1. 表象题:在我们的自定义框架中,PriorityLimitedDispatcher 是如何同时实现优先级和并发限制的?

  2. 场景题:如果需要在框架中加入“请求去重”功能(相同 ID 的请求在飞行中时,新请求直接复用已有结果),应该如何设计?

  3. 原理题:九境之中,哪一境的原理对你理解协程的帮助最大?为什么?


道友,修仙之路已至尽头,但协程的探索永无止境。愿你以九境修为为基,继续在 Kotlin 的世界中开辟新的天地。

—— 全系列完 ——

欢迎一键四连关注 + 点赞 + 收藏 + 评论