13、Service 管理:AMS对Service的启动、绑定、生命周期调度

Service 这玩意儿,说白了就是 Android 里跑后台任务的组件。很多刚入行的朋友容易把它跟线程搞混——其实 Service 默认是跑在主线程的,你如果不在里面开子线程,照样会 ANR。我当年就踩过这个坑,写了个 Service 做网络请求,结果直接卡死,还被测试小姐姐追着骂了一整天。

今天咱们就深入 AMS 层面,看看 Service 到底是怎么被创建、绑定、销毁的。嗯,这部分代码量不小,但逻辑其实挺清晰的。

13.1 Service 的启动流程

你调用 startService() 的时候,背后发生了什么?我习惯把整个过程拆成三个阶段:

  1. 应用进程 -> AMS:通过 Binder 跨进程调用
  2. AMS 内部处理:检查权限、判断进程是否存在、调度生命周期
  3. 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 的完整管理流程 客户端进程 startService / bindService AMS (system_server) ActiveServices.java Service 进程 ActivityThread Binder ApplicationThread AMS 内部处理 1. 解析 Intent 2. 检查权限 3. 查找/创建 ServiceRecord 4. 判断进程是否存活 进程不存在 通过 ProcessRecord 拉起进程 等待 ActivityThread 就绪 进程已存在 直接回调生命周期 scheduleCreateService onCreate → onStartCommand / onBind

从这张图你能看到,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