Day5 Kotlin 协程

协程是Kotlin的异步编程解决方案,类似于Swift的 async/await

suspend 函数

suspend 是 Kotlin 协程的核心关键字,标记一个函数可以被挂起 。挂起的意思是:函数执行到某个点时可以暂停,释放当前线程去做别的事,等结果回来再继续,整个过程不阻塞线程

kotlin 复制代码
// Kotlin
suspend fun fetchUser(): User { ... }
// Swift
func fetchUser() async -> User { ... }
`async` = `suspend`,概念完全一样

suspend 函数的规则

scss 复制代码
// Kotlin 
suspend fun loadData() {
    val user = fetchUser() // ✅ suspend 函数里调用 suspend 函数
}

fun normalFunc() {
    val user = fetchUser() // ❌ 编译报错,必须在协程或 suspend 函数里
}

// Swift --- async 函数只能在 async 上下文调用
func loadData() async {
    let user = await fetchUser() // ✅
}

func normalFunc() {
    let user = await fetchUser() // ❌ 编译报错
}

启动协程

suspend 函数不能凭空调用,需要一个入口来启动协程,主要有三种方式。

  1. launch --- 不关心返回值
scss 复制代码
// Kotlin
viewModelScope.launch {
    loadUser()
}
// Swift
Task {
    await loadUser()
}
`launch` 返回一个 `Job` 对象,可以用来取消,但**拿不到协程的执行结果**:
val job = viewModelScope.launch {
    loadUser()
}

job.cancel() // 可以取消
// job 拿不到 loadUser() 的返回值
  1. async --- 关心返回值
csharp 复制代码
// Kotlin
val deferred = viewModelScope.async {
    fetchUser()
}
val result = deferred.await() // 等待拿结果
对应 Swift 的 `async let`:
// Swift
async let user = fetchUser()
let result = await user
`async` 返回 `Deferred<T>`,调用 `.await()` 才能拿到值。
最常见的用法是并行请求
// Kotlin 
fun loadAll() {
    viewModelScope.launch {
        val user = async { fetchUser() }
        val posts = async { fetchPosts() }
        
        val u = user.await()
        val p = posts.await()
    }
}
// Swift 
func loadAll() async {
    async let user = fetchUser()
    async let posts = fetchPosts()
    
    let u = try? await user
    let p = try? await posts
}
  1. runBlocking --- 测试专用

这个 Swift 没有对应的,它的作用是把协程变成阻塞调用,主要用于单元测试:

kotlin 复制代码
// 普通函数里没法调用 suspend 函数
// runBlocking 强行创建一个阻塞的协程环境
fun main() {
    runBlocking {
        val user = fetchUser() // 现在可以调用了
        println(user)
    }
}

⚠️ 生产代码里不要用,会阻塞线程,只在测试和 main 函数里用。


launch vs async 怎么选

csharp 复制代码
需要返回值吗?
├── 不需要 → launch
└── 需要   → async + await

Scope 作用域

Scope 定义了协程的生命周期,协程必须在某个 Scope 里运行,Scope 取消时它里面所有协程都会取消。

viewModelScope

kotlin 复制代码
class UserViewModel : ViewModel() {
    fun loadUser() {
        viewModelScope.launch {
            val user = fetchUser()
            _uiState.value = user
        }
    }
}
// ViewModel 被清除时,所有协程自动取消
// Swift UIKit --- VC 都 deinit 了,Task 还在跑
class UserViewController: UIViewController {
    func loadUser() {
        Task {
            let user = await fetchUser()
            self.updateUI(user) // VC 都没了还在回调,可能 crash
        }
    }
    
    // 要自己存 task 然后手动 cancel
    var task: Task<Void, Never>?
    
    override func viewDidDisappear(_ animated: Bool) {
        task?.cancel() // 还得记得取消
    }
}

lifecycleScope

用在 Activity / Fragment 里,和 Activity 生命周期绑定:

kotlin 复制代码
class UserActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 基础用法,Activity 销毁自动取消
        lifecycleScope.launch {
            loadData()
        }

        // 进阶用法:感知前后台
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // App 进后台自动暂停,回前台自动恢复
                viewModel.uiState.collect { state ->
                    updateUI(state)
                }
            }
        }
    }
}
// Swift UIKit --- 你可能这样写过
class UserViewController: UIViewController {
    var task: Task<Void, Never>?

