Skip to content

安卓请求必须这样写:

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 { ... } 内:

  1. 异步执行与非阻塞主线程
    • 网络请求通常是耗时操作,如果在主线程(UI 线程)中直接执行会导致界面卡顿或 ANR(应用无响应)。
    • 使用协程(例如通过 launch)可以让网络请求在后台异步执行,同时主线程可以继续响应用户操作。
  2. Suspend 函数的调用要求
    • 在 Kotlin 中,标记为 suspend 的函数(例如 apiService.sendCode(requestBody))只能在协程中调用。
    • 这意味着你必须在一个协程作用域中(例如 viewModelScoperememberCoroutineScope)使用 launchasync 来启动协程,从而调用这些 suspend 函数。
  3. 结构化并发与生命周期管理
    • 通过使用特定的协程作用域(如 viewModelScope),你可以将网络请求的生命周期绑定到组件或 ViewModel 的生命周期上,确保当组件销毁时,相关协程也会自动取消,避免内存泄漏或无效请求。
    • 这种结构化并发能让代码更加简洁、易于管理和调试。
  4. 简化错误处理
    • 协程中的 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 里面的详细原因。