13、Service 管理:AMS对Service的启动、绑定、生命周期调度
Service 这玩意儿,说白了就是 Android 里跑后台任务的组件。很多刚入行的朋友容易把它跟线程搞混——其实 Service 默认是跑在主线程的,你如果不在里面开子线程,照样会 ANR。我当年就踩过这个坑,写了个 Service 做网络请求,结果直接卡死,还被测试小姐姐追着骂了一整天。
今天咱们就深入 AMS 层面,看看 Service 到底是怎么被创建、绑定、销毁的。嗯,这部分代码量不小,但逻辑其实挺清晰的。
13.1 Service 的启动流程
你调用 startService() 的时候,背后发生了什么?我习惯把整个过程拆成三个阶段:
- 应用进程 -> AMS:通过 Binder 跨进程调用
- AMS 内部处理:检查权限、判断进程是否存在、调度生命周期
- AMS -> 应用进程:通过 ApplicationThread 回调到 ActivityThread
核心代码在 ActiveServices.java 里。AMS 收到请求后,会调用 startServiceLocked()。这里有个关键点:AMS 会先检查目标 Service 所在的进程是否已经启动。如果没有,它会先通过 ProcessRecord 拉起进程。
核心逻辑:AMS 维护了一个 ServiceMap,里面按用户 ID 和 Intent 的 Filter 做了索引。每次启动 Service 时,先查这个 Map,看有没有已经运行的实例。如果有,直接复用;没有就创建新的 ServiceRecord。
// 简化后的启动流程
// AMS 端
final int startServiceLocked(IApplicationThread caller, Intent service, ...) {
// 1. 解析 Intent,找到对应的 ServiceRecord
ServiceLookupResult res = retrieveServiceLocked(service, ...);
// 2. 检查调用者权限
if (res == null || res.record == null) {
return ServiceRecord.START_NOT_FOUND;
}
// 3. 真正启动
return startServiceInnerLocked(res.record, callerFg, ...);
}
private int startServiceInnerLocked(ServiceRecord r, ...) {
// 4. 把 ServiceRecord 加到待启动列表
r.pendingStarts.add(new ServiceRecord.StartItem(r, ...));
// 5. 调度启动
bringUpServiceLocked(r, ...);
return ServiceRecord.START_SUCCESS;
}
你看,AMS 其实不直接创建 Service 对象。它只是维护了一个 ServiceRecord,真正的实例化是在应用进程里完成的。AMS 通过 scheduleCreateService() 告诉 ActivityThread:“嘿,该创建 Service 了。”
13.2 绑定服务的内部机制
bindService() 比 startService() 复杂得多。为什么?因为涉及到了 Binder 对象的传递。你想想看,客户端要拿到 Service 的 onBind 返回的 IBinder,这中间要跨两次进程:
- 客户端 -> AMS:请求绑定
- AMS -> Service 进程:要求创建 Service 并调用 onBind
- Service 进程 -> AMS:返回 Binder 对象
- AMS -> 客户端:把 Binder 对象传回去
我在项目中遇到过一个问题:多个客户端同时绑定同一个 Service,AMS 是怎么处理的?答案是 引用计数。每个 ServiceRecord 里有一个 bindings 列表,记录了所有连接到这个 Service 的 ConnectionRecord。
个人经验:我曾经在做一个即时通讯 App 时,发现 Service 的 onBind 被调用了多次。排查后发现是因为每次绑定都传了不同的 Intent,而 AMS 认为这是不同的绑定请求。解决方案是在 Intent 里加上 BIND_AUTO_CREATE 标志,并且确保 Intent 的 filter 一致。
// AMS 处理绑定
final int bindServiceLocked(IApplicationThread caller, IBinder token,
Intent service, IServiceConnection connection, int flags) {
// 1. 查找或创建 ServiceRecord
ServiceLookupResult res = retrieveServiceLocked(service, ...);
// 2. 建立连接
AppBindRecord b = r.retrieveAppBindingLocked(service, callerApp);
ConnectionRecord c = new ConnectionRecord(b, ...);
r.connections.add(c);
b.connections.add(c);
// 3. 如果 Service 还没启动,先启动
if ((flags & Context.BIND_AUTO_CREATE) != 0) {
r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
bringUpServiceLocked(r, ...);
}
// 4. 如果 Service 已经运行,直接回调 onBind
if (r.app != null && r.app.thread != null) {
requestServiceBindLocked(r, ...);
}
}
13.3 生命周期调度的核心逻辑
Service 的生命周期回调,全部是由 AMS 通过 ApplicationThread 来触发的。具体来说:
| AMS 方法 | 回调到应用进程 | 触发时机 |
|---|---|---|
| scheduleCreateService | onCreate | Service 首次创建 |
| scheduleServiceArgs | onStartCommand | 每次 startService |
| requestServiceBind | onBind | 首次绑定时 |
| scheduleServiceStop | onDestroy | Service 被停止 |
这里有个细节:onStartCommand 的返回值会影响 AMS 对 Service 的调度策略。比如你返回 START_STICKY,AMS 会在 Service 被杀死后尝试重启它。返回 START_NOT_STICKY,AMS 就懒得管你了。
避坑指南:我曾经接手过一个项目,Service 总是莫名其妙地重启。查了半天发现是 onStartCommand 返回了 START_STICKY,而业务逻辑里又没有做幂等处理。每次重启都会重新请求网络,导致流量暴增。解决方案很简单:改成 START_NOT_STICKY,或者自己维护一个状态标记。
13.4 进程优先级与 Service 的关系
AMS 在管理 Service 时,会动态调整进程的优先级。这个优先级直接决定了你的 App 会不会被 LMK(Low Memory Killer)干掉。规则如下:
- 前台 Service(有通知栏):进程优先级为
PROCESS_STATE_FOREGROUND_SERVICE,几乎不会被杀 - 后台 Service:优先级为
PROCESS_STATE_SERVICE,内存紧张时会被回收 - 绑定 Service:如果绑定的客户端是前台进程,Service 进程也会被提升优先级
嗯,这里要注意:从 Android 8.0 开始,后台 Service 的限制越来越严格。你不能再随意在后台启动 Service 了,必须使用 startForegroundService() 并在 5 秒内显示通知。否则系统会直接抛异常。
13.5 核心流程图
下面这张图是我自己整理的,把 Service 从启动到销毁的完整流程画了出来。你看一眼就能明白 AMS 在整个过程中扮演的角色。
从这张图你能看到,AMS 其实就是一个调度中心。它不直接创建 Service 对象,而是通过 Binder 机制协调客户端和 Service 进程之间的通信。所有的生命周期回调,都是 AMS 通过 ApplicationThread 这个代理来触发的。
13.6 几个容易踩的坑
最后分享几个我在实际项目中遇到的 Service 相关问题:
- Service 泄漏:绑定 Service 后忘记解绑,导致 Activity 无法被 GC。我建议在
onDestroy里统一调用unbindService()。 - Intent 重复传递:如果 Service 正在运行,再次 startService 不会重新创建,但 onStartCommand 会被调用。注意处理重复的 Intent。
- 进程被杀后的恢复:如果 Service 返回了
START_STICKY,AMS 会在进程重启后重新传递最后一个 Intent。但这个 Intent 可能已经过期了,需要做空指针判断。
我的习惯:在写 Service 时,我一般会在 onStartCommand 里加一个 Intent 的空判断。因为 AMS 在重启 Service 时,可能传一个 null 的 Intent 进来。这个坑我踩过一次,后来就长记性了。
好了,Service 的管理机制就讲到这里。核心就是记住:AMS 是调度者,应用进程是执行者。所有的生命周期回调都是通过 Binder 跨进程完成的。理解了这一点,再看源码就会清晰很多。
公众号:蓝海资料掘金营,微信deep3321