    override func viewDidAppear(_ animated: Bool) {
        task = Task {
            for await state in viewModel.uiStateStream {
                updateUI(state)
            }
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        task?.cancel() // 手动取消
    }
}
`lifecycleScope + repeatOnLifecycle` 把这套模板代码帮你封装好了。

CoroutineScope手动创建

用在普通类里,比如 Repository、Manager,这些类没有框架提供的 Scope:

kotlin 复制代码
class UserRepository {

    // 手动创建 scope
    private val scope = CoroutineScope(
        Dispatchers.IO + SupervisorJob()
    )

    fun startSync() {
        scope.launch {
            syncData()
        }
    }

    // ⚠️ 必须手动取消,否则内存泄漏
    fun destroy() {
        scope.cancel()
    }
}
// Swift
class UserRepository {
    var tasks: [Task<Void, Never>] = []

    func startSync() {
        let task = Task {
            await syncData()
        }
        tasks.append(task)
    }

    func destroy() {
        tasks.forEach { $0.cancel() }
    }
}
两者都需要手动管理,Kotlin 只是用 `scope.cancel()` 一次性取消所有。

GlobalScope --- 几乎不用-----未完待续

coroutineScope {}supervisorScope {}

scss 复制代码
`coroutineScope` --- 一个失败全取消
//Kotlin 对应 Swift 的 async let,行为完全一样
suspend fun loadAll(): Pair<User, List<Post>> = coroutineScope {
    val user = async { fetchUser() }
    val posts = async { fetchPosts() }
    // fetchUser 抛异常 → fetchPosts 也取消
    Pair(user.await(), posts.await())
}
// Swift async let 也是同样行为
func loadAll() async throws -> (User, [Post]) {
    async let user = fetchUser()
    async let posts = fetchPosts()
    // fetchUser 抛异常 → fetchPosts 也取消
    return try await (user, posts)
}
### `supervisorScope` --- 失败互不影响

suspend fun loadAll() = supervisorScope { val user = async { fetchUser() } val posts = async { fetchPosts() } // fetchUser 抛异常 → fetchPosts 继续跑,互不影响

ini 复制代码
val u = runCatching { user.await() }.getOrNull()
val p = runCatching { posts.await() }.getOrNull()

}

markdown 复制代码
Swift 没有直接对应,你之前用 `try?` 单独处理每个 `await` 来达到类似效果。
Scope 选择总结
你在哪写代码?
│
├── ViewModel 里          → viewModelScope
├── Activity/Fragment 里  → lifecycleScope  
├── 普通类里              → CoroutineScope(...) 手动管理
│
└── suspend 函数内部需要并发?
    ├── 一个失败全取消    → coroutineScope {}
    └── 失败互不影响      → supervisorScope {}

Dispatcher 调度器

Dispatcher 决定协程在哪个线程上运行。对应 Swift 里你切线程的方式。

Dispatchers.Main --- 主线程

arduino 复制代码
// Kotlin --- 主线程,用来更新 UI
viewModelScope.launch(Dispatchers.Main) {
    textView.text = "Hello"
}

// 其实 viewModelScope 默认就是 Main
// 所以大部分时候不用显式写
viewModelScope.launch {
    textView.text = "Hello" // 已经在主线程了
}
// Swift --- 切回主线程
await MainActor.run {
    self.tableView.reloadData()
}

Dispatchers.IO --- IO 线程

javascript 复制代码
// Kotlin --- 网络请求、数据库、文件读写都用这个
viewModelScope.launch {
    val data = withContext(Dispatchers.IO) {
        api.fetchUser() // 切到 IO 线程
    }
    textView.text = data.name // 自动切回 Main 线程
}
背后有一个线程池,最多 64 个线程,专门处理等待 IO 的任务。
// Swift --- 后台线程做网络/文件
Task.detached(priority: .background) {
    let data = await fetchFromNetwork()
}
`withContext` --- 切换线程
这是最常用的切线程方式,对应 Swift 的 `await MainActor.run {}`:

Dispatchers.Default --- CPU 密集线程

scss 复制代码
viewModelScope.launch {
    val result = withContext(Dispatchers.Default) {
        // 大量数据排序、图片处理、JSON 解析
        processLargeDataSet(data)
    }
    updateUI(result)
}
线程数和 CPU 核心数一样,专门跑计算密集的任务。
Swift 没有直接对应,用于**计算量大**的任务:

Dispatchers.Main → UI 操作、更新状态

Dispatchers.IO → 网络请求、数据库、文件

Dispatchers.Default → 排序、计算、图片处理

错误处理

基础 try/catch 和 Swift 的 do/try/catch 几乎一样:

scss 复制代码
// Kotlin --- 结构完全一样
fun loadUser() {
    viewModelScope.launch {
        try {
            val user = fetchUser()
            updateUI(user)
        } catch (e: Exception) {
            showError(e)
        }
    }
}
// Swift
func loadUser() async {
    do {
        let user = try await fetchUser()
        updateUI(user)
    } catch {
        showError(error)
    }
}

runCatching --- 对应 Swift 的 try?

scss 复制代码
// Kotlin --- 失败返回 null
val user = runCatching { fetchUser() }.getOrNull()

// 还可以拿到错误信息
val result = runCatching { fetchUser() }
result.onSuccess { user -> updateUI(user) }
result.onFailure { e -> showError(e) }
// Swift --- 失败返回 nil
let user = try? await fetchUser()

CoroutineExceptionHandler --- 全局兜底

Swift 没有对应的,这是协程特有的全局错误处理机制:

kotlin 复制代码
 class UserViewModel : ViewModel() {

