安卓请求必须这样写:
kotlin
scope.launch {
try {
val response = apiService.listCourses(courseName, subjectName)
allCourses = response.data as List<Course>
val response2 = apiService.listStudentRegistrations()
allRegistrations = response2.data as List<Registration>
@Suppress("UNCHECKED_CAST")
courses = allCourses.take(pageSize).map { course ->
val matchingRegistration = allRegistrations.find { it.courseId == course.courseId }
course.copy(status = matchingRegistration?.status ?: "")
} ?: emptyList()
page++
} catch (e: Exception) {
ErrorNotifier.showError(e.message ?: "Register failed.")
}
}
在 Kotlin 中,许多==网络请求都是通过 suspend 函数实现的,而 suspend 函数只能在协程中被调用==。下面详细解释为什么网络请求通常需要写在 scope.launch { ... }
内:
- 异步执行与非阻塞主线程
- 网络请求通常是耗时操作,如果在主线程(UI 线程)中直接执行会导致界面卡顿或 ANR(应用无响应)。
- 使用协程(例如通过
launch
)可以让网络请求在后台异步执行,同时主线程可以继续响应用户操作。
- Suspend 函数的调用要求
- 在 Kotlin 中,标记为 suspend 的函数(例如
apiService.sendCode(requestBody)
)只能在协程中调用。 - 这意味着你必须在一个协程作用域中(例如
viewModelScope
或rememberCoroutineScope
)使用launch
或async
来启动协程,从而调用这些 suspend 函数。
- 在 Kotlin 中,标记为 suspend 的函数(例如
- 结构化并发与生命周期管理
- 通过使用特定的协程作用域(如
viewModelScope
),你可以将网络请求的生命周期绑定到组件或 ViewModel 的生命周期上,确保当组件销毁时,相关协程也会自动取消,避免内存泄漏或无效请求。 - 这种结构化并发能让代码更加简洁、易于管理和调试。
- 通过使用特定的协程作用域(如
- 简化错误处理
- 协程中的 try/catch 语法非常直观,可以很方便地捕获和处理网络请求中抛出的异常。
- 将网络请求放入
launch
块中,可以在 try 块中执行请求,在 finally 块中统一恢复状态(比如关闭加载指示器)。
举例说明
假设你有一个网络请求接口 apiService.sendCode(requestBody)
,它是一个 suspend 函数:
kotlin
// 定义 suspend 网络请求函数
suspend fun sendCode(requestBody: Map<String, String>): CommonResponse { ... }
如果你想在 UI 层调用这个函数,而不阻塞 UI,就需要用协程。例如:
kotlin
val coroutineScope = rememberCoroutineScope()
Button(onClick = {
coroutineScope.launch {
// 在协程中调用 suspend 函数,不会阻塞主线程
try {
isLoading = true // 更新加载状态,显示加载圈
val requestBody = mapOf("email" to "user@example.com")
val response = apiService.sendCode(requestBody)
// 请求成功,处理返回数据
ErrorNotifier.showSuccess("Send successful! Please check your email.")
} catch (e: Exception) {
// 捕获异常,处理错误
ErrorNotifier.showError(e.message ?: "Send failed.")
} finally {
isLoading = false // 请求结束,隐藏加载圈
}
}
}) {
Text("Send Code")
}
在这个例子中:
- 使用
rememberCoroutineScope()
创建一个局部协程作用域; - 在
Button
的点击事件中,通过launch
启动一个协程,并在其中调用 suspend 函数执行网络请求; - 这样做既确保了请求在后台异步执行,又不会阻塞 UI,同时通过 try/catch/finally 来管理加载状态和错误处理。
对比 React
- React 中的
setState
是异步的,更新会被批处理,而你不能直接在调用setState
后马上拿到最新状态;而在 Compose 中,状态更新(例如 mutableStateOf 的赋值)是同步的,但 UI 重组会在之后调度。 - 在 React 中,通常使用
useEffect
来处理异步操作,并且需要依赖 Promise 或 async/await;在 Compose 中,使用 Kotlin 协程(如launch
)实现异步操作,这更直接,也利用了 Kotlin 的强大语言特性。 - React 中通常需要使用第三方库(例如 Axios)来处理网络请求,并在组件内部管理加载状态;而在 Compose 中,我们可以直接在 ViewModel 或 Composable 中使用 suspend 函数和协程来管理请求,状态更新更加直观。
小结
- 网络请求是耗时操作,必须异步执行以防止阻塞 UI。
- suspend 函数只能在协程中调用,所以你需要用
scope.launch { ... }
来启动协程。 - 协程能够帮助你绑定请求的生命周期、管理并发和简化错误处理。
- 与 React 相比,Compose 中状态更新同步但 UI 重组调度异步,利用 Kotlin 协程实现网络请求既简单又高效。
这就是为什么在 Compose 中网络请求通常要写在 scope.launch
里面的详细原因。