CodeRoadMap
首页路线题库资源库文章社区
CodeRoadMap

程序员的学习成长路线图。内容免费开放,登录后可同步路线图与课时进度。

由 程序员柏风 独立维护 · 关于作者

学习

  • 路线图
  • 课程
  • 面试题
  • 文章
  • 我

更多

  • 资源
  • 社区
  • 关于作者
  • 内容说明

© 2026 CodeRoadMap

津ICP备2026012044号-1·津公网安备12011002026155号·coderoadmap@126.com
Android 开发入门/课时/异步与响应式
RawBlame
课时目录12 节

Android 开发入门

  1. 01Android 开发概览
  2. 02四大组件
  3. 03传统 View UI
  4. 04Jetpack Compose
  5. 05架构模式
  6. 06依赖注入
  7. 07本地存储
  8. 08网络层
  9. 09异步与响应式
  10. 10Firebase 生态
  11. 11测试与质量
  12. 12综合练习

异步与响应式

Coroutines/Flow/WorkManager。

35 分钟

异步与响应式

这一课你将能做到

  1. 用 Kotlin Coroutines 处理异步任务,理解 suspend、launch、async 的区别
  2. 使用 Flow 构建响应式数据流,区分冷流与热流,掌握 StateFlow 与 SharedFlow
  3. 用 WorkManager 调度后台任务,理解其与 Coroutines 的协作方式

为什么要学

移动设备资源有限,主线程必须保持 60fps 流畅。网络请求、数据库查询、文件读写都不能阻塞 UI。Coroutines 是 Kotlin 的异步解决方案,Flow 是响应式数据流,WorkManager 是可靠的后台任务调度。三者结合,构成现代 Android 异步编程的完整图景。

人话:Coroutines 就像「高效的餐厅服务员」——不用为每桌客人(任务)配一个服务员(线程),而是少量服务员(协程)轮流服务多桌,谁需要服务就去谁那里。Flow 是「传送带」,数据像菜品一样源源不断送来。WorkManager 是「预约系统」,确保重要任务即使 App 关闭也能执行。

面试中,「suspend 原理」、「Flow vs LiveData」、「WorkManager 适用场景」是必考题。

先建立图像

线程模型对比:

传统线程(一任务一线程):
┌─────┐ ┌─────┐ ┌─────┐
│Thread│ │Thread│ │Thread│  ← 每个任务独占线程
│  1   │ │  2   │ │  3   │     内存开销大
└─────┘ └─────┘ └─────┘

协程(多任务共享线程):
┌─────────────────────────┐
│        Thread Pool       │
│  ┌────┐ ┌────┐ ┌────┐   │
│  │Cor1│ │Cor2│ │Cor3│   │  ← 协程挂起时释放线程
│  └────┘ └────┘ └────┘   │     内存开销极小
│  ┌────┐ ┌────┐ ┌────┐   │
│  │Cor4│ │Cor5│ │Cor6│   │
│  └────┘ └────┘ └────┘   │
└─────────────────────────┘

Flow 数据流:
┌────────┐    ┌────────┐    ┌────────┐
│  源头   │ → │  操作符  │ → │  收集器  │
│ (Room) │    │map/filter│    │ (Compose)│
└────────┘    └────────┘    └────────┘
     ↑ 数据变化时自动推送,UI 自动更新

跟着做

步骤 1:Coroutines 基础

