跳转到正文
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 高级操作符与背压控制
YGJoon 的头像

YGJoon

记录 Flutter、Web 与原生移动端工程实践。更多背景见关于页,源码与项目见GitHub。