跳转到正文
YGJoon 技术博客

Android Kotlin 协程在实际项目中的应用

发布于:
·959 字 · 约 2 分钟·作者:
协程在真实项目里的组合用法:Retrofit 并发请求、Room 事务、ViewModel 作用域,互斥锁与限流器解决并发冲突。
Android Kotlin 协程在实际项目中的应用文章封面
目录

网络请求最佳实践

Retrofit + 协程

interface ApiService {
    @GET("/user/profile")
    suspend fun getUserProfile(): ApiResponse<User>
}

class UserRepository @Inject constructor(private val api: ApiService) {
    suspend fun loadProfile(): Result<User> = runCatching {
        api.getUserProfile().requireData()
    }
}

并发请求

class DashboardRepository @Inject constructor(private val api: ApiService) {
    suspend fun loadDashboard(): Dashboard = coroutineScope {
        val userDeferred = async { api.getUserProfile() }
        val feedDeferred = async { api.getFeed() }
        Dashboard(userDeferred.await().data, feedDeferred.await().items)
    }
}

Room 数据库

@Dao
interface ArticleDao {
    @Query("SELECT * FROM article WHERE id = :id")
    suspend fun getById(id: Long): ArticleEntity?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(list: List<ArticleEntity>)
}

class ArticleRepository @Inject constructor(
    private val dao: ArticleDao,
    private val api: ApiService
) {
    fun observeArticles(): Flow<List<Article>> = flow {
        emit(dao.queryAll().map { it.toDomain() })
        val remote = api.fetchArticles()
        dao.insertAll(remote.map { it.toEntity() })
        emit(dao.queryAll().map { it.toDomain() })
    }.flowOn(Dispatchers.IO)
}

UI 层协程模式

ViewModel

@HiltViewModel
class HomeViewModel @Inject constructor(
    private val repository: HomeRepository
) : ViewModel() {

    private val _uiState = MutableStateFlow<HomeUiState>(HomeUiState.Loading)
    val uiState: StateFlow<HomeUiState> = _uiState

    fun refresh() = viewModelScope.launch {
        repository.fetchHome()
            .onFailure { _uiState.value = HomeUiState.Error(it) }
            .onSuccess { _uiState.value = HomeUiState.Success(it) }
    }
}

LifecycleScope

class HomeFragment : Fragment(R.layout.fragment_home) {
    private val viewModel: HomeViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { render(it) }
            }
        }
    }
}

并发控制与节流

1. 互斥锁

class TokenManager {
    private val mutex = Mutex()
    private var token: String? = null

    suspend fun refreshToken(force: Boolean = false): String {
        return mutex.withLock {
            if (token == null || force) {
                token = api.refreshToken()
            }
            token!!
        }
    }
}

2. 限流器

class RateLimiter(private val intervalMs: Long) {
    private var lastTime = 0L

    suspend fun acquire() {
        val now = SystemClock.elapsedRealtime()
        val elapsed = now - lastTime
        if (elapsed < intervalMs) delay(intervalMs - elapsed)
        lastTime = SystemClock.elapsedRealtime()
    }
}

性能调优

viewModelScope.launch {
    searchQuery.debounce(300)
        .distinctUntilChanged()
        .flatMapLatest { keyword -> repository.search(keyword) }
        .flowOn(Dispatchers.IO)
        .collect { result ->
            _uiState.value = HomeUiState.Success(result)
        }
}

协程测试

@OptIn(ExperimentalCoroutinesApi::class)
class HomeViewModelTest {
    private val dispatcher = StandardTestDispatcher()

    @Before
    fun setup() {
        Dispatchers.setMain(dispatcher)
    }

    @Test
    fun `refresh success`() = runTest {
        viewModel.refresh()
        dispatcher.scheduler.advanceUntilIdle()
        assertEquals(HomeUiState.Success(expected), viewModel.uiState.value)
    }

    @After
    fun tearDown() {
        Dispatchers.resetMain()
    }
}

常见问题

问题原因解决方案
协程未取消作用域与生命周期不匹配使用 viewModelScope/lifecycleScope
内存泄漏launch 引用 UI 对象但未取消使用弱引用或在 onDestroyView 取消
并发请求失败不回收资源未使用结构化并发使用 coroutineScope 或 supervisorScope

实战案例:Dart 的 Stream vs Kotlin 的 Channel

我在 Flutter 的串口项目(flutter_brightness)里,用 Dart 的 StreamController.broadcast() 把串口数据抛给 UI 层。Kotlin 的 Channel 做的是同一件事,但语义更严格:

Dart 的 broadcast Stream 是”广播”——发了就发了,没人听就丢了,也没有背压概念。而 Kotlin Channel 默认是 Rendezvous(容量 0):send() 会挂起直到有人 receive(),这叫”交接”。如果生产者比消费者快,不加缓冲就是死锁,加 Channel.BUFFERED 或 CONFLATED(只保留最新)才能跑。

我的串口场景如果用 Kotlin 写,选型会是 CONFLATED——光照传感器每 100ms 吐一个数,UI 只关心最新值,旧数据堆积毫无意义。Dart 里我得自己在 listener 里做节流,Kotlin 直接用 Channel 的容量语义就解决了。

一句话:Dart Stream 是”水管”,Kotlin Channel 是”带阀门的水管”。理解背压,就理解了 Channel 存在的意义。

参考:Kotlin 官方文档 Channels 章节(kotlinlang.org)

总结

  1. 协程可简化网络、数据库等异步操作,结合结构化并发保障稳定性。
  2. 使用框架提供的作用域(ViewModel、Lifecycle)可自动管理生命周期。
  3. 性能调优与测试必不可少,合理使用调度器、互斥锁与限流器是大型项目稳定运行的关键。

下一篇将聚焦 Kotlin Flow,打造响应式的数据流管道。


分享这篇文章:


相关文章

上一篇
Android Kotlin WorkManager 后台任务策略
下一篇
Android Kotlin Flow 高级操作符与背压控制