// 在 ViewModel 中启动协程
@HiltViewModel
class TrackViewModel @Inject constructor(
    private val repository: TrackRepository
) : ViewModel() {

    // launch:启动协程,不返回结果
    fun loadTracks() {
        viewModelScope.launch {
            // 自动在 Main 线程启动,可切换
            val result = withContext(Dispatchers.IO) {
                repository.getTracks()  // 网络/数据库操作
            }
            // 回到 Main 线程更新 UI
            _uiState.value = result
        }
    }

    // async:启动协程,返回 Deferred 结果
    fun loadTrackAndLessons(slug: String) {
        viewModelScope.launch {
            // 并行执行两个任务
            val trackDeferred = async { repository.getTrack(slug) }
            val lessonsDeferred = async { repository.getLessons(slug) }
            
            val track = trackDeferred.await()
            val lessons = lessonsDeferred.await()
            
            _detailState.value = DetailState(track, lessons)
        }
    }

    // 异常处理
    fun safeLoad() {
        viewModelScope.launch {
            try {
                val data = repository.getTracks()
                _uiState.value = UiState.Success(data)
            } catch (e: CancellationException) {
                throw e  // 协程取消异常必须重新抛出
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message)
            }
        }
    }

    // 使用 supervisorScope:子协程失败不影响兄弟协程
    fun loadAllData() {
        viewModelScope.launch {
            supervisorScope {
                val trackJob = launch { loadTracks() }
                val userJob = launch { loadUser() }
                // trackJob 失败不会影响 userJob
            }
        }
    }
}

步骤 2:Flow 操作符实战

@Singleton
class TrackRepository @Inject constructor(
    private val dao: TrackDao,
    private val api: TrackApiService
) {
    // 冷流:每次收集都重新执行
    val tracks: Flow<List<TrackEntity>> = dao.getAllTracks()
        .map { entities ->
            entities.sortedByDescending { it.updatedAt }
        }
        .filter { it.isNotEmpty() }
        .catch { e ->
            // 错误处理,发射空列表
            emit(emptyList())
        }
        .flowOn(Dispatchers.IO)  // 上游在 IO 线程执行

    // 搜索:防抖 + 切换
    private val _searchQuery = MutableStateFlow("")
    val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()

    val searchResults: Flow<List<TrackEntity>> = _searchQuery
        .debounce(300L)  // 防抖,300ms 内输入不触发
        .distinctUntilChanged()  // 相同查询不重复
        .flatMapLatest { query ->
            if (query.isBlank()) {
                dao.getAllTracks()
            } else {
                dao.searchTracks("%$query%")
            }
        }
        .flowOn(Dispatchers.IO)

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

// ViewModel 中转换为 StateFlow
@HiltViewModel
class SearchViewModel @Inject constructor(
    private val repository: TrackRepository
) : ViewModel() {

    val searchResults: StateFlow<List<TrackUiModel>> = repository.searchResults
        .map { entities -> entities.map { it.toUiModel() } }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

    fun onQueryChange(query: String) {
        repository.updateSearchQuery(query)
    }
}

步骤 3:StateFlow 与 SharedFlow

@HiltViewModel
class MainViewModel @Inject constructor() : ViewModel() {

    // StateFlow:状态,有初始值,粘性(新收集者立即获得当前值)
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    // SharedFlow:事件,无初始值,非粘性(适合一次性事件)
    private val _events = MutableSharedFlow<UiEvent>(
        replay = 0,  // 不重放,新订阅者收不到旧事件
        extraBufferCapacity = 1  // 缓冲 1 个事件,防止丢失
    )
    val events: SharedFlow<UiEvent> = _events.asSharedFlow()

    fun onButtonClick() {
        viewModelScope.launch {
            // 发送一次性事件:显示 Toast、导航
            _events.emit(UiEvent.ShowToast("操作成功"))
        }
    }

    sealed class UiEvent {
        data class ShowToast(val message: String) : UiEvent()
        object NavigateToDetail : UiEvent()
    }
}

// Compose 中收集
@Composable
fun MainScreen(viewModel: MainViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    
    // 收集一次性事件
    LaunchedEffect(Unit) {
        viewModel.events.collect { event ->
            when (event) {
                is MainViewModel.UiEvent.ShowToast -> {
                    // 显示 Toast
                }
                is MainViewModel.UiEvent.NavigateToDetail -> {
                    // 导航
                }
            }
        }
    }
}

步骤 4:WorkManager 后台任务

// 定义 Worker
class SyncWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted params: WorkerParameters,
    private val repository: TrackRepository
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        return try {
            repository.syncTracks()  // 同步数据
            repository.syncProgress()
            Result.success()
        } catch (e: Exception) {
            if (runAttemptCount < 3) {
                Result.retry()  // 重试
            } else {
                Result.failure()
            }
        }
    }

    companion object {
        const val WORK_NAME = "sync_worker"
    }
}

// 使用 Hilt 注入 Worker
@HiltWorker
class SyncWorker @AssistedInject constructor(
    @Assisted context: Context,
    @Assisted params: WorkerParameters,
    private val repository: TrackRepository
) : CoroutineWorker(context, params) { ... }

// 调度任务
fun scheduleSyncWork(context: Context) {
    val constraints = Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)  // 需要网络
        .setRequiresBatteryNotLow(true)  // 电量不低
        .build()

    val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
        repeatInterval = 12,  // 每 12 小时
        repeatIntervalTimeUnit = TimeUnit.HOURS
    )
        .setConstraints(constraints)
        .setBackoffCriteria(
            BackoffPolicy.EXPONENTIAL,  // 指数退避重试
            WorkRequest.MIN_BACKOFF_MILLIS,
            TimeUnit.MILLISECONDS
        )
        .build()

    WorkManager.getInstance(context).enqueueUniquePeriodicWork(
        SyncWorker.WORK_NAME,
        ExistingPeriodicWorkPolicy.UPDATE,  // 已存在则更新
        syncRequest
    )
}

// 观察任务状态
fun observeWorkStatus(context: Context): Flow<WorkInfo?> {
    return WorkManager.getInstance(context)
        .getWorkInfosForUniqueWorkFlow(SyncWorker.WORK_NAME)
        .map { it.firstOrNull() }
}

步骤 5:Coroutines 与 Flow 结合的最佳实践

@HiltViewModel
class TrackDetailViewModel @Inject constructor(
    private val repository: TrackRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val trackSlug: String = savedStateHandle["trackSlug"] ?: ""

    // 组合多个 Flow
    val detailState: StateFlow<DetailUiState> = combine(
        repository.getTrack(trackSlug),
        repository.getLessons(trackSlug),
        repository.getProgress(trackSlug)
    ) { track, lessons, progress ->
        DetailUiState(
            track = track?.toUiModel(),
            lessons = lessons.map { it.toUiModel() },
            progress = progress,
            isLoading = false
        )
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = DetailUiState(isLoading = true)
    )

    // 带加载状态的一次性操作
    private val _refreshState = MutableStateFlow<RefreshState>(RefreshState.Idle)
    val refreshState: StateFlow<RefreshState> = _refreshState.asStateFlow()

    fun refresh() {
        viewModelScope.launch {
            _refreshState.value = RefreshState.Loading
            try {
                repository.refreshTrack(trackSlug)
                _refreshState.value = RefreshState.Success
            } catch (e: Exception) {
                _refreshState.value = RefreshState.Error(e.message)
            }
        }
    }

    sealed class RefreshState {
        object Idle : RefreshState()
        object Loading : RefreshState()
        object Success : RefreshState()
        data class Error(val message: String?) : RefreshState()
    }
}

讲清楚要点

  1. suspend 不是「切换线程」:suspend 是「挂起」,允许协程在 await 时释放线程,恢复时继续执行。线程切换由 withContext(Dispatchers.IO) 显式声明。

  2. viewModelScope 自动取消:ViewModel 销毁时,viewModelScope 自动取消所有协程,防止内存泄漏。这是用 viewModelScope 而不是 GlobalScope 的核心原因。

  3. 冷流 vs 热流:Flow 是冷流,每次 collect 都重新执行;StateFlow/SharedFlow 是热流,共享数据,多个收集者看到同一数据。UI 状态用 StateFlow,一次性事件用 SharedFlow。

  4. stateIn 的三个参数:scope 决定生命周期;started 决定何时开始共享(WhileSubscribed 5000 表示最后一个订阅者消失后 5 秒停止);initialValue 是初始状态。

  5. WorkManager 是「可靠」的后台任务:即使 App 被杀、设备重启,WorkManager 也会调度执行。适合数据同步、日志上报。不适合实时性要求高的任务。

