【Kotlin系列17】协程在Android中的最佳实践:从入门到精通

77 阅读13分钟

引言

"用户点击按钮后应用卡死2秒,然后突然显示数据。"——这是我在Code Review时听到最多的问题描述之一。

几年前,我接手过一个Android项目,代码里充斥着各种Thread { ... }.start(),还有大量的runOnUiThread,以及让人头疼的内存泄漏。每次用户旋转屏幕,网络请求都会重新发起;当用户快速点击按钮时,多个请求并发执行导致界面显示错乱。最糟糕的是,当Activity销毁后,后台线程仍在运行,持有Activity引用,造成内存泄漏。

Kotlin协程彻底改变了这一切。 配合Android的生命周期感知组件(ViewModel、Lifecycle),协程让异步编程变得简单、安全、高效。Google官方已经将协程作为Android异步编程的首选方案,Jetpack库全面支持协程。

本文将系统讲解Kotlin协程在Android开发中的最佳实践,涵盖:

  • 协程作用域的正确使用(lifecycleScope、viewModelScope、GlobalScope)
  • 网络请求、数据库操作、文件读写的协程实践
  • UI更新与线程切换
  • 异常处理与错误传播
  • 性能优化与常见陷阱

无论你是协程新手,还是想深入掌握协程在Android中的应用,这篇文章都会给你带来价值。


一、Android协程生态概览

1.1 协程在Android架构中的位置

17-01-android-coroutines-architecture.png

在现代Android开发中,协程与以下组件深度集成:

架构组件层:

  • ViewModel - 提供viewModelScope,自动取消协程
  • Lifecycle - 提供lifecycleScope,生命周期感知
  • LiveData - 通过liveData {}构建器支持协程
  • Room - 原生支持suspend函数

网络层:

  • Retrofit - 通过suspend关键字支持协程
  • OkHttp - 非阻塞I/O,与协程完美配合

数据持久化层:

  • Room Database - @Dao接口支持suspend函数
  • DataStore - 完全基于协程和Flow

1.2 核心依赖配置

build.gradle.kts中添加必要的依赖:

dependencies {
    // 协程核心库
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")

    // Lifecycle与协程集成
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")

    // Room与协程集成
    implementation("androidx.room:room-ktx:2.6.1")

    // Retrofit与协程集成
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2")
}

1.3 三大协程作用域

Android提供了三个主要的协程作用域:

作用域生命周期调度器使用场景
viewModelScopeViewModel生命周期Dispatchers.MainViewModel中的数据加载
lifecycleScopeActivity/Fragment生命周期Dispatchers.MainUI相关操作
GlobalScope应用进程生命周期需手动指定❌ 通常不推荐使用

核心原则: 始终使用生命周期感知的作用域,避免内存泄漏和不必要的计算。


二、ViewModel中的协程实践

2.1 viewModelScope的正确使用

viewModelScope是ViewModel的扩展属性,当ViewModel被清除时自动取消所有协程。

基本用法:

