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 中编写简洁、高效且安全的异步代码,避免回调地狱和内存泄漏问题。

相关推荐
猿小蔡-Cool4 分钟前
Kotlin 中 Lambda 表达式的语法结构及简化推导
开发语言·windows·kotlin
YSoup10 分钟前
2025年目前最新版本Android Studio自定义xml预览的屏幕分辨率
android·xml·android studio
虾球xz14 分钟前
CppCon 2014 学习: C++ Test-driven Development
开发语言·c++·学习
stevenzqzq29 分钟前
android lifeCycleOwner生命周期
android
小吴同学·31 分钟前
OPC Client第6讲(wxwidgets):Logger.h日志记录文件(单例模式);登录后的主界面
开发语言·c++·单例模式·wxwidgets
小鹭同学_43 分钟前
Java基础 Day26
java·开发语言
iCxhust1 小时前
8088 单板机 汇编 NMI 中断程序示例 (脱离 DOS 环境)
c语言·开发语言·汇编·单片机·嵌入式硬件·mcu
一叶知秋秋1 小时前
python学习day34
开发语言·python·学习
爱凤的小光1 小时前
C#数字图像处理(一)
开发语言·c#
2501_911828502 小时前
Python训练营---Day41
开发语言·python·深度学习