你可能会懵的地方

  • suspend 函数和 regular 函数有什么区别? suspend 函数只能在协程或另一个 suspend 函数中调用。它可以在不阻塞线程的情况下挂起,等待异步结果。编译器会把它转成状态机。

  • Dispatchers.Main、IO、Default 怎么选? Main:UI 更新;IO:网络、数据库、文件;Default:CPU 密集型计算(如 JSON 解析、排序)。用错会导致卡顿或 ANR。

  • Flow 的 collect 会阻塞吗? collect 是 suspend 函数,会挂起当前协程直到 Flow 完成。冷流可能无限执行(如数据库观察),需要显式取消或依赖作用域自动取消。

  • WorkManager 和 AlarmManager 什么关系? AlarmManager 是系统级定时器,精确但不保证执行(Doze 模式限制)。WorkManager 是任务调度器,保证执行但不一定准时。需要精确定时用 AlarmManager,需要可靠执行用 WorkManager。

  • Coroutines 能替代 RxJava 吗? 大多数场景可以。Flow 提供类似的操作符(map、filter、flatMap)。但 RxJava 的背压策略更丰富,复杂事件流处理可能仍有优势。新项目推荐 Coroutines。

错法对照

错法问题正解
用 GlobalScope.launch生命周期泄漏,无法取消viewModelScope 或 lifecycleScope
在 Dispatchers.Main 里做网络请求阻塞 UI,ANRwithContext(Dispatchers.IO)
忘记处理 CancellationException协程无法正常取消catch 后重新抛出
用 SharedFlow 存 UI 状态新订阅者收不到当前状态用 StateFlow
WorkManager 里直接更新 UIWorker 无 UI 上下文发送广播或更新数据库,UI 观察数据库

卡点急救

  1. 协程不执行或立即结束:检查作用域是否正确。在 Activity 里用 lifecycleScope,在 ViewModel 里用 viewModelScope。不要在 Composable 里直接 launch,用 LaunchedEffect。

  2. Flow 收集不到数据:检查是否 flowOn 切换了线程导致收集在错误的线程。或检查上游是否实际发射了数据(如 Room 查询条件不匹配)。

  3. WorkManager 任务不执行:检查约束条件是否满足(如需要网络但设备离线)。用 WorkManager.getInstance(context).getWorkInfoByIdLiveData(id) 观察状态。

面试常问

  • suspend 函数的原理? 编译器把 suspend 函数转成状态机,挂起点保存状态(Continuation),恢复时从挂起点继续执行。不是真正的「阻塞」,而是「挂起-恢复」,线程在挂起时被释放。

  • StateFlow 和 LiveData 的区别? StateFlow 是 Kotlin 协程原生,支持 Flow 所有操作符,需要初始值,总是粘性。LiveData 是 Android 生态产物,生命周期感知,但操作符少,在 Kotlin 中逐渐被 Flow 取代。

  • WorkManager 如何保证任务执行? 基于 JobScheduler/AlarmManager/Firebase JobDispatcher 的封装,根据系统版本选择最佳实现。任务持久化到数据库,设备重启后重新调度。支持约束条件(网络、电量、存储)。

自测三问

  1. 解释 viewModelScope.launch 和 GlobalScope.launch 的区别,以及为什么前者更安全。
  2. 什么场景用 StateFlow,什么场景用 SharedFlow?各举一个例子。
  3. 描述 WorkManager 的执行流程:从调度到执行到结果返回。

小结一句话

Coroutines 让异步代码像同步一样简洁,Flow 让数据流像管道一样清晰,WorkManager 让后台任务像闹钟一样可靠;三者结合,构建出现代 Android 的响应式架构。

延伸

  • Kotlin Coroutines 官方文档
  • Flow 官方文档
  • WorkManager 官方文档
  • 下一课:Firebase 生态——Auth、FCM、Firestore 与 Crashlytics。
加载本节练习…
← 上一课网络层下一课 →Firebase 生态