四大组件不是四种页面
它们是操作系统进入你进程的四扇门;后台执行题的核心,是谁承诺这项工作必须活多久。
用户按发送后立刻把应用划走。这条医疗消息必须最终上传,允许等网络恢复。首选什么?
viewModelScope.launch- 普通后台
Service - 带网络约束的唯一 WorkManager 工作
- 精确
AlarmManager
组件是系统合同
Activity 是有界面、能接收 Intent 的交互入口。Service 不是线程,而是告诉系统「这项没有页面的工作仍有用户可感知价值」的组件;代码默认仍跑主线程。BroadcastReceiver 接受短促事件,回调必须尽快返回。ContentProvider 用 URI 与权限暴露结构化数据,是跨进程数据合同。
Compose 改了 UI 写法,没有改这四种系统合同。你的单 Activity 应用仍靠 Manifest、Intent、任务栈、进程优先级与权限模型活着。
后台工作先问三个问题
- 离开页面后还要不要继续?不要:页面或 ViewModel 的协程。
- 进程被杀后还要不要完成?要:可持久调度的 WorkManager。
- 是否必须立刻、持续、且用户可感知?是:满足类型限制的前台服务,并展示通知。
WorkManager 适合可靠、可延迟、带约束的工作,例如发待同步消息、上传已经落到私有临时目录的附件。它会把调度意图持久化,尊重 Doze 与系统限制,并提供退避重试。它不保证某个精确时刻开始,也不是常驻进程。
| 需求 | 工具 | 关键理由 |
|---|---|---|
| 页面可见时搜索 | 协程 | 页面消失即可取消 |
| 离线消息最终发送 | WorkManager | 跨进程、约束、重试 |
| 用户正在录音 | 前台服务 | 立刻持续,用户必须知情 |
| 日历在准确时刻提醒 | AlarmManager | 真正的时间语义 |
| 推送到达 | FCM 接收入口 | 由系统唤起,处理应短 |
Intent 是能力请求
显式 Intent 指定你自己的组件;隐式 Intent 描述「我想完成什么」,由系统匹配能力。外部输入一律不可信:深链参数、分享 URI、通知 PendingIntent 都要验证。导出组件必须明确 android:exported,敏感能力要加权限或在入口完成身份校验。
医疗图片尤其要避免裸文件路径与广泛存储权限。用系统 Photo Picker 或受限相机流程获得最小访问,用 content:// URI 与临时授权分享,不把临床照片写进公共相册。
为「待发消息」排一个唯一同步任务。连续点重试时,结果不是创建许多上传,而是同名工作保持唯一:
workName=sync-message-c-1 constraint=CONNECTED policy=KEEP result=at most one active chain
fun enqueueMessageSync(context: Context, clientId: String) {
val request = OneTimeWorkRequestBuilder<SendMessageWorker>()
.setInputData(workDataOf("clientId" to clientId))
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
)
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
10,
TimeUnit.SECONDS
)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
"sync-message-$clientId",
ExistingWorkPolicy.KEEP,
request
)
}在 Android Studio 的最小应用里运行,先开飞行模式排队,再恢复网络。用 WorkManager Inspector 确认状态从 ENQUEUED 进入 RUNNING 与 SUCCEEDED。
用户投诉「消息顺序乱」时,根因未必在 RecyclerView 或 Compose。多个 Worker 并发、每次重试生成新 ID、服务端按到达时间排序,都可能把 UI 正确渲染的错误事实送回来。后台调度只负责「再试」,顺序与幂等要由数据协议保证。
「Service 会自动开后台线程,而且比协程可靠。」Service 默认在主线程;普通后台 Service 还受严格启动限制。
把「执行位置」「组件寿命」「持久承诺」分开回答。协程处理进程内并发,前台服务表达用户可感知的持续工作,WorkManager 表达跨进程的最终完成。
答案是第 3 项。任务允许延迟、需要网络、必须跨进程最终完成,正是 WorkManager 的合同。viewModelScope 随 ViewModel 结束,普通 Service 不等于可靠队列,AlarmManager 只该用于真正精确的闹钟语义。
这一章的一句话
先说这项工作必须活多久、是否立刻、用户是否知情,再说 API 名字。
下一章回到 Kotlin:同一段 Java 风格代码改成 Kotlin 后,真正该少掉的不是字符,而是三个本来能进入运行时的非法分支。