class UserViewModel(
    private val userRepository: UserRepository
) : ViewModel() {

    private val _userState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val userState: StateFlow<UiState<User>> = _userState.asStateFlow()

    fun loadUser(userId: String) {
        viewModelScope.launch {
            _userState.value = UiState.Loading

            try {
                val user = userRepository.getUser(userId)
                _userState.value = UiState.Success(user)
            } catch (e: Exception) {
                _userState.value = UiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String) : UiState<Nothing>()
}

关键点:

  • 使用viewModelScope.launch启动协程,无需手动取消
  • 使用StateFlow暴露UI状态,替代LiveData
  • 在协程中处理异常,转换为UI状态

2.2 并发请求优化

场景: 需要同时加载用户信息、好友列表和帖子列表。

❌ 错误方式:串行执行

// 串行执行:总耗时 = time1 + time2 + time3
suspend fun loadAllData(userId: String) {
    val user = userRepository.getUser(userId)        // 1秒
    val friends = friendsRepository.getFriends(userId) // 1秒
    val posts = postsRepository.getPosts(userId)     // 1秒
    // 总耗时:3秒
}

✅ 正确方式:并发执行

import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll

suspend fun loadAllData(userId: String): Triple<User, List<Friend>, List<Post>> {
    return coroutineScope {
        // 并发执行三个请求
        val userDeferred = async { userRepository.getUser(userId) }
        val friendsDeferred = async { friendsRepository.getFriends(userId) }
        val postsDeferred = async { postsRepository.getPosts(userId) }

        // 等待所有结果
        Triple(
            userDeferred.await(),
            friendsDeferred.await(),
            postsDeferred.await()
        )
    }
    // 总耗时:约1秒(最慢的那个请求)
}

在ViewModel中使用:

class DashboardViewModel(
    private val userRepository: UserRepository,
    private val friendsRepository: FriendsRepository,
    private val postsRepository: PostsRepository
) : ViewModel() {

    private val _dashboardState = MutableStateFlow<DashboardState>(DashboardState.Loading)
    val dashboardState = _dashboardState.asStateFlow()

    fun loadDashboard(userId: String) {
        viewModelScope.launch {
            _dashboardState.value = DashboardState.Loading

            try {
                val (user, friends, posts) = loadAllData(userId)
                _dashboardState.value = DashboardState.Success(
                    Dashboard(user, friends, posts)
                )
            } catch (e: Exception) {
                _dashboardState.value = DashboardState.Error(e.message ?: "加载失败")
            }
        }
    }

    private suspend fun loadAllData(userId: String): Triple<User, List<Friend>, List<Post>> {
        return coroutineScope {
            val userDeferred = async { userRepository.getUser(userId) }
            val friendsDeferred = async { friendsRepository.getFriends(userId) }
            val postsDeferred = async { postsRepository.getPosts(userId) }

            Triple(
                userDeferred.await(),
                friendsDeferred.await(),
                postsDeferred.await()
            )
        }
    }
}

性能对比:

  • 串行执行:3秒
  • 并发执行:1秒
  • 性能提升:67%

2.3 处理用户操作防抖

场景: 用户在搜索框输入时实时搜索,避免频繁请求。

class SearchViewModel(
    private val searchRepository: SearchRepository
) : ViewModel() {

    private val _searchQuery = MutableStateFlow("")

    private val _searchResults = MutableStateFlow<List<SearchResult>>(emptyList())
    val searchResults = _searchResults.asStateFlow()

    init {
        // 使用Flow操作符实现防抖
        viewModelScope.launch {
            _searchQuery
                .debounce(300)  // 300ms防抖
                .distinctUntilChanged()  // 去重
                .filter { it.length >= 2 }  // 至少2个字符才搜索
                .collectLatest { query ->
                    searchInternal(query)
                }
        }
    }

    fun updateQuery(query: String) {
        _searchQuery.value = query
    }

    private suspend fun searchInternal(query: String) {
        try {
            val results = searchRepository.search(query)
            _searchResults.value = results
        } catch (e: Exception) {
            _searchResults.value = emptyList()
        }
    }
}

UI层调用:

class SearchActivity : AppCompatActivity() {
    private val viewModel: SearchViewModel by viewModels()

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

        binding.searchEditText.addTextChangedListener { text ->
            viewModel.updateQuery(text.toString())
        }

        lifecycleScope.launch {
            viewModel.searchResults.collect { results ->
                updateSearchResults(results)
            }
        }
    }
}

关键点:

  • 使用debounce(300)实现300ms防抖
  • distinctUntilChanged()避免重复查询
  • collectLatest确保只处理最新的搜索结果

三、网络请求的协程实践

3.1 Retrofit与协程集成

定义API接口:

interface ApiService {
    @GET("users/{id}")
    suspend fun getUser(@Path("id") userId: String): User

    @POST("users")
    suspend fun createUser(@Body user: User): User

    @GET("users/{id}/posts")
    suspend fun getUserPosts(
        @Path("id") userId: String,
        @Query("page") page: Int
    ): List<Post>
}

Repository层封装:

class UserRepository(
    private val apiService: ApiService,
    private val userDao: UserDao
) {
    // 先从本地获取,再从网络更新
    suspend fun getUser(userId: String): User {
        return withContext(Dispatchers.IO) {
            try {
                // 尝试从网络获取最新数据
                val user = apiService.getUser(userId)
                // 缓存到本地数据库
                userDao.insertUser(user)
                user
            } catch (e: Exception) {
                // 网络失败,从本地数据库获取
                userDao.getUserById(userId) ?: throw e
            }
        }
    }

    // 分页加载帖子
    suspend fun getUserPosts(userId: String, page: Int): List<Post> {
        return withContext(Dispatchers.IO) {
            apiService.getUserPosts(userId, page)
        }
    }
}

3.2 处理网络错误与重试

17-02-network-error-handling.png

实现智能重试机制:

// 重试扩展函数
suspend fun <T> retryIO(
    times: Int = 3,
    initialDelay: Long = 100,
    maxDelay: Long = 1000,
    factor: Double = 2.0,
    block: suspend () -> T
): T {
    var currentDelay = initialDelay
    repeat(times - 1) { attempt ->
        try {
            return block()
        } catch (e: IOException) {
            // 网络错误,进行重试
            delay(currentDelay)
            currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
        }
    }
    return block() // 最后一次尝试,失败则抛出异常
}

// 在Repository中使用
class UserRepository(private val apiService: ApiService) {

    suspend fun getUserWithRetry(userId: String): User {
        return withContext(Dispatchers.IO) {
            retryIO(times = 3) {
                apiService.getUser(userId)
            }
        }
    }
}

处理不同类型的网络错误:

sealed class NetworkResult<out T> {
    data class Success<T>(val data: T) : NetworkResult<T>()
    data class Error(val exception: Exception) : NetworkResult<Nothing>()
    object Loading : NetworkResult<Nothing>()
}

suspend fun <T> safeApiCall(
    apiCall: suspend () -> T
): NetworkResult<T> {
    return withContext(Dispatchers.IO) {
        try {
            NetworkResult.Success(apiCall())
        } catch (e: HttpException) {
            // HTTP错误(4xx, 5xx)
            NetworkResult.Error(Exception("网络请求失败: ${e.code()}"))
        } catch (e: IOException) {
            // 网络连接错误
            NetworkResult.Error(Exception("网络连接失败"))
        } catch (e: Exception) {
            // 其他未知错误
            NetworkResult.Error(Exception("未知错误: ${e.message}"))
        }
    }
}

// 在ViewModel中使用
fun loadUser(userId: String) {
    viewModelScope.launch {
        _userState.value = UiState.Loading

        when (val result = safeApiCall { userRepository.getUser(userId) }) {
            is NetworkResult.Success -> {
                _userState.value = UiState.Success(result.data)
            }
            is NetworkResult.Error -> {
                _userState.value = UiState.Error(result.exception.message ?: "未知错误")
            }
            is NetworkResult.Loading -> {
                // 不处理
            }
        }
    }
}

3.3 请求取消与超时

设置超时:

suspend fun getUserWithTimeout(userId: String): User {
    return withContext(Dispatchers.IO) {
        withTimeout(5000) {  // 5秒超时
            apiService.getUser(userId)
        }
    }
}

// 或者使用withTimeoutOrNull返回null而不是抛出异常
suspend fun getUserWithTimeoutOrNull(userId: String): User? {
    return withContext(Dispatchers.IO) {
        withTimeoutOrNull(5000) {
            apiService.getUser(userId)
        }
    }
}

取消正在进行的请求:

class UserViewModel(
    private val userRepository: UserRepository
) : ViewModel() {

    private var loadUserJob: Job? = null

    fun loadUser(userId: String) {
        // 取消之前的请求
        loadUserJob?.cancel()

        loadUserJob = viewModelScope.launch {
            _userState.value = UiState.Loading

            try {
                val user = userRepository.getUser(userId)
                _userState.value = UiState.Success(user)
            } catch (e: CancellationException) {
                // 协程被取消,不处理
            } catch (e: Exception) {
                _userState.value = UiState.Error(e.message ?: "未知错误")
            }
        }
    }

    fun cancelLoadUser() {
        loadUserJob?.cancel()
    }
}

四、数据库操作的协程实践

4.1 Room与协程集成

定义DAO接口:

@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: String): User?

    @Query("SELECT * FROM users")
    fun getAllUsersFlow(): Flow<List<User>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: User)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUsers(users: List<User>)

    @Delete
    suspend fun deleteUser(user: User)

    @Query("DELETE FROM users WHERE id = :userId")
    suspend fun deleteUserById(userId: String)
}

在Repository中使用:

class UserRepository(
    private val userDao: UserDao,
    private val apiService: ApiService
) {
    // 从数据库读取用户(Flow自动在IO线程执行)
    fun observeUsers(): Flow<List<User>> {
        return userDao.getAllUsersFlow()
    }

    // 从网络获取并缓存
    suspend fun refreshUsers() {
        withContext(Dispatchers.IO) {
            val users = apiService.getUsers()
            userDao.insertUsers(users)
        }
    }

    // 单一数据源模式(Single Source of Truth)
    fun getUser(userId: String): Flow<User?> = flow {
        // 1. 先发射数据库中的数据
        emit(userDao.getUserById(userId))

        // 2. 从网络获取最新数据
        try {
            val user = apiService.getUser(userId)
            userDao.insertUser(user)
            // 数据库更新后,Flow会自动发射新值
        } catch (e: Exception) {
            // 网络错误,使用数据库缓存
        }
    }.flowOn(Dispatchers.IO)
}

4.2 实现离线优先策略

17-03-offline-first-flow.png

完整的离线优先实现:

class PostRepository(
    private val postDao: PostDao,
    private val apiService: ApiService,
    private val connectivityManager: ConnectivityManager
) {
    // 观察帖子列表(离线优先)
    fun observePosts(): Flow<List<Post>> = flow {
        // 1. 立即发射本地缓存
        emitAll(postDao.getAllPostsFlow())

        // 2. 如果有网络,从服务器同步
        if (isNetworkAvailable()) {
            try {
                val posts = apiService.getPosts()
                postDao.insertPosts(posts)
                // 数据库更新后会自动触发新的emit
            } catch (e: Exception) {
                // 网络同步失败,继续使用本地缓存
            }
        }
    }.flowOn(Dispatchers.IO)

    // 刷新(强制从网络加载)
    suspend fun refresh(): Result<Unit> {
        return withContext(Dispatchers.IO) {
            try {
                val posts = apiService.getPosts()
                postDao.deleteAllPosts()
                postDao.insertPosts(posts)
                Result.success(Unit)
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }

    private fun isNetworkAvailable(): Boolean {
        val network = connectivityManager.activeNetwork ?: return false
        val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
        return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    }
}

在ViewModel中使用:

class PostListViewModel(
    private val postRepository: PostRepository
) : ViewModel() {

    val posts: StateFlow<List<Post>> = postRepository.observePosts()
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

    private val _isRefreshing = MutableStateFlow(false)
    val isRefreshing = _isRefreshing.asStateFlow()

    fun refresh() {
        viewModelScope.launch {
            _isRefreshing.value = true
            postRepository.refresh()
            _isRefreshing.value = false
        }
    }
}

4.3 批量操作优化

场景: 插入大量数据时使用事务提升性能。

@Dao
interface PostDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertPosts(posts: List<Post>)

    // 使用事务批量删除并插入
    @Transaction
    suspend fun replaceAllPosts(posts: List<Post>) {
        deleteAllPosts()
        insertPosts(posts)
    }

    @Query("DELETE FROM posts")
    suspend fun deleteAllPosts()
}

// 在Repository中使用
suspend fun syncPosts() {
    withContext(Dispatchers.IO) {
        val posts = apiService.getPosts()
        postDao.replaceAllPosts(posts)  // 事务中执行,保证原子性
    }
}

五、UI更新与线程切换

5.1 Dispatchers详解

Kotlin协程提供了四种调度器:

Dispatcher用途线程池
Dispatchers.MainUI更新、轻量级操作主线程
Dispatchers.IO网络、文件、数据库共享线程池(最多64线程)
Dispatchers.DefaultCPU密集型计算线程数=CPU核心数
Dispatchers.Unconfined不限制线程(不推荐)调用者线程

最佳实践:

viewModelScope.launch {
    // 在主线程启动协程

    val user = withContext(Dispatchers.IO) {
        // 切换到IO线程执行网络请求
        apiService.getUser(userId)
    }
    // 自动切回主线程

    // 更新UI(在主线程)
    binding.userName.text = user.name
}

5.2 避免不必要的线程切换

❌ 过度切换:

suspend fun loadData() {
    withContext(Dispatchers.IO) {
        val data = apiService.getData()

        withContext(Dispatchers.Main) {
            // 不必要的切换
            updateUI(data)
        }
    }
}

✅ 优化后:

suspend fun loadData() {
    val data = withContext(Dispatchers.IO) {
        apiService.getData()
    }
    // 自动在主线程
    updateUI(data)
}

5.3 在Activity/Fragment中收集Flow

使用lifecycleScope与repeatOnLifecycle:

class UserProfileFragment : Fragment() {
    private val viewModel: UserViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // ✅ 推荐:生命周期感知
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // 只在STARTED状态收集Flow
                viewModel.userState.collect { state ->
                    when (state) {
                        is UiState.Loading -> showLoading()
                        is UiState.Success -> showUser(state.data)
                        is UiState.Error -> showError(state.message)
                    }
                }
            }
        }
    }
}

为什么使用repeatOnLifecycle(STARTED):

  • Fragment在后台时停止收集,节省资源
  • Fragment回到前台时自动恢复收集
  • 避免内存泄漏和不必要的UI更新

六、异常处理与错误传播

6.1 协程中的异常类型

两种异常类型:

  1. CancellationException: 协程取消,不会传播
  2. 其他异常: 会传播到父协程,导致整个协程树取消

示例:

viewModelScope.launch {
    launch {
        delay(100)
        throw RuntimeException("子协程异常")
        // 这个异常会传播到父协程,导致viewModelScope取消
    }

    launch {
        delay(200)
        println("这行代码不会执行")  // 父协程已被取消
    }
}

6.2 使用SupervisorJob隔离异常

SupervisorJob: 子协程异常不会影响其他子协程。

val supervisorScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)

supervisorScope.launch {
    launch {
        delay(100)
        throw RuntimeException("子协程1异常")
        // 这个异常不会影响子协程2
    }

    launch {
        delay(200)
        println("子协程2继续执行")  // 会执行
    }
}

在ViewModel中使用SupervisorJob:

class RobustViewModel : ViewModel() {
    private val supervisorJob = SupervisorJob()
    private val scope = CoroutineScope(supervisorJob + Dispatchers.Main)

    fun loadMultipleData() {
        scope.launch {
            // 启动多个独立的子任务
            launch {
                try {
                    loadUserData()
                } catch (e: Exception) {
                    handleError("用户数据加载失败", e)
                }
            }

            launch {
                try {
                    loadPostsData()
                } catch (e: Exception) {
                    handleError("帖子数据加载失败", e)
                }
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        supervisorJob.cancel()
    }
}

6.3 全局异常处理

设置CoroutineExceptionHandler:

val exceptionHandler = CoroutineExceptionHandler { _, exception ->
    Log.e("Coroutine", "捕获到未处理的异常", exception)
    // 上报到崩溃收集平台(如Firebase Crashlytics)
}

val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main + exceptionHandler)

scope.launch {
    throw RuntimeException("未捕获的异常")
    // 会被exceptionHandler捕获
}

在Application中设置全局处理器:

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // 设置全局协程异常处理器
        Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
            Log.e("GlobalHandler", "未捕获的异常", throwable)
            // 上报崩溃
            FirebaseCrashlytics.getInstance().recordException(throwable)
        }
    }
}

七、性能优化与常见陷阱

7.1 避免内存泄漏

❌ 陷阱1: 使用GlobalScope

class LeakyActivity : AppCompatActivity() {
    fun loadData() {
        GlobalScope.launch {
            // 危险!Activity销毁后协程仍在运行
            val data = fetchData()
            updateUI(data)  // 可能访问已销毁的Activity
        }
    }
}

✅ 正确方式: 使用lifecycleScope

class SafeActivity : AppCompatActivity() {
    fun loadData() {
        lifecycleScope.launch {
            // Activity销毁时自动取消
            val data = fetchData()
            updateUI(data)
        }
    }
}

❌ 陷阱2: 静态引用持有Context

companion object {
    var coroutineJob: Job? = null  // 危险!
}

fun loadData(context: Context) {
    coroutineJob = GlobalScope.launch {
        // context可能是Activity,导致内存泄漏
        val data = fetchData(context)
    }
}

✅ 正确方式: 使用Application Context

fun loadData(context: Context) {
    val appContext = context.applicationContext

    viewModelScope.launch {
        val data = fetchData(appContext)
    }
}

7.2 控制并发数量

问题: 同时发起1000个网络请求导致OOM。

✅ 解决方案: 使用Semaphore限流

import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit

class ImageLoader {
    private val semaphore = Semaphore(5)  // 最多5个并发

    suspend fun loadImages(urls: List<String>): List<Bitmap> {
        return urls.map { url ->
            async {
                semaphore.withPermit {
                    // 最多同时执行5个
                    loadImage(url)
                }
            }
        }.awaitAll()
    }

    private suspend fun loadImage(url: String): Bitmap {
        // 下载图片
        return withContext(Dispatchers.IO) {
            // 实际下载逻辑
            TODO()
        }
    }
}

7.3 使用Channel避免背压问题

场景: 生产者速度 >> 消费者速度,导致内存占用暴涨。

❌ 使用Flow(无背压控制)

fun produceData(): Flow<Data> = flow {
    repeat(1_000_000) {
        emit(Data(it))  // 快速发射数据
    }
}

fun consumeData() {
    viewModelScope.launch {
        produceData().collect { data ->
            delay(10)  // 消费慢
            process(data)
        }
    }
}

✅ 使用Channel(有容量限制)

val channel = Channel<Data>(capacity = 100)  // 缓冲100个元素

fun produceData() {
    viewModelScope.launch {
        repeat(1_000_000) {
            channel.send(Data(it))  // 缓冲满时挂起
        }
        channel.close()
    }
}

fun consumeData() {
    viewModelScope.launch {
        for (data in channel) {
            delay(10)
            process(data)
        }
    }
}

7.4 避免阻塞Dispatchers.IO线程池

❌ 在IO线程池执行CPU密集型任务

suspend fun processLargeData(data: List<Int>) {
    withContext(Dispatchers.IO) {
        // CPU密集型计算,占用IO线程
        data.map { it * it * it }.sorted()
    }
}

✅ 使用Dispatchers.Default

suspend fun processLargeData(data: List<Int>) {
    withContext(Dispatchers.Default) {
        // CPU密集型任务使用Default
        data.map { it * it * it }.sorted()
    }
}

八、总结与最佳实践清单

核心要点回顾

  1. 使用生命周期感知的作用域

    • viewModelScope for ViewModel
    • lifecycleScope for Activity/Fragment
    • 避免使用GlobalScope
  2. 线程切换

    • Dispatchers.Main - UI操作
    • Dispatchers.IO - 网络、文件、数据库
    • Dispatchers.Default - CPU密集型计算
  3. 错误处理

    • 使用try-catch处理已知异常
    • 使用SupervisorJob隔离子协程异常
    • 设置CoroutineExceptionHandler全局捕获
  4. 性能优化

    • 使用async并发执行独立任务
    • 使用Semaphore控制并发数量
    • 避免在循环中创建协程
  5. 数据流管理

    • 使用StateFlow替代LiveData
    • 使用repeatOnLifecycle收集Flow
    • 实现离线优先策略

最佳实践检查清单

作用域管理:

  • 使用viewModelScope在ViewModel中启动协程
  • 使用lifecycleScope在Activity/Fragment中启动协程
  • 避免使用GlobalScope
  • 在自定义作用域时使用SupervisorJob

线程调度:

  • 网络请求使用Dispatchers.IO
  • 数据库操作使用Dispatchers.IO
  • CPU密集型计算使用Dispatchers.Default
  • UI更新在Dispatchers.Main

异常处理:

  • 在协程顶层使用try-catch
  • 区分CancellationException和其他异常
  • 使用CoroutineExceptionHandler记录未捕获异常
  • 在Repository层捕获并转换异常

性能优化:

  • 使用async并发执行独立任务
  • 使用Semaphore限制并发数量
  • 避免阻塞Dispatcher线程池
  • 使用Flow.buffer()控制背压

测试:

  • 使用runTest测试suspend函数
  • 使用StandardTestDispatcher控制测试时间
  • Mock Repository和API
  • 测试不同的UI状态

学习资源

官方文档:

推荐阅读:

  • 《Kotlin Coroutines by Tutorials》
  • 《Asynchronous Programming with Kotlin》
  • Roman Elizarov的博客(协程库作者)

视频教程:

  • Android Developers YouTube频道 - Coroutines系列
  • Kotlin Conf演讲 - Coroutines相关主题

系列文章导航:


如果这篇文章对你有帮助,欢迎点赞、收藏、分享!有任何问题或建议,欢迎在评论区留言讨论。让我们一起学习,一起成长!

也欢迎访问我的个人主页发现更多宝藏资源