android协程异步编程常用方法

在 Android 开发中,Kotlin 协程是处理异步操作的首选方案,它能让异步代码更简洁、更易读。以下是 Android 协程异步编程的常用方法和模式:

一、基础构建块

1. launch
  • 作用:启动一个新协程,不返回结果。

  • 适用场景:执行不需要返回值的异步任务(如 UI 更新、启动后台操作)。

  • 示例

    复制代码
    viewModelScope.launch {
        // 默认在主线程执行
        updateUI()
        
        // 切换到 IO 线程执行耗时操作
        withContext(Dispatchers.IO) {
            saveDataToDisk()
        }
    }
2. async + await
  • 作用 :异步执行任务并返回 Deferred 对象,通过 await() 获取结果。

  • 适用场景:并行执行多个异步任务,合并结果。

  • 示例

    复制代码
    viewModelScope.launch {
        val task1 = async(Dispatchers.IO) { fetchDataFromNetwork() }
        val task2 = async(Dispatchers.IO) { loadCacheFromDisk() }
        
        // 等待两个任务完成并合并结果
        val result = combine(task1.await(), task2.await())
        updateUI(result)
    }
3. withContext
  • 作用:在指定调度器中执行代码块,并挂起当前协程,返回结果。

  • 适用场景:切换线程执行耗时操作后回到原线程。

  • 示例

    复制代码
    viewModelScope.launch {
        // 主线程:显示加载状态
        showLoading()
        
        // 切换到 IO 线程执行耗时操作
        val data = withContext(Dispatchers.IO) {
            repository.fetchData()
        }
        
        // 自动回到主线程:更新 UI
        updateUI(data)
    }

二、调度器(Dispatchers)

调度器 作用 适用场景
Dispatchers.Main 主线程,用于 UI 操作 更新 TextView、启动动画
Dispatchers.IO 后台线程,优化磁盘 / 网络 IO 文件读写、网络请求
Dispatchers.Default 后台线程,用于 CPU 密集型任务 复杂计算、JSON 解析
Dispatchers.Unconfined 不指定固定线程,由调用者决定 测试、初期协程启动

三、取消与异常处理

1. 自动取消(ViewModelScope)
复制代码
class MyViewModel : ViewModel() {
    fun fetchData() {
        // 使用 viewModelScope,Activity/Fragment 销毁时自动取消
        viewModelScope.launch {
            val data = withContext(Dispatchers.IO) {
                repository.loadData()
            }
            _liveData.value = data
        }
    }
}
2. 手动取消协程
复制代码
private var job: Job? = null

fun startTask() {
    job = viewModelScope.launch {
        // 执行耗时操作
    }
}

fun cancelTask() {
    job?.cancel() // 手动取消协程
}
3. 异常处理
复制代码
viewModelScope.launch {
    try {
        val result = withContext(Dispatchers.IO) {
            api.fetchData() // 可能抛出异常的操作
        }
        updateUI(result)
    } catch (e: IOException) {
        showError(e.message)
    }
}

四、Flow(数据流)

1. 冷流(Cold Flow)
  • 特点:只有订阅时才执行,类似 RxJava 的 Observable。

  • 示例

    复制代码
    // 从数据库获取实时数据
    fun getUserFlow(): Flow<User> = flow {
        while (true) {
            emit(database.getUser()) // 发送数据
            delay(1000) // 每秒更新一次
        }
    }.flowOn(Dispatchers.IO) // 指定流的执行线程
    
    // 在 ViewModel 中收集
    viewModelScope.launch {
        userFlow.collect { user ->
            _liveData.value = user // 更新 LiveData
        }
    }
2. StateFlow & SharedFlow
  • StateFlow:保存最新值的热流,适合表示状态。

    复制代码
    // ViewModel 中
    private val _uiState = MutableStateFlow<UiState>(Loading)
    val uiState: StateFlow<UiState> = _uiState
    
    // 协程中更新状态
    _uiState.value = Success(data)
  • SharedFlow:更灵活的热流,可配置重放次数、缓冲区大小。

    复制代码
    private val _event = MutableSharedFlow<Event>()
    val event: SharedFlow<Event> = _event
    
    // 发送事件
    viewModelScope.launch {
        _event.emit(ToastEvent("操作成功"))
    }

五、Channel(通道)

1. 基本用法
  • 作用:在协程间传递数据,类似生产者 - 消费者模式。

  • 示例

    复制代码
    val channel = Channel<String>()
    
    // 生产者协程
    viewModelScope.launch {
        repeat(5) {
            delay(1000)
            channel.send("消息 $it")
        }
        channel.close() // 发送完毕后关闭
    }
    
    // 消费者协程
    viewModelScope.launch {
        for (msg in channel) {
            Log.d("Channel", "收到: $msg")
        }
    }

六、组合多个异步操作

1. 串行执行
复制代码
viewModelScope.launch {
    val user = withContext(Dispatchers.IO) { api.getUser() }
    val orders = withContext(Dispatchers.IO) { api.getOrders(user.id) }
    updateUI(orders)
}
2. 并行执行
复制代码
viewModelScope.launch {
    val userDeferred = async(Dispatchers.IO) { api.getUser() }
    val ordersDeferred = async(Dispatchers.IO) { api.getOrders(userId) }
    
    val user = userDeferred.await()
    val orders = ordersDeferred.await()
    
    updateUI(user, orders)
}
3. 使用 zip 合并流
复制代码
val flow1 = flow { emit(1) }
val flow2 = flow { emit("A") }

// 合并两个流的结果
flow1.zip(flow2) { num, str -> "$num-$str" }
    .collect { result -> Log.d("Zip", result) } // 输出 "1-A"

七、最佳实践

  1. 避免在主线程执行耗时操作

    复制代码
    // 错误:在主线程执行网络请求
    viewModelScope.launch {
        val data = api.fetchData() // 耗时操作,应切换到 IO 线程
    }
    
    // 正确:使用 withContext 切换线程
    viewModelScope.launch {
        val data = withContext(Dispatchers.IO) {
            api.fetchData()
        }
    }
  2. 使用 ViewModelScope 管理生命周期

    复制代码
    class MyViewModel : ViewModel() {
        fun loadData() {
            // 使用 viewModelScope,自动与 ViewModel 生命周期绑定
            viewModelScope.launch { ... }
        }
    }
  3. 避免使用 GlobalScope

    复制代码
    // 错误:可能导致内存泄漏
    GlobalScope.launch { ... }
    
    // 正确:使用限定作用域的协程
    viewModelScope.launch { ... }
    lifecycleScope.launch { ... }

八、与其他组件结合

1. 与 LiveData 结合
复制代码
// 将 Flow 转换为 LiveData
val liveData: LiveData<User> = flow {
    emit(repository.getUser())
}.flowOn(Dispatchers.IO)
  .asLiveData()
2. 与 Room 结合
复制代码
// DAO 方法返回 Flow
@Query("SELECT * FROM users")
fun getUsers(): Flow<List<User>>

// ViewModel 中收集数据
viewModelScope.launch {
    userDao.getUsers().collect { users ->
        _usersLiveData.value = users
    }
}

通过合理使用上述方法,你可以在 Android 中编写简洁、高效且安全的异步代码,避免回调地狱和内存泄漏问题。

相关推荐
Scott9999HH6 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社7 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海7 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖8 小时前
JDK 26 新特性详解
java·开发语言
马优晨8 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区10 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大11 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai11 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜11 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa11 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio