前言
渡劫中阶已过,你已执掌造化,能亲手锻造优先级调度、令牌桶限流、线程亲和等神兵利器。九境修为——从炼气的挂起初啼,到渡劫的调度器定制——已全部汇聚于你一身。
今日,是你飞升前的最终大典。我们将不再学习新的 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
框架的核心设计原则:
- 单一职责:调度、重试、队列、状态各司其职。
- 可组合:各模块通过协程的
CoroutineContext和Flow组合。 - 可测试:所有依赖均可注入,支持
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.Element 和 Flow 的 onEach、catch 实现统一日志。
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")
}
}
}
}
飞升结语:九境修仙路,协程大道成
道友,你已走完了从炼气到渡劫的九重境界。我们一同回顾这条修仙之路:
| 境界 | 核心修为 | 法器 |
|---|---|---|
| 炼气境 | 挂起函数、launch、Job、结构化并发、CPS 原理 | suspend 关键字、viewModelScope |
| 筑基境 | CoroutineContext、Dispatcher、withContext、异常处理 | Job 树、SupervisorJob |
| 金丹境 | async/await、CoroutineStart、supervisorScope | Deferred、并发组合 |
| 元婴境 | Flow 冷流、操作符、背压 | flow {}、buffer、conflate |
| 化神境 | StateFlow、SharedFlow、stateIn/shareIn | 热流、事件总线 |
| 炼虚境 | Channel、select、actor、produce | 协程间通信 |
| 合体境 | WorkManager、Room、Retrofit、Compose 集成 | Android 架构融合 |
| 大乘境 | 调试、测试、线程池、limitedParallelism、泄漏排查 | runTest、Mutex |
| 渡劫境 | CPS 字节码、自定义调度器、工业级框架 | 调度神兵、九境归一 |
你已不再是协程的初学者,而是能够看穿字节码、锻造调度神兵、构建工业级框架的协程剑仙。
协程的大道,不在于记忆 API,而在于理解其设计哲学:结构化并发、挂起不阻塞、冷流热流、通信顺序进程。当你领悟了这些哲学,无论未来出现什么新的异步框架,你都能一眼看穿其本质。
飞升之后,并非终点。愿你将这份对协程的深刻理解,应用到日常的每一行代码中,写出安全、优雅、高效的 Kotlin 程序。
协程修仙录,至此完结。道友,江湖再见。
【最终境界修为面板】
| 当前境界 | 修炼技能 | 最终称号 | 修炼心得 |
|---|---|---|---|
| 九境大圆满 飞升成仙 | 工业级网络框架 九境归一总纲 | 协程剑仙 | 九境修为熔于一炉:调度、重试、队列、状态、异常、测试,六位一体方为工业级框架。 |
【本讲思考题】
-
表象题:在我们的自定义框架中,
PriorityLimitedDispatcher是如何同时实现优先级和并发限制的? -
场景题:如果需要在框架中加入“请求去重”功能(相同 ID 的请求在飞行中时,新请求直接复用已有结果),应该如何设计?
-
原理题:九境之中,哪一境的原理对你理解协程的帮助最大?为什么?
道友,修仙之路已至尽头,但协程的探索永无止境。愿你以九境修为为基,继续在 Kotlin 的世界中开辟新的天地。
—— 全系列完 ——
欢迎一键四连(
关注+点赞+收藏+评论)