引言
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 开发不可或缺的技能。
建议开发者在新项目中积极采用协程,并逐步将现有项目中的异步代码迁移到协程上来,以获得更好的开发体验和应用性能。
上一篇 下一篇