    private val handler = CoroutineExceptionHandler { _, e ->
        _uiState.value = UiState.Error(e.message)
    }

    fun loadUser() {
        viewModelScope.launch(handler) {
            val user = fetchUser()
            _uiState.value = UiState.Success(user)
        }
    }
}

launch vs async 的错误处理行为不一样 这是 Kotlin 特有的坑,Swift 没有这个区别:

dart 复制代码
// launch --- 异常立刻抛出
viewModelScope.launch {
    throw Exception("出错了") // 立刻崩,必须在里面 catch
}

// async --- 异常在 await() 时才抛出
viewModelScope.launch {
    val deferred = async {
        throw Exception("出错了") // 这里不崩
    }
    deferred.await() // 异常在这里才抛出
}
所以用 `async` 的时候要在 `await()` 外面 catch:
viewModelScope.launch {
    try {
        val deferred = async { fetchUser() }
        val user = deferred.await() // catch 要包这里
    } catch (e: Exception) {
        showError(e)
    }
}
错误处理选哪种
单个请求出错就展示错误   → try/catch
不关心错误只想要 null    → runCatching + getOrNull
全局兜底防止 crash       → CoroutineExceptionHandler

取消与超时

基础取消

scss 复制代码
// Kotlin
val job = viewModelScope.launch {
    loadUser()
}
job.cancel() // 取消
// Swift
let task = Task {
    await loadUser()
}
task.cancel() // 取消

取消是协作式的 这点和 Swift 完全一样,取消不是强制停止,协程需要配合才能真正取消

kotlin 复制代码
// Kotlin --- 同样需要配合
suspend fun loadUser() {
    for (item in items) {
        if (!isActive) return // 主动检查是否还活着
        process(item)
    }
}
Kotlin 内置的 `suspend` 函数(比如 `delay`、`withContext`)都自动支持取消,不用手动检查。只有你自己写的循环逻辑才需要加 `isActive` 检查。
// Swift --- 需要检查 isCancelled
func loadUser() async {
    for item in items {
        try Task.checkCancellation() // 主动检查
        await process(item)
    }
}

withTimeout --- 超时取消

csharp 复制代码
// Kotlin --- 一行搞定
suspend fun fetchWithTimeout(): User {
    return withTimeout(3000) { // 3秒超时
        fetchUser()
    }
    // 超时抛出 TimeoutCancellationException
}

// 不想让超时抛异常,返回 null
val user = withTimeoutOrNull(3000) {
    fetchUser()
} // 超时返回 null,不崩
// Swift --- 没有原生支持,要自己实现
func fetchWithTimeout() async throws -> User {
    try await withThrowingTaskGroup(of: User.self) { group in
        group.addTask { try await fetchUser() }
        group.addTask {
            try await Task.sleep(nanoseconds: 3_000_000_000)
            throw TimeoutError()
        }
        let result = try await group.next()!
        group.cancelAll()
        return result
    }
}

取消后的清理工作

scss 复制代码
// Kotlin --- 用 try/finally
suspend fun loadUser() {
    try {
        showLoading()
        val user = fetchUser()
        updateUI(user)
    } finally {
        hideLoading() // 无论成功、失败、取消都执行
    }
}
`finally` 在协程被取消时也会执行,和 Swift 的 `defer` 一样可靠。

// Swift --- 用 defer 清理
func loadUser() async {
    defer {
        hideLoading() // 无论成功还是取消都执行
    }
    showLoading()
    let user = try await fetchUser()
}
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui