| 分类 android  | 标签 Kotlin  协程  Android  异步编程 

title

引言

Kotlin 协程是一种轻量级的并发编程解决方案,它为 Android 开发者提供了一种优雅的方式来处理异步操作。相比传统的回调和线程管理方式,协程能够让异步代码看起来像同步代码一样简洁易读,同时避免了回调地狱的问题。本文将深入探讨 Kotlin 协程在 Android 开发中的应用,包括基础概念、实际使用场景以及最佳实践。

协程基础概念

什么是协程

协程是一种可以暂停和恢复执行的计算实例。在 Kotlin 中,协程提供了一种编写异步代码的方式,这种代码在语法上类似于同步代码,但实际上是非阻塞的。

// 传统的异步代码
fun fetchUserData(callback: (User?) -> Unit) {
    networkService.getUser { user ->
        databaseService.saveUser(user) { success ->
            if (success) {
                callback(user)
            } else {
                callback(null)
            }
        }
    }
}

// 使用协程的代码
suspend fun fetchUserData(): User? {
    return try {
        val user = networkService.getUser()
        databaseService.saveUser(user)
        user
    } catch (e: Exception) {
        null
    }
}

核心概念

1. suspend 函数

suspend 关键字标记的函数可以被暂停和恢复,它们只能在协程或其他 suspend 函数中调用。

suspend fun performNetworkCall(): String {
    delay(1000) // 模拟网络延迟
    return "Network response"
}

suspend fun processData() {
    val data = performNetworkCall()
    println("Received: $data")
}

2. 协程构建器

协程构建器用于启动新的协程:

// launch - 启动一个新协程,不返回结果
viewModelScope.launch {
    val data = fetchData()
    updateUI(data)
}

// async - 启动一个新协程,返回 Deferred 结果
val deferred = viewModelScope.async {
    fetchData()
}
val result = deferred.await()

// runBlocking - 阻塞当前线程直到协程完成(主要用于测试)
runBlocking {
    val result = fetchData()
    println(result)
}

3. 协程作用域

协程作用域定义了协程的生命周期:

class MyViewModel : ViewModel() {
    
    fun loadData() {
        // viewModelScope 会在 ViewModel 清除时自动取消
        viewModelScope.launch {
            try {
                val data = repository.fetchData()
                _uiState.value = UiState.Success(data)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
            }
        }
    }
}

Android 中的协程应用

1. 网络请求

使用协程处理网络请求是最常见的应用场景:

class UserRepository {
    private val apiService: ApiService = RetrofitClient.apiService
    
    suspend fun getUser(userId: String): Result<User> {
        return try {
            val user = apiService.getUser(userId)
            Result.success(user)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    suspend fun getUsersParallel(userIds: List<String>): List<User> {
        return userIds.map { userId ->
            async { apiService.getUser(userId) }
        }.awaitAll()
    }
}

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

2. 数据库操作

Room 数据库完美支持协程:

@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: String): User?
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: User)
    
    @Query("SELECT * FROM users")
    fun getAllUsersFlow(): Flow<List<User>>
}

class UserRepository {
    private val userDao: UserDao
    
    suspend fun saveUser(user: User) {
        withContext(Dispatchers.IO) {
            userDao.insertUser(user)
        }
    }
    
    fun observeUsers(): Flow<List<User>> {
        return userDao.getAllUsersFlow()
    }
}

3. UI 更新

在 ViewModel 中使用协程更新 UI:

class UserViewModel(private val repository: UserRepository) : ViewModel() {
    
    private val _uiState = MutableLiveData<UiState<List<User>>>()
    val uiState: LiveData<UiState<List<User>>> = _uiState
    
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading
    
    fun loadUsers() {
        viewModelScope.launch {
            _isLoading.value = true
            try {
                val users = repository.getUsers()
                _uiState.value = UiState.Success(users)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Unknown error")
            } finally {
                _isLoading.value = false
            }
        }
    }
    
    fun refreshUsers() {
        viewModelScope.launch {
            try {
                val users = repository.refreshUsers()
                _uiState.value = UiState.Success(users)
            } catch (e: Exception) {
                // 处理错误,但不改变当前状态
                showError(e.message)
            }
        }
    }
}

Flow 和响应式编程

Flow 基础

Flow 是 Kotlin 协程库中的响应式流,用于处理异步数据流:

class DataRepository {
    
    fun getDataStream(): Flow<List<Data>> = flow {
        while (true) {
            val data = fetchDataFromNetwork()
            emit(data)
            delay(5000) // 每5秒更新一次
        }
    }.flowOn(Dispatchers.IO)
    
    fun searchUsers(query: String): Flow<List<User>> = flow {
        if (query.length >= 3) {
            val users = apiService.searchUsers(query)
            emit(users)
        } else {
            emit(emptyList())
        }
    }
}

Flow 操作符

class UserViewModel : ViewModel() {
    
    private val searchQuery = MutableStateFlow("")
    
    val searchResults = searchQuery
        .debounce(300) // 防抖动
        .filter { it.length >= 3 } // 过滤短查询
        .distinctUntilChanged() // 去重
        .flatMapLatest { query ->
            repository.searchUsers(query)
                .catch { emit(emptyList()) } // 错误处理
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )
    
    fun updateSearchQuery(query: String) {
        searchQuery.value = query
    }
}

错误处理和异常管理

结构化并发

协程提供了结构化并发,确保异常能够正确传播:

class DataProcessor {
    
    suspend fun processDataSafely(): Result<ProcessedData> {
        return try {
            coroutineScope {
                val data1 = async { fetchData1() }
                val data2 = async { fetchData2() }
                val data3 = async { fetchData3() }
                
                val results = awaitAll(data1, data2, data3)
                val processed = processResults(results)
                Result.success(processed)
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

异常处理策略

class NetworkRepository {
    
    suspend fun fetchDataWithRetry(
        maxRetries: Int = 3,
        delayMs: Long = 1000
    ): Result<Data> {
        repeat(maxRetries) { attempt ->
            try {
                val data = apiService.fetchData()
                return Result.success(data)
            } catch (e: Exception) {
                if (attempt == maxRetries - 1) {
                    return Result.failure(e)
                }
                delay(delayMs * (attempt + 1)) // 指数退避
            }
        }
        return Result.failure(Exception("Max retries exceeded"))
    }
}

性能优化

1. 选择合适的调度器

class OptimizedRepository {
    
    suspend fun performCpuIntensiveTask(): Result<String> {
        return withContext(Dispatchers.Default) {
            // CPU 密集型任务
            processLargeDataSet()
        }
    }
    
    suspend fun performIoOperation(): Result<String> {
        return withContext(Dispatchers.IO) {
            // I/O 操作
            readFromFile()
        }
    }
    
    suspend fun updateUI(data: String) {
        withContext(Dispatchers.Main) {
            // UI 更新
            updateUserInterface(data)
        }
    }
}

2. 协程池管理

class CustomDispatcherExample {
    
    // 自定义线程池
    private val customDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
    
    suspend fun performCustomTask() {
        withContext(customDispatcher) {
            // 使用自定义调度器
            performSpecializedTask()
        }
    }
    
    fun cleanup() {
        customDispatcher.close()
    }
}

测试协程代码

单元测试

class UserViewModelTest {
    
    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()
    
    private val mockRepository = mockk<UserRepository>()
    private lateinit var viewModel: UserViewModel
    
    @Before
    fun setup() {
        viewModel = UserViewModel(mockRepository)
    }
    
    @Test
    fun `loadUsers should update uiState with success`() = runTest {
        // Given
        val expectedUsers = listOf(User("1", "John"))
        coEvery { mockRepository.getUsers() } returns expectedUsers
        
        // When
        viewModel.loadUsers()
        
        // Then
        val uiState = viewModel.uiState.value
        assertTrue(uiState is UiState.Success)
        assertEquals(expectedUsers, (uiState as UiState.Success).data)
    }
    
    @Test
    fun `loadUsers should handle errors gracefully`() = runTest {
        // Given
        val exception = RuntimeException("Network error")
        coEvery { mockRepository.getUsers() } throws exception
        
        // When
        viewModel.loadUsers()
        
        // Then
        val uiState = viewModel.uiState.value
        assertTrue(uiState is UiState.Error)
        assertEquals("Network error", (uiState as UiState.Error).message)
    }
}

最佳实践

1. 避免内存泄漏

// 好的做法:使用适当的作用域
class MyActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 使用 lifecycleScope,会在 Activity 销毁时自动取消
        lifecycleScope.launch {
            val data = fetchData()
            updateUI(data)
        }
    }
}

// 避免的做法:使用 GlobalScope
class MyActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 不好的做法:可能导致内存泄漏
        GlobalScope.launch {
            val data = fetchData()
            updateUI(data) // Activity 可能已经销毁
        }
    }
}

2. 合理使用 suspend 函数

// 好的做法:suspend 函数应该是真正的挂起函数
suspend fun fetchDataFromNetwork(): String {
    return withContext(Dispatchers.IO) {
        // 真正的异步操作
        networkClient.getData()
    }
}

// 避免的做法:不必要的 suspend
suspend fun simpleCalculation(a: Int, b: Int): Int {
    return a + b // 这不需要是 suspend 函数
}

3. 异常处理

class RobustRepository {
    
    suspend fun fetchDataSafely(): Result<Data> {
        return try {
            val data = apiService.fetchData()
            Result.success(data)
        } catch (e: CancellationException) {
            // 重新抛出取消异常
            throw e
        } catch (e: Exception) {
            // 处理其他异常
            Result.failure(e)
        }
    }
}

总结

Kotlin 协程为 Android 开发带来了革命性的变化,它提供了一种优雅、高效的方式来处理异步操作。通过合理使用协程,开发者可以编写出更加简洁、可读性更强的代码,同时避免传统异步编程中的常见问题。

掌握协程的关键在于理解其核心概念,包括 suspend 函数、协程作用域、Flow 等,并在实际项目中遵循最佳实践。随着 Android 开发生态系统对协程支持的不断完善,协程已经成为现代 Android 开发不可或缺的技能。

建议开发者在新项目中积极采用协程,并逐步将现有项目中的异步代码迁移到协程上来,以获得更好的开发体验和应用性能。


上一篇     下一篇