Android-Framework(十七)-startService 与 bindService 的启动流程

文章内容

startService() 和 bindService() 表面上对应两套 API,进入 Framework 后却共享了大半条启动链。两者都从调用方进程进入 ContextImpl,经 Binder 到达 system_server 中的 ActivityManagerService(下文简称 AMS),由 ActiveServices 查找或创建 ServiceRecord,再把生命周期调用投递给目标进程的 ActivityThread。

真正的分岔发生在 AMS 如何记录请求,以及目标进程收到哪类事务:
启动请求维护 pendingStarts,最终触发 onStartCommand();
绑定请求还要登记 ConnectionRecord,最终触发 onBind(),并把服务端 Binder 沿回调链送回调用方。

Service ANR 也不取决于 API 名称。ActiveServices 在生命周期调用发出前登记执行状态并投递延迟消息,只有相应的 serviceDoneExecuting() 及时返回,延迟消息才会被移除。

为避免把“调用 API”和“服务对象已经创建”混为一谈,先记住四个参与者:

所在位置 关键对象 职责
调用方进程 MainActivity、ContextImpl、LoadedApk.ServiceDispatcher 发起请求,包装 ServiceConnection,接收绑定回调
system_server AMS、ActiveServices、ServiceMap、ServiceRecord、ConnectionRecord 校验、解析、缓存、调度进程与生命周期、监控超时
目标 Service 进程 ActivityThread、Service 实例 创建 Service,调用 onCreate()、onStartCommand()、onBind()、onDestroy()
PMS 接口 resolveService() 把 Intent 解析成 ServiceInfo;调用发生在 system_server 内的服务管理流程中

一、从 Activity 里那一行调用说起:Context 只是代理

1.1 mBase 与 ContextImpl

Activity 继承自 ContextThemeWrapper,对外暴露的是 Context API;承担具体工作的是作为基础 Context 的 ContextImpl。因此,应用代码里的 startService(intent) 不会在 Activity 内直接创建 MessageService。它只是进入 ContextImpl,由后者整理调用方身份、Intent 类型和 userId,再请求 AMS。

下面的代码只保留与调用链有关的部分。它占住调用方主线程时也可能触发 ANR,但触发源不等同于 Service 执行超时。

java
// 代码位置 MainActivity.java 第 1—25 行// 声明演示页面类public class MainActivity extends Activity {// 保存后续停止服务所需的 Intent    private Intent intent;// 定义接收绑定结果的回调    private final ServiceConnection serviceConn = new ServiceConnection() {// 处理服务连接成功        @Override public void onServiceConnected(ComponentName name, IBinder service) {// 把 Binder 转成业务接口            UserAidl api = UserAidl.Stub.asInterface(service);// 结束连接成功回调        }// 处理服务进程断开        @Override public void onServiceDisconnected(ComponentName name) {// 结束断开回调        }// 结束匿名对象    };// 进入页面初始化    @Override protected void onCreate(Bundle savedInstanceState) {// 调用父类初始化        super.onCreate(savedInstanceState);// 创建指向 MessageService 的显式 Intent        intent = new Intent(this, MessageService.class);// 发起启动请求        startService(intent);// 发起自动创建式绑定        bindService(intent, serviceConn, Context.BIND_AUTO_CREATE);// 用长时间阻塞复现主线程超时        Thread.sleep(30500);// 结束初始化    }// 响应停止按钮    public void testClick(View view) {// 请求停止启动态 Service        stopService(intent);// 结束点击方法    }// 结束页面类}

Thread.sleep(1000) 时,启动场景的日志顺序为 onCreate → onStartCommand,不会调用 onBind;绑定场景调用 onCreate 与 onBind。这组差异对应 Framework 投递的消息类型,不能用“都是启动 Service”一笔带过。

startService 与 bindService 共享三段跨进程主链
图 1 startService 与 bindService 共享三段跨进程主链,并在参数记录和生命周期投递处发生分岔。

1.2 startServiceCommon 交给 AMS 的五个参数

ContextImpl.startService() 很薄,主体位于 startServiceCommon()。它先检查 Intent,再调用 prepareToLeaveProcess(),随后通过 ActivityManagerNative.getDefault() 进入 AMS。传入的核心信息包括调用方 IApplicationThread、Service Intent、解析后的 MIME type、调用包名和 userId。

java
// 代码位置 core/java/android/app/ContextImpl.java 第 1236—1242 行// 定义 startService 的公共实现private ComponentName startServiceCommon(Intent service, UserHandle user) {// 捕获跨进程异常    try {// 校验 Service Intent        validateServiceIntent(service);// 把 Intent 整理成可离开当前进程的状态        service.prepareToLeaveProcess();// 获取 AMS 代理并发起启动请求        ComponentName cn = ActivityManagerNative.getDefault().startService(// 传递应用线程、Intent 和解析类型            mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(// 传递包名与用户标识                        getContentResolver()), getOpPackageName(), user.getIdentifier());

AMS 可能用特殊 ComponentName 传回失败原因。包名为 "!" 表示权限不足,"!!" 表示无法启动;ContextImpl 把它们还原成 SecurityException。这也解释了返回类型为何是 ComponentName:正常时它标识实际组件,异常时还能承载受控的错误协议。

1.3 跨进程:一跳为什么是 ActivityManagerNative.getDefault()

在 Android 6.0 中,ActivityManagerNative.getDefault() 返回 AMS 的 Binder 接口代理。调用方看到的是 Java 方法,参数经过 Binder 驱动送到 system_server,再进入 AMS 的同名入口。入口拒绝 isolated caller 和携带文件描述符的 Intent,并在 AMS 锁内把请求交给 ActiveServices。

java
// 代码位置 services/core/java/com/android/server/am/ActivityManagerService.java 第 15661—15668 行// 定义 AMS 的 Binder 服务端入口public ComponentName startService(IApplicationThread caller, Intent service,// 接收解析类型、调用包和用户标识        String resolvedType, String callingPackage, int userId)// 声明大事务异常        throws TransactionTooLargeException {// 禁止隔离进程调用    enforceNotIsolatedCaller("startService");// 拒绝携带文件描述符的 Intent    if (service != null && service.hasFileDescriptors() == true) {// 抛出非法参数异常        throw new IllegalArgumentException("File descriptors passed in Intent");// 结束文件描述符检查    }
java
// 代码位置 services/core/java/com/android/server/am/ActivityManagerService.java 第 15676—15684 行// 在 AMS 全局锁内修改服务状态synchronized(this) {// 取得 Binder 调用方进程号    final int callingPid = Binder.getCallingPid();// 取得 Binder 调用方用户号    final int callingUid = Binder.getCallingUid();// 清除调用身份以执行系统侧工作    final long origId = Binder.clearCallingIdentity();// 把启动请求交给 ActiveServices    ComponentName res = mServices.startServiceLocked(caller, service,// 传入类型、身份、包名和用户            resolvedType, callingPid, callingUid, callingPackage, userId);// 恢复原 Binder 身份    Binder.restoreCallingIdentity(origId);// 返回组件或错误哨兵    return res;// 退出 AMS 锁}

这里的“跨进程”只是调用路径的一段。PMS 的解析、目标进程的创建以及回调到应用,都有各自的身份和调度边界。分析 Service 问题时,应同时记录当前方法属于哪个进程、持有什么锁、下一跳是本地调用还是 Binder 调用。

二、ServiceRecord 从哪来:Intent 到 PMS 的解析与缓存

2.1 AMS.startService 的三道检查

AMS 入口的检查可以按职责分成三层:

  1. 调用者不能是 isolated process;
  2. Intent 不能夹带文件描述符;
  3. callingPackage 不能为空。
    进入 ActiveServices.startServiceLocked() 后,还会取得调用方进程记录、判断调用方是否前台,并调用 retrieveServiceLocked()。因此 ServiceRecord 不是应用传入的对象,也不是 Service 实例,它是 system_server 中保存组件解析结果和运行状态的账本。

2.2 userId 到 ServiceMap:按名字查、再按 Intent 查

retrieveServiceLocked() 先通过 handleIncomingUser() 规范 userId,然后取得对应用户的 ServiceMap。
显式 Intent 可按 ComponentName 查询 mServicesByName;若未命中,再用 Intent.FilterComparison 查 mServicesByIntent。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1032—1043 行// 规范跨用户调用的 userIduserId = mAm.handleIncomingUser(callingPid, callingUid, userId,// 传入用户处理策略        false, ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE, "service", null);// 取得该用户的 ServiceMapServiceMap smap = getServiceMap(userId);// 读取显式组件名final ComponentName comp = service.getComponent();// 判断 Intent 是否带组件名if (comp != null) {// 优先按组件名查找    r = smap.mServicesByName.get(comp);// 结束显式查询}// 名字查询未命中时继续if (r == null) {// 构造用于过滤比较的 Intent 包装    Intent.FilterComparison filter = new Intent.FilterComparison(service);// 再按 Intent 过滤条件查找    r = smap.mServicesByIntent.get(filter);// 结束 Intent 查询}

两张表指向同一个 ServiceRecord,索引目的不同。mServicesByName 适合组件身份明确的访问;mServicesByIntent 让同一过滤语义的 Intent 复用记录。这里所谓“缓存”是 system_server 内的运行态索引,不能等同于磁盘缓存,也不能推导出 Service Java 对象会永久存在。

retrieveServiceLocked 的查找与创建
图 2 retrieveServiceLocked 先查两张运行态索引,均未命中时才请求 PMS 解析并创建 ServiceRecord。

2.3 只有查不到才 new ServiceRecord,并回写两张表

两次查询都失败后,代码调用 PackageManager 的 resolveService(),得到 ResolveInfo 和 ServiceInfo。若组件确实存在,方法再次按解析出的规范组件名查表;只有仍未命中且 createIfNeeded 为真,才创建记录并写回两张索引。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1044—1055 行// 两张表都未命中时进入解析if (r == null) {// 捕获包管理服务的远程异常    try {// 声明解析结果        ResolveInfo rInfo =// 请求 PMS 解析 Service            AppGlobals.getPackageManager().resolveService(// 传入 Intent、类型、标志和用户                        service, resolvedType,// 使用 AMS 的标准包管理查询标志                        ActivityManagerService.STOCK_PM_FLAGS, userId);// 提取 ServiceInfo        ServiceInfo sInfo =// 对空解析结果做保护            rInfo != null ? rInfo.serviceInfo : null;// 判断组件是否存在        if (sInfo == null) {// 记录找不到 Service            Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId +// 补全日志文本                  ": not found");// 返回未找到            return null;// 结束空结果分支        }
java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1069—1084 行// 用解析后的规范组件名再次查表r = smap.mServicesByName.get(name);// 记录不存在且允许创建时建账if (r == null && createIfNeeded) {// 声明 Intent 比较键    Intent.FilterComparison filter// 克隆过滤相关字段,避免外部修改            = new Intent.FilterComparison(service.cloneFilter());// 创建重启辅助对象    ServiceRestarter res = new ServiceRestarter();// 初始化电量统计句柄    BatteryStatsImpl.Uid.Pkg.Serv ss = null;// 取得活动统计对象    BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics();// 在统计对象锁内取 Service 统计    synchronized (stats) {// 查询该组件的服务统计        ss = stats.getServiceStatsLocked(// 传入 uid、包名和类名                sInfo.applicationInfo.uid, sInfo.packageName,// 传入 Service 名称                sInfo.name);// 退出统计锁    }// 创建 system_server 侧的 ServiceRecord    r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res);// 让重启器关联该记录    res.setService(r);// 写入组件名索引    smap.mServicesByName.put(name, r);// 写入 Intent 索引    smap.mServicesByIntent.put(filter, r);

第二次启动同一 Service 时,如果记录仍在 ServiceMap,查询会直接命中,PMS 解析和 new ServiceRecord 都不会重复执行。但后续仍可增加新的 StartItem,onStartCommand() 也可能再次收到调用。ServiceRecord 复用不代表生命周期回调只发生一次。

这里有两个容易混起来的对象。ServiceInfo 由 PackageManager 解析 AndroidManifest.xml 得到,描述组件的静态声明:包名、类名、exported、permission 这些字段随 APK 安装而定,不随进程运行状态变化。ServiceRecord 是 system_server 在首次解析成功后创建的运行态记录,ServiceRecord.java 第 71 行把它持有的字段声明为 final ServiceInfo serviceInfo,也就是引用一份 ServiceInfo,同时还带 app、startRequested、pendingStarts、connections、executeNesting 这些只在 AMS 侧才有意义的字段。resolveService() 每次都可能返回新的 ServiceInfo 对象,但只要 mServicesByName 或 mServicesByIntent 命中,复用的就是同一个 ServiceRecord。把 ServiceRecord 理解成“更详细的 ServiceInfo”会错,它俩是静态声明与运行态记录的关系。

2.4 权限与导出:两种 ServiceLookupResult

解析完成后还要检查调用者是否有权访问组件。未导出的 Service 会返回带原因文本、但 record 为空的 ServiceLookupResult;声明了权限而调用者缺少权限时,也返回失败结果,并记录所需权限。上层据此区分“组件不存在”和“组件存在但不可访问”,最终形成不同的异常或返回值。retrieveServiceLocked() 末尾这段判断就是把两种失败分开的位置。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1099—1115 行// 解析成功后继续检查访问权限if (r != null) {// 调用 AMS 的组件权限检查    if (mAm.checkComponentPermission(r.permission,// 传入调用方 pid、uid,以及组件 uid 与是否导出            callingPid, callingUid, r.appInfo.uid, r.exported)// 权限检查未通过时进入失败分支            != PackageManager.PERMISSION_GRANTED) {// 先判断组件是否未导出        if (!r.exported) {// 记录未导出访问被拒绝            Slog.w(TAG, "Permission Denial: Accessing service " + r.name// 附加调用方 pid                    + " from pid=" + callingPid// 附加调用方 uid                    + ", uid=" + callingUid// 附加目标组件 uid                    + " that is not exported from uid " + r.appInfo.uid);// 返回 record 为空、reason 为未导出说明的结果            return new ServiceLookupResult(null, "not exported from uid "// 拼接 reason 文本                    + r.appInfo.uid);// 结束未导出分支        }// 记录已导出但权限不足        Slog.w(TAG, "Permission Denial: Accessing service " + r.name// 附加调用方 pid                + " from pid=" + callingPid// 附加调用方 uid                + ", uid=" + callingUid// 附加所需权限名                + " requires " + r.permission);// 返回 record 为空、reason 为所需权限名的结果        return new ServiceLookupResult(null, r.permission);// 结束权限检查失败分支    }

安全判断发生在 system_server,不能靠应用进程里的显式 Intent 绕过。即使调用方知道完整类名,exported 与组件权限仍然决定跨应用访问能否继续。

三、把 onCreate 和 onStartCommand 投递到目标进程

3.1 进程已在跑:直接 realStartServiceLocked

bringUpServiceLocked() 的分支先看目标进程是否已经有可用的 ApplicationThread。若 Service 本身已经关联到运行中的 app,方法可以直接发送新的启动参数;若只有进程存在,则取得 ProcessRecord 后调用 realStartServiceLocked()。这两种“已在跑”含义不同:前者可能不需要重建 Service,后者仍需投递创建事务。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1415—1427 行// 判断 Service 是否要求隔离进程final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;// 读取目标进程名final String procName = r.processName;// 声明目标进程记录ProcessRecord app;// 普通进程走现有进程查询if (!isolated) {// 按进程名和 uid 查 ProcessRecord    app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);// 判断进程及 Binder 线程接口是否可用    if (app != null && app.thread != null) {// 捕获投递异常        try {// 把包关联到进程统计            app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);// 进入真实启动阶段            realStartServiceLocked(r, app, execInFg);// 成功后结束 bring-up            return null;

3.2 进程没起来:startProcessLocked 与 mPendingServices

没有可用进程时,AMS 调用 startProcessLocked()。此时不能立即执行 Service 生命周期,因为 ActivityThread 尚未通过 Binder 附着到 AMS。ServiceRecord 会进入 mPendingServices,等待进程启动完成后的回环。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1449—1467 行// 没有目标进程时进入创建流程if (app == null) {// 请求 AMS 启动目标进程    if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,// 标记进程因 Service 而启动            "service", r.name, false, isolated, false)) == null) {// 组织进程启动失败信息        String msg = "Unable to launch app "// 拼接包名                + r.appInfo.packageName + "/"// 拼接 uid                + r.appInfo.uid + " for service "// 拼接原始 Intent                + r.intent.getIntent() + ": process is bad";// 记录失败        Slog.w(TAG, msg);// 清理 Service 状态        bringDownServiceLocked(r);// 返回失败原因        return msg;// 结束启动失败分支    }// 隔离 Service 单独保存进程记录    if (isolated) {// 关联隔离进程        r.isolatedProc = app;// 结束隔离分支    }// 结束无进程分支}// 避免重复加入等待队列if (!mPendingServices.contains(r)) {// 等待进程附着后继续    mPendingServices.add(r);// 结束等待队列判断}

bringUpServiceLocked 的分支与进程晚到的回环
图 3 bringUpServiceLocked 在目标进程缺席时挂起 ServiceRecord,并在进程附着后回到 realStartServiceLocked。

3.3 realStartServiceLocked 与 scheduleCreateService

进入 realStartServiceLocked() 后,ServiceRecord 与 ProcessRecord 建立关联,Service 被加入进程的 services 集合。紧接着调用 bumpServiceExecutingLocked(..., "create") 登记执行状态,再通过目标进程的 IApplicationThread 调用 scheduleCreateService()。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1500—1506 行// 让 ServiceRecord 关联目标进程r.app = app;// 记录重启和活动时间r.restartTime = r.lastActivity = SystemClock.uptimeMillis();// 把 Service 加入进程集合final boolean newService = app.services.add(r);// 标记 create 生命周期正在执行bumpServiceExecutingLocked(r, execInFg, "create");// 更新进程 LRUmAm.updateLruProcessLocked(app, false, null);// 重算 OOM 调整值mAm.updateOomAdjLocked();
java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1520—1524 行// 确保 Service 包完成 dex 优化mAm.ensurePackageDexOpt(r.serviceInfo.packageName);// 把进程状态至少提升到 Service 级别app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);// 跨进程投递创建 Service 请求app.thread.scheduleCreateService(r, r.serviceInfo,// 附带兼容信息        mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),// 附带进程状态        app.repProcState);

scheduleCreateService() 这个名字里的 schedule 很重要:AMS 发出 Binder 调用,不等于目标进程已完成 onCreate()。目标进程的 ApplicationThread 把数据转成消息,ActivityThread 主线程再处理它。ANR 监控覆盖的就是这段“已经投递、尚未完成”的时间窗。

3.4 handleCreateService:反射、ContextImpl、attach、onCreate

目标进程收到创建消息后,handleCreateService() 取得 LoadedApk 和类加载器,反射构造 Service。随后创建该组件使用的 ContextImpl,构造或取得 Application,调用 Service.attach() 注入运行环境,才进入开发者熟悉的 onCreate()。

java
// 代码位置 core/java/android/app/ActivityThread.java 第 2854—2860 行// 取得目标包的 LoadedApkLoadedApk packageInfo = getPackageInfoNoCheck(// 使用应用信息与兼容配置        data.info.applicationInfo, data.compatInfo);// 初始化 Service 引用Service service = null;// 捕获反射创建异常try {// 取得包的类加载器    java.lang.ClassLoader cl = packageInfo.getClassLoader();// 按 Service 类名反射实例化    service = (Service) cl.loadClass(data.info.name).newInstance();// 进入异常分支} catch (Exception e) {
java
// 代码位置 core/java/android/app/ActivityThread.java 第 2871—2881 行// 创建组件使用的 ContextImplContextImpl context = ContextImpl.createAppContext(this, packageInfo);// 把外层 Context 指向 Servicecontext.setOuterContext(service);// 取得或创建 ApplicationApplication app = packageInfo.makeApplication(false, mInstrumentation);// 向 Service 注入上下文、线程、token 和 AMS 接口service.attach(context, this, data.info.name, data.token, app,// 取得 AMS Binder 接口        ActivityManagerNative.getDefault());// 调用 Service.onCreateservice.onCreate();// 按 token 保存 Service 实例mServices.put(data.token, service);// 捕获完成回报的远程异常try {// 告诉 AMS 本次匿名生命周期执行完成    ActivityManagerNative.getDefault().serviceDoneExecuting(// 传回 token 与执行类型            data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);

这里能看到 Service Java 对象属于目标进程,ServiceRecord 属于 system_server。两者靠 token 对应。把 ServiceRecord 称为 Service 实例,会导致后续对停止、重启和进程死亡的理解全部错位。

3.5 sendServiceArgsLocked 与 handleServiceArgs

创建完成只是第一段。startServiceLocked() 已把一个 StartItem 加入 pendingStarts,realStartServiceLocked() 后续调用 sendServiceArgsLocked(),再用 scheduleServiceArgs() 把参数送到目标进程。ActivityThread.handleServiceArgs() 最终执行 onStartCommand(data.args, data.flags, data.startId),并把返回值随 SERVICE_DONE_EXECUTING_START 回报 AMS。

pendingStarts 和 3.2 节的 mPendingServices 只差一个字,层级完全不同。mPendingServices(ActiveServices.java 第 138 行)挂在 ActiveServices 上,装的是等待目标进程创建完成的 ServiceRecord,进程起来之后记录就会被移出。pendingStarts(ServiceRecord.java 第 173 行)挂在每个 ServiceRecord 自己身上,装的是尚未投递给目标进程的 ServiceRecord.StartItem(类定义在第 120 行),每次 startServiceLocked() 都会追加一个新的 StartItem,sendServiceArgsLocked() 再把队首的 StartItem 经 scheduleServiceArgs() 送到目标进程,转成一次 onStartCommand() 调用。同一个 ServiceRecord 在进程已经起来、不再出现在 mPendingServices 之后,仍然会不断有新的 StartItem 进出 pendingStarts。

java
// 代码位置 core/java/android/app/ActivityThread.java 第 3008—3024 行// 声明生命周期返回值int res;// 普通启动参数进入 onStartCommandif (!data.taskRemoved) {// 调用 Service 并保存返回值    res = s.onStartCommand(data.args, data.flags, data.startId);// 任务移除事件走另一条回调} else {// 调用 onTaskRemoved    s.onTaskRemoved(data.args);// 使用任务移除完成标志    res = Service.START_TASK_REMOVED_COMPLETE;// 结束任务类型分支}// 等待排队工作完成QueuedWork.waitToFinish();// 捕获向 AMS 回报的远程异常try {// 回报 start 生命周期已经完成    ActivityManagerNative.getDefault().serviceDoneExecuting(// 携带 token、startId 与 onStartCommand 返回值            data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);// 捕获 Binder 异常} catch (RemoteException e) {// AOSP 此处不做额外处理    // nothing to do.// 结束远程异常分支}// 启用即时编译支持ensureJitEnabled();

录屏登记的 Thread.sleep(1000) 实验出现 onCreate → onStartCommand,与这两段投递顺序一致。若同一个 Service 已经创建,后续 startService() 可以只增加新的启动参数并再次进入 onStartCommand(),无需再次调用 onCreate()。

四、ANR 是启动链上那颗没拆掉的延迟消息

4.1 埋下监控:bumpServiceExecutingLocked 与 scheduleServiceTimeoutLocked

在 scheduleCreateService()、scheduleServiceArgs() 和 scheduleBindService() 之前,ActiveServices 都会调用 bumpServiceExecutingLocked()。它增加 executeNesting,记录 executingStart,并把 Service 放进所属进程的 executingServices。当集合从空变成一个元素时,系统安排超时消息。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1142—1154 行// 读取当前运行时间long now = SystemClock.uptimeMillis();// 只在最外层执行开始时登记进程状态if (r.executeNesting == 0) {// 记录本次执行是否按前台标准计时    r.executeFg = fg;// 取得进程统计中的 Service 状态    ProcessStats.ServiceState stracker = r.getTracker();// 判断统计对象是否存在    if (stracker != null) {// 标记 Service 正在执行        stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now);// 结束统计分支    }// 判断 Service 是否已经关联进程    if (r.app != null) {// 加入进程的执行中集合        r.app.executingServices.add(r);// 汇总进程内是否存在前台执行        r.app.execServicesFg |= fg;// 第一个执行项负责安排超时        if (r.app.executingServices.size() == 1) {// 投递 Service 超时消息            scheduleServiceTimeoutLocked(r.app);// 结束首次执行判断        }// 结束进程存在分支    }

这颗消息属于进程级监控。一个进程可以同时关联多个 Service,AMS 通过 executingServices 和各记录的 executingStart 找到真正超时的对象。它并非给每次 Java 回调各创建一个互不相关的定时器。

ANR 的埋弹与拆弹
图 4 生命周期投递前安排 SERVICE_TIMEOUT_MSG,完成回报清空执行集合后移除该消息。

4.2 20 秒与 200 秒:execServicesFg 决定时限

AOSP 6.0 定义前台 Service 执行超时为 20 秒,后台为其十倍,即 200 秒。这里的前台/后台来自调度时传入的 execInFg,汇总到 ProcessRecord.execServicesFg。它不等同于后来版本里调用 startForegroundService() 后显示通知的“前台服务”概念。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 87—90 行// 定义前台执行超时为二十秒static final int SERVICE_TIMEOUT = 20*1000;// 定义后台执行超时为前台的十倍static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 2536—2546 行// 定义 Service 超时消息调度void scheduleServiceTimeoutLocked(ProcessRecord proc) {// 没有执行中 Service 或线程未附着时退出    if (proc.executingServices.size() == 0 || proc.thread == null) {// 不安排无效消息        return;// 结束早退条件    }// 读取当前时间    long now = SystemClock.uptimeMillis();// 创建 Service 超时消息    Message msg = mAm.mHandler.obtainMessage(// 使用统一的消息编号            ActivityManagerService.SERVICE_TIMEOUT_MSG);// 把目标进程放入消息    msg.obj = proc;// 按进程执行属性选择触发时间    mAm.mHandler.sendMessageAtTime(msg,// 前台二十秒,后台二百秒            proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));// 结束调度方法}

录屏登记的 MessageService.onCreate() 内 Thread.sleep(25000) 会触发 ANR,与 20 秒窗口相符。这个观察只说明当时路径按前台执行标准计时;若把同样的耗时放到未被该监控覆盖的线程或改走不同进程状态,结果不能直接类推。

4.3 完成回报:serviceDoneExecuting 移除同一个消息

目标进程完成 onCreate()、onStartCommand()、onBind() 或停止流程后,会通过不同分支回报 serviceDoneExecuting()。AMS 最终进入 ActiveServices.serviceDoneExecutingLocked(),减少嵌套计数。计数归零后,记录从 executingServices 移除;当进程内不再有执行中的 Service,Handler 删除以该 ProcessRecord 为 obj 的 SERVICE_TIMEOUT_MSG。

回报时携带的 type 有三个取值,定义在 ActivityThread.java 第 170、172、174 行:SERVICE_DONE_EXECUTING_ANON = 0、SERVICE_DONE_EXECUTING_START = 1、SERVICE_DONE_EXECUTING_STOP = 2。划分依据是 AMS 侧要不要按 type 做额外记账。START 对应 handleServiceArgs() 回报 onStartCommand() 的返回值,AMS 侧进入 switch (res) 更新 stopIfKilled 等 StartItem 状态;STOP 对应 handleStopService() 回报 onDestroy() 已完成,AMS 侧校验该记录是否在 mDestroyingServices 中、executeNesting 是否等于 1;ANON 用在 onCreate()、onBind() 的非 rebind 分支和 onRebind() 这些不需要额外记账的位置。三个 type 共享的动作是减 executeNesting 和执行集合清理,只有 START 与 STOP 会触发各自的额外状态更新。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1980—1990 行// 完成一层 Service 执行r.executeNesting--;// 嵌套计数归零时清理执行状态if (r.executeNesting <= 0) {// 确认记录仍关联进程    if (r.app != null) {// 清除前台执行汇总        r.app.execServicesFg = false;// 从执行中集合移除 Service        r.app.executingServices.remove(r);// 进程内没有其他执行项时撤销超时消息        if (r.app.executingServices.size() == 0) {// 从 AMS Handler 删除与该进程关联的超时消息            mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);

所谓“拆弹”应理解为状态机完成闭环:目标进程回报、嵌套计数下降、执行集合清空、延迟消息删除。任一环节没有按时发生,消息都可能到期。只盯着 Thread.sleep() 会漏掉 Binder 阻塞、锁等待和主线程消息拥堵等同样能延迟完成回报的因素。

4.4 没有及时完成:serviceTimeout 到 appNotResponding

SERVICE_TIMEOUT_MSG 的编号是 12。AMS 的 MainHandler 收到后调用 mServices.serviceTimeout((ProcessRecord) msg.obj)。

java
// 代码位置 services/core/java/com/android/server/am/ActivityManagerService.java 第 1317 行// 定义 Service 执行超时消息的编号static final int SERVICE_TIMEOUT_MSG = 12;
java
// 代码位置 services/core/java/com/android/server/am/ActivityManagerService.java 第 1615—1623 行// 处理 Service 执行超时消息case SERVICE_TIMEOUT_MSG: {// 命中过一次 dex 优化时先顺延一轮    if (mDidDexOpt) {// 清除 dex 优化标记        mDidDexOpt = false;// 重新构造一条同类型消息        Message nmsg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG);// 沿用原消息携带的进程对象        nmsg.obj = msg.obj;// 按标准超时时长重新投递        mHandler.sendMessageDelayed(nmsg, ActiveServices.SERVICE_TIMEOUT);// 本轮先返回,不继续处理        return;// 结束顺延分支    }// 真正进入 ActiveServices 的超时判定    mServices.serviceTimeout((ProcessRecord)msg.obj);// 结束该 case} break;

serviceTimeout() 计算 maxTime,遍历执行中 Service,以 executingStart 判断谁越过时限。确认目标进程仍在 LRU 列表后,它保存诊断文本,并形成 executing service <shortName> 的 ANR 原因,最终交给 AMS 的 appNotResponding() 流程抓取 traces 和展示 ANR。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 2496—2511 行// 读取当前运行时间final long now = SystemClock.uptimeMillis();// 依据进程执行属性计算最早允许时间final long maxTime =  now -// 选择前台或后台超时值        (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);// 初始化超时 ServiceServiceRecord timeout = null;// 初始化下一次检查时间long nextTime = 0;// 逆序遍历执行中 Servicefor (int i=proc.executingServices.size()-1; i>=0; i--) {// 取得一条执行记录    ServiceRecord sr = proc.executingServices.valueAt(i);// 判断开始时间是否早于阈值    if (sr.executingStart < maxTime) {// 记下超时对象        timeout = sr;// 找到后退出遍历        break;// 结束超时判断    }// 更新尚未超时记录中的较晚时间    if (sr.executingStart > nextTime) {// 保存下一次计算依据        nextTime = sr.executingStart;// 结束时间更新    }// 结束遍历}// 只处理仍属于活动进程列表的超时if (timeout != null && mAm.mLruProcesses.contains(proc)) {

appNotResponding() 的入口接收进程、关联 Activity、是否强制置顶以及原因文本;本次调用来自 serviceTimeout(),只用到进程与原因文本两个参数。

java
// 代码位置 services/core/java/com/android/server/am/ActivityManagerService.java 第 4890—4906 行// 定义 ANR 处理入口final void appNotResponding(ProcessRecord app, ActivityRecord activity,// 接收父 ActivityRecord、是否强制置顶与 ANR 说明文本        ActivityRecord parent, boolean aboveSystem, final String annotation) {// 准备收集主进程附近的 pid 列表    ArrayList<Integer> firstPids = new ArrayList<Integer>(5);// 准备收集次要相关 pid 列表    SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);// 存在外部 ActivityController 时先询问处理意见    if (mController != null) {// 捕获远程调用异常        try {// 询问是否继续或立即杀掉进程            int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation);// 返回负值且不是本进程时执行终止            if (res < 0 && app.pid != MY_PID) {// 以 anr 原因终止目标进程                app.kill("anr", true);// 结束终止分支            }// 捕获 Controller 失效的情况        } catch (RemoteException e) {// 清空失效的 Controller 引用            mController = null;// 通知 Watchdog 清除 ActivityController            Watchdog.getInstance().setActivityController(null);// 结束异常处理        }// 结束 Controller 判断    }// 记录本次 ANR 发生时间    long anrTime = SystemClock.uptimeMillis();

appNotResponding() 后续如何生成弹窗、如何抓取 traces,本文素材没有覆盖到,第 8.2 节把它登记为待核实项。

MainActivity.onCreate() 中阻塞 30.5 秒也会出现 ANR,但那条路径通常由输入分发或 Activity 生命周期监控发现。现象同为 ANR,不表示底层一定经过 ActiveServices.serviceTimeout()。判断来源要结合 ANR reason、traces 和 AMS 日志。

4.5 onStartCommand 返回值如何影响 stopIfKilled

handleServiceArgs() 把 onStartCommand() 的返回值送回 AMS。serviceDoneExecutingLocked(r, type, startId, res) 在 SERVICE_DONE_EXECUTING_START 分支中处理它。三种常用返回值对 stopIfKilled 的影响如下。

返回值 serviceDoneExecutingLocked 中的处理 stopIfKilled 进程被杀后的含义
START_NOT_STICKY findDeliveredStart(startId, true) 移除已交付项;仅当这是最后一个 startId 时设置 true 没有新的显式启动请求时,不要求系统重建该启动态服务
START_STICKY 移除已交付项 false 保留“被杀后仍需启动”的语义;重建时 Intent 可能为 null
START_REDELIVER_INTENT findDeliveredStart(startId, false) 保留 StartItem,清零 deliveryCount 并增加完成次数 true 保留待重新投递的 Intent,由记录本身驱动 redelivery;不能只凭 stopIfKilled 单字段理解

源码中 START_REDELIVER_INTENT 也把 stopIfKilled 设为 true,容易与 API 文档的“重新投递 Intent”产生表面冲突。关键在于对应的 StartItem 没有被移除。重启决策读取的是一组状态,不是单看这个布尔值。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1892—1909 行// 按 onStartCommand 返回值分支switch (res) {// 兼容模式与 START_STICKY 共用处理    case Service.START_STICKY_COMPATIBILITY:// 进入 START_STICKY 分支    case Service.START_STICKY: {// 移除已交付的启动项        r.findDeliveredStart(startId, true);// 被杀时仍保留启动需求        r.stopIfKilled = false;// 结束分支        break;// 结束 sticky 块    }// 进入 START_NOT_STICKY 分支    case Service.START_NOT_STICKY: {// 移除已交付的启动项        r.findDeliveredStart(startId, true);// 仅对最后一次启动设置停止标记        if (r.getLastStartId() == startId) {// 允许被杀后停止            r.stopIfKilled = true;// 结束最后一次启动判断        }// 结束分支        break;// 结束 not-sticky 块    }
java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1911—1922 行// 进入重新投递 Intent 分支case Service.START_REDELIVER_INTENT: {// 查找但不移除已交付启动项    ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);// 确认启动项存在    if (si != null) {// 清零本轮投递次数        si.deliveryCount = 0;// 增加完成执行次数        si.doneExecutingCount++;// 允许结合保留的启动项决定后续行为        r.stopIfKilled = true;// 结束启动项判断    }// 结束分支    break;// 结束 redeliver 块}

五、bindService

5.1 不能跨进程的 ServiceConnection

·
应用传给 bindService() 的 ServiceConnection 是普通 Java 接口对象。AMS 位于另一个进程,无法持有并直接调用这份对象引用。Framework 要把它包装成实现 IServiceConnection Binder 接口的对象,跨进程传递的是这个 Binder 端点。

ContextImpl.bindServiceCommon() 先调用 LoadedApk.getServiceDispatcher(),得到 IServiceConnection sd,随后才向 AMS 发起 bindService。这一步还带来另一个效果:Framework 保存原始 ServiceConnection 与代理的对应关系,便于去重、解绑和泄漏检查。

5.2 getServiceDispatcher 与 ServiceDispatcher/InnerConnection

LoadedApk 以 Context 为第一层键,以应用传入的 ServiceConnection 为第二层键保存 ServiceDispatcher。已有记录会复用并校验 Context 与 Handler;没有记录时创建新的包装器。

java
// 代码位置 core/java/android/app/LoadedApk.java 第 977—997 行// 定义 ServiceConnection 包装入口public final IServiceConnection getServiceDispatcher(ServiceConnection c,// 接收 Context、Handler 和绑定标志        Context context, Handler handler, int flags) {// 在连接映射锁内操作    synchronized (mServices) {// 初始化包装器        LoadedApk.ServiceDispatcher sd = null;// 按 Context 取得连接表        ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context);// 连接表存在时尝试复用        if (map != null) {// 按原始 ServiceConnection 查询            sd = map.get(c);// 结束查询        }// 没有包装器时创建        if (sd == null) {// 创建 ServiceDispatcher            sd = new ServiceDispatcher(c, context, handler, flags);// Context 首次绑定时创建二级表            if (map == null) {// 分配连接映射                map = new ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>();// 保存 Context 对应的映射                mServices.put(context, map);// 结束新表分支            }// 保存原对象到包装器的映射            map.put(c, sd);// 已存在时校验调用环境        } else {// 检查 Context 和 Handler 是否一致            sd.validate(context, handler);// 结束复用分支        }// 返回可跨进程的 IServiceConnection        return sd.getIServiceConnection();// 退出连接映射锁    }// 结束方法}

ServiceDispatcher 内部的 InnerConnection 继承 IServiceConnection.Stub,具备 Binder 服务端能力。它通过 WeakReference 指回 ServiceDispatcher,收到 connected() 后转交给包装器。

java
// 代码位置 core/java/android/app/LoadedApk.java 第 1067—1080 行// 定义 Binder 回调端点private static class InnerConnection extends IServiceConnection.Stub {// 弱引用外层 ServiceDispatcher    final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;// 构造回调端点    InnerConnection(LoadedApk.ServiceDispatcher sd) {// 保存弱引用,避免单凭 Binder 端点强持有包装器        mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);// 结束构造    }// 接收 AMS 发回的连接结果    public void connected(ComponentName name, IBinder service) throws RemoteException {// 尝试取得 ServiceDispatcher        LoadedApk.ServiceDispatcher sd = mDispatcher.get();// 包装器仍存在时继续        if (sd != null) {// 转交连接结果            sd.connected(name, service);// 结束非空判断        }// 结束回调    }// 结束内部类}

ServiceConnection 的代理包装与回调链
图 5 ServiceConnection 经 ServiceDispatcher 和 InnerConnection 变成 Binder 回调端点,再切回调用方 Handler。

IServiceConnection 是 android.app 包下的 AIDL 生成接口,它的 Stub/Proxy 分工与 5.7 节 UserAidl 那套结构相同。InnerConnection extends IServiceConnection.Stub 意味着这份实现留在调用方进程,跨进程传出去的只是它的 Binder 引用;AMS 在 publishServiceLocked() 里执行 c.conn.connected(r.name, service) 时拿到的是 IServiceConnection.Proxy,这次调用经 Binder 驱动回到调用方进程的 InnerConnection.connected()。Stub 与 Proxy 分处两端,两者的边界就是 bindService() 跨过的那道 Binder。

5.3 bindServiceLocked 登记了什么

AMS 的 bindService() 进入 ActiveServices.bindServiceLocked()。它同样调用 retrieveServiceLocked() 取得 ServiceRecord,随后通过 retrieveAppBindingLocked() 取得调用方与该 Service/Intent 的绑定记录,再创建 ConnectionRecord。

AppBindRecord 和 ConnectionRecord 分属两个粒度,后面的 b.connections.add(c)、b.client.connections.add(c) 里的 b 与 c 就是这个区别。ServiceRecord.retrieveAppBindingLocked()(ServiceRecord.java 第 363—377 行)以 IntentBindRecord 和调用方 ProcessRecord 为键,做的是查不到才建;同一个调用进程用同一个 Intent 反复 bindService(),取到的是同一个 AppBindRecord,它代表“这个进程对这个 Service 的这个绑定 Intent”的聚合状态。ConnectionRecord 则是 bindServiceLocked() 里每次调用都新建的对象(new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent)),对应一次具体的 bindService() 调用,也就是应用传入的那一个 ServiceConnection。同一个 AppBindRecord 的 connections 字段可以聚合多个 ConnectionRecord:同一个调用方对同一个 Service、同一个 Intent 用两个不同的 ServiceConnection 各绑一次,会得到两个 ConnectionRecord,共享同一个 AppBindRecord。

连接会写入多个容器:Service 侧的 s.connections、AppBindRecord.connections、调用方进程的 connections,以及 ActiveServices.mServiceConnections。这些索引服务于不同清理方向,解绑、Activity 销毁、调用进程死亡或 Service 进程死亡时都能定位关系。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 783—813 行// 取得调用方与 Service 的绑定记录AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);// 创建一条具体连接记录ConnectionRecord c = new ConnectionRecord(b, activity,// 保存 Binder 回调、标志及客户端信息        connection, flags, clientLabel, clientIntent);// 取得回调端点的 Binder 身份IBinder binder = connection.asBinder();// 查询 Service 侧同一 Binder 的连接列表ArrayList<ConnectionRecord> clist = s.connections.get(binder);// 首次出现时创建列表if (clist == null) {// 分配连接列表    clist = new ArrayList<ConnectionRecord>();// 写入 Service 侧映射    s.connections.put(binder, clist);// 结束新列表分支}// 加入 Service 侧列表clist.add(c);// 加入 AppBindRecordb.connections.add(c);// 加入调用进程的连接集合b.client.connections.add(c);// 查询全局连接表clist = mServiceConnections.get(binder);// 全局表没有该 Binder 时创建列表if (clist == null) {// 分配全局连接列表    clist = new ArrayList<ConnectionRecord>();// 写入全局索引    mServiceConnections.put(binder, clist);// 结束全局新列表分支}// 把连接加入全局索引clist.add(c);

5.4 复用同一条启动链

当 flags 带 BIND_AUTO_CREATE,bindServiceLocked() 更新 lastActivity 后调用同一个 bringUpServiceLocked()。所以“绑定会不会创建 Service”的准确答案取决于标志和当前状态;在常见的 BIND_AUTO_CREATE 情况下,目标进程与 Service 尚不存在时,会复用第三章的进程创建和 scheduleCreateService() 链。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 815—820 行// 判断是否要求绑定时自动创建 Serviceif ((flags&Context.BIND_AUTO_CREATE) != 0) {// 刷新 Service 活动时间    s.lastActivity = SystemClock.uptimeMillis();// 复用 bringUpServiceLocked 启动链    if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {// 启动失败时返回零        return 0;// 结束失败判断    }// 结束自动创建分支}

启动态和绑定态可以同时存在。stopService() 只清除启动需求时,仍有自动创建式绑定的 Service 可能继续存活;unbindService() 清除最后一条绑定时,若 startRequested 仍为真,Service 也不会因此停止。

5.5 requestServiceBindingLocked 到 publishService

Service 进程可用后,requestServiceBindingLocked() 先登记执行状态,再调用 scheduleBindService()。目标进程的 handleBindService() 在非 rebind 分支执行 onBind(),取得业务 Binder,并通过 publishService() 发回 AMS。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1171—1180 行// 首次请求或 rebind 且存在客户端时投递if ((!i.requested || rebind) && i.apps.size() > 0) {// 捕获跨进程投递异常    try {// 标记 bind 生命周期正在执行        bumpServiceExecutingLocked(r, execInFg, "bind");// 提升目标进程状态        r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);// 投递绑定生命周期        r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,// 附带进程状态                r.app.repProcState);// 首次绑定时记录请求已发出        if (!rebind) {// 避免重复发起相同绑定            i.requested = true;// 结束首次绑定分支        }// 标记曾经绑定        i.hasBound = true;
java
// 代码位置 core/java/android/app/ActivityThread.java 第 2902—2911 行// 捕获 Binder 回报异常try {// 首次绑定走 onBind    if (!data.rebind) {// 调用业务 Service 并取得 Binder        IBinder binder = s.onBind(data.intent);// 把 Binder 发布给 AMS        ActivityManagerNative.getDefault().publishService(// 携带 Service token、Intent 和 Binder                data.token, data.intent, binder);// rebind 走单独回调    } else {// 调用 onRebind        s.onRebind(data.intent);// 告诉 AMS rebind 执行完成        ActivityManagerNative.getDefault().serviceDoneExecuting(// 使用匿名执行完成类型                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);// 结束 rebind 分支    }

AMS 收到 publishService() 后,publishServiceLocked() 把 Binder 写入 IntentBindRecord,遍历与该 Intent 匹配的连接,调用 c.conn.connected(r.name, service)。这里的 c.conn 正是调用方进程创建的 InnerConnection Binder 端点。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 873—895 行// 构造发布 Intent 的比较键Intent.FilterComparison filter// 包装原 Intent        = new Intent.FilterComparison(intent);// 查找对应的绑定记录IntentBindRecord b = r.bindings.get(filter);// 只处理尚未收到 Binder 的记录if (b != null && !b.received) {// 保存 Service 返回的 Binder    b.binder = service;// 标记请求已发出    b.requested = true;// 标记 Binder 已收到    b.received = true;// 遍历 Service 的连接映射    for (int conni=r.connections.size()-1; conni>=0; conni--) {// 取得一组连接        ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);// 遍历组内连接        for (int i=0; i<clist.size(); i++) {// 取得单条连接            ConnectionRecord c = clist.get(i);// 跳过 Intent 不匹配的连接            if (!filter.equals(c.binding.intent.intent)) {// 继续下一条连接                continue;// 结束过滤分支            }// 捕获连接回调异常            try {// 跨进程回调调用方的 InnerConnection                c.conn.connected(r.name, service);

5.6 回调回到调用方主线程:RunConnection 与 doConnected

Binder 线程收到 InnerConnection.connected() 后,ServiceDispatcher.connected() 不会直接调用应用的 ServiceConnection。有 mActivityThread Handler 时,它投递 RunConnection,由指定线程执行 doConnected()。常规 Activity Context 使用主线程 Handler,因此 onServiceConnected() 回到调用方主线程。

java
// 代码位置 core/java/android/app/LoadedApk.java 第 1146—1152 行// 接收 InnerConnection 转来的连接结果public void connected(ComponentName name, IBinder service) {// 有目标 Handler 时切换线程    if (mActivityThread != null) {// 投递连接任务        mActivityThread.post(new RunConnection(name, service, 0));// 没有 Handler 时直接处理    } else {// 同步更新连接        doConnected(name, service);// 结束 Handler 分支    }// 结束方法}

doConnected() 还会为新 Binder 注册 linkToDeath()。如果旧 Binder 存在,先调用 onServiceDisconnected();新 Binder 非空时再调用 onServiceConnected()。死亡监听使调用方能在服务端进程意外退出后清理本地连接状态。

java
// 代码位置 core/java/android/app/LoadedApk.java 第 1191—1204 行// 新 Binder 非空时建立连接信息if (service != null) {// 清除死亡状态    mDied = false;// 创建连接信息    info = new ConnectionInfo();// 保存 Binder    info.binder = service;// 创建死亡监听器    info.deathMonitor = new DeathMonitor(name, service);// 捕获 Binder 已死亡异常    try {// 注册死亡通知        service.linkToDeath(info.deathMonitor, 0);// 保存活动连接        mActiveConnections.put(name, info);// Binder 已死时清理    } catch (RemoteException e) {// 移除无效连接        mActiveConnections.remove(name);// 结束本次处理        return;// 结束异常分支    }
java
// 代码位置 core/java/android/app/LoadedApk.java 第 1217—1224 行// 存在旧连接时通知断开if (old != null) {// 回调应用的 ServiceConnection    mConnection.onServiceDisconnected(name);// 结束旧连接分支}// 新 Binder 存在时通知连接成功if (service != null) {// 把 Binder 交给应用    mConnection.onServiceConnected(name, service);// 结束新连接分支}

5.7 asInterface 代理

onServiceConnected() 参数类型是 IBinder。若服务通过 AIDL 定义接口,客户端调用 UserAidl.Stub.asInterface(service):本地 Binder 会返回本地接口,远程 Binder 则包装成 Proxy。业务代码随后调用的是 UserAidl,实际跨进程行为由生成代码完成。

aidl
// 代码位置 UserAidl.aidl 第 1—5 行// 声明 AIDL 包package com.example.service;// 定义跨进程用户接口interface UserAidl {// 读取用户名    String getUserName();// 读取口令    String getUserPassword();// 结束接口}

下面摘出 AIDL 生成代码中决定“本地对象还是远程代理”的结构。实际生成文件还包含事务编号、onTransact() 与 Parcel 编解码,这些机械代码不影响本节判断。

java
// 代码位置 UserAidl.java 第 1—20 行// 定义生成的 Binder 接口public interface UserAidl extends android.os.IInterface {// 提供默认实现    public static class Default implements UserAidl {// 返回默认用户名        public String getUserName() { return null; }// 返回默认口令        public String getUserPassword() { return null; }// 返回默认 Binder        public android.os.IBinder asBinder() { return null; }// 结束默认实现    }// 定义 Binder 服务端骨架    public static abstract class Stub extends android.os.Binder implements UserAidl {// 声明接口描述符        private static final java.lang.String DESCRIPTOR = "com.example.service.UserAidl";// 把 IBinder 转换成业务接口        public static UserAidl asInterface(android.os.IBinder obj) {// 空 Binder 直接返回空            if (obj == null) return null;// 查询同进程本地接口            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);// 类型匹配时复用本地对象            if (iin instanceof UserAidl) return (UserAidl) iin;// 跨进程时创建代理            return new Proxy(obj);// 结束转换方法        }// 结束 Stub    }// 结束接口}

演示 Service 返回 UserAidl.Stub 匿名实现。示例中的邮箱和口令已经替换为中性占位值。

java
// 代码位置 MessageService.java 第 1—31 行// 声明演示 Servicepublic class MessageService extends Service {// 定义日志标签    private static final String TAG = "MessageService";// 创建 AIDL Binder 实现    private final UserAidl.Stub mBinder = new UserAidl.Stub() {// 实现用户名读取        @Override public String getUserName() {// 返回中性邮箱占位值            return "user@example.com";// 结束用户名方法        }// 实现口令读取        @Override public String getUserPassword() {// 返回中性口令占位值            return "123456";// 结束口令方法        }// 结束 Binder 实现    };// 处理 Service 创建    @Override public void onCreate() {// 调用父类实现        super.onCreate();// 记录创建日志        Log.e(TAG, "onCreate");// 用阻塞复现 Service 执行超时        Thread.sleep(25000);// 结束创建回调    }// 处理启动参数    @Override public int onStartCommand(Intent intent, int flags, int startId) {// 记录启动日志        Log.e(TAG, "onStartCommand");// 返回非粘性策略        return START_NOT_STICKY;// 结束启动回调    }// 处理绑定    @Override public IBinder onBind(Intent intent) {// 返回 AIDL Binder        return mBinder;// 结束绑定回调    }// 处理解绑    @Override public boolean onUnbind(Intent intent) {// 记录解绑日志        Log.e(TAG, "onUnbind");// 采用默认不 rebind 语义        return false;// 结束解绑回调    }// 结束 Service}

Binder 驱动通信流程的三个入口
图 6 Binder 驱动以 binder_proc、handle、todo 与 wait 队列衔接写入、唤醒和读取过程。

Android 6.0 的 Binder 驱动通信可以用三个系统调用阶段定位:

  • binder_open 为进程建立 binder_proc。驱动内部用多棵红黑树维护节点、引用和缓冲区等结构;handle 0 约定指向 ServiceManager,其余 handle 由引用关系分配。
  • binder_mmap 建立 Binder 缓冲区的用户空间与内核空间映射。录屏登记的 6.0 讲解口径是只开一个物理页用于映射管理,并把一次完整往返按六次数据复制描述;这个计数依赖统计边界,本文只把它作为该版本材料的实现提示,不扩展成所有 Binder 版本的固定结论。
  • binder_ioctl 承载 binder_write 与 binder_read。写入侧依据 handle 找到目标 binder_proc,把工作项送入线程或进程 todo 队列并唤醒;读取侧先检查 todo,没有工作时进入 wait 队列等待。

从这层回看 Service 绑定,IServiceConnection、IApplicationThread、AIDL 的 UserAidl 都只是不同的 Binder 接口。驱动传递的是事务与 Binder 引用,调用方最终拿到的是本地 Stub 或远程 Proxy,取决于服务端是否与调用方处于同一进程。

六、进程没起来时的一次回环

6.1 mPendingServices 与 attachApplicationLocked

第三章在 startProcessLocked() 后把 ServiceRecord 放进 mPendingServices。新进程创建 ActivityThread 并通过 attachApplication 连回 AMS 后,ActivityManagerService.attachApplicationLocked() 调用 mServices.attachApplicationLocked(app, processName)。ActiveServices 遍历等待队列,按隔离进程记录,或按 uid 与进程名,挑出属于该进程的 Service。

匹配项先从 mPendingServices 删除,再调用 realStartServiceLocked()。这段回环保证 AMS 不会在 ApplicationThread 尚不可用时投递创建事务,也避免把另一个同名但 uid 不同的进程误当成目标。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 2026—2045 行// 处理新附着进程等待中的 Serviceboolean attachApplicationLocked(ProcessRecord proc, String processName)// 声明远程异常        throws RemoteException {// 记录是否处理了工作    boolean didSomething = false;// 仅在等待队列非空时遍历    if (mPendingServices.size() > 0) {// 保存当前候选记录        ServiceRecord sr = null;// 捕获真实启动的远程异常        try {// 遍历等待中的 Service            for (int i=0; i<mPendingServices.size(); i++) {// 取得候选记录                sr = mPendingServices.get(i);// 隔离进程或 uid、进程名不匹配时跳过                if (proc != sr.isolatedProc && (proc.uid != sr.appInfo.uid// 完成复合匹配条件                        || !processName.equals(sr.processName))) {// 继续下一条                    continue;// 结束不匹配分支                }// 从等待队列移除匹配记录                mPendingServices.remove(i);// 修正删除后的索引                i--;// 把 Service 包加入新进程                proc.addPackage(sr.appInfo.packageName, sr.appInfo.versionCode,// 关联进程统计                        mAm.mProcessStats);// 回到真实启动阶段                realStartServiceLocked(sr, proc, sr.createdFromFg);// 标记本轮完成工作                didSomething = true;// 结束遍历            }

这也是 startService 与带 BIND_AUTO_CREATE 的 bindService 共享启动链的证据:等待队列保存的是 ServiceRecord,并不按最初入口拆成两套进程启动机制。回到 realStartServiceLocked() 创建 Service 后,启动请求继续发送 args,绑定请求继续调用 requestServiceBindingLocked()。

七、stopService 与 unbindService

7.1 stopService:目标进程走到 onDestroy

ContextImpl.stopServiceCommon() 同样请求 AMS,由 ActiveServices 清除启动态需求,并在 Service 已无存活理由时投递停止。它与 startServiceCommon() 对称,只是把 Binder 调用换成 stopService(),并把负数返回码翻译成权限异常。

java
// 代码位置 core/java/android/app/ContextImpl.java 第 1265—1280 行// 定义 stopService 的公共实现private boolean stopServiceCommon(Intent service, UserHandle user) {// 捕获跨进程异常    try {// 校验 Service Intent        validateServiceIntent(service);// 把 Intent 整理成可离开当前进程的状态        service.prepareToLeaveProcess();// 获取 AMS 代理并发起停止请求        int res = ActivityManagerNative.getDefault().stopService(// 传递应用线程、Intent、解析类型与用户标识            mMainThread.getApplicationThread(), service,// 补全参数并接收整型返回码            service.resolveTypeIfNeeded(getContentResolver()), user.getIdentifier());// 负数返回码表示权限不足        if (res < 0) {// 抛出安全异常            throw new SecurityException(// 说明未获准停止                    "Not allowed to stop service " + service);// 结束权限判断        }// 非零返回码表示确实停止了某个 Service        return res != 0;// 捕获远程异常    } catch (RemoteException e) {// 转换成运行时异常        throw new RuntimeException("Failure from system", e);// 结束异常处理    }// 结束方法}

目标进程的 ActivityThread.handleStopService() 按 token 从 mServices 删除 Java 对象,调用 onDestroy(),等待 QueuedWork 收尾,再回报 SERVICE_DONE_EXECUTING_STOP。

java
// 代码位置 core/java/android/app/ActivityThread.java 第 3035—3052 行// 处理停止 Service 消息private void handleStopService(IBinder token) {// 从目标进程实例表移除 Service    Service s = mServices.remove(token);// 找到实例时执行销毁    if (s != null) {// 捕获生命周期异常        try {// 调用 Service.onDestroy            s.onDestroy();// 取得 Service 基础 Context            Context context = s.getBaseContext();// 只处理 ContextImpl            if (context instanceof ContextImpl) {// 取得 Service 类名                final String who = s.getClassName();// 安排 Context 最终清理                ((ContextImpl) context).scheduleFinalCleanup(who, "Service");// 结束 Context 类型判断            }// 等待排队工作完成            QueuedWork.waitToFinish();// 捕获完成回报异常            try {// 回报 stop 生命周期完成                ActivityManagerNative.getDefault().serviceDoneExecuting(// 使用停止执行类型                        token, SERVICE_DONE_EXECUTING_STOP, 0, 0);

onDestroy() 是组件生命周期回调,不等于进程退出。进程中可能还有 Activity、Provider、Receiver、其他 Service、线程或 Binder 对象。AMS 会根据整体组件状态和内存压力调整进程优先级,不能从一次 stopService() 推导出 Linux 进程立即消失。

7.2 unbindService:isServiceNeeded 决定是否真正停止

unbindServiceLocked() 删除对应 ConnectionRecord。删除动作由 removeConnectionLocked() 执行,判据是 IServiceConnection 的 Binder 身份;同一调用方用多个 ServiceConnection 绑定时,这里按 Binder 逐条清空。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 912—931 行// 定义 unbindService 的锁内实现boolean unbindServiceLocked(IServiceConnection connection) {// 取得回调端点的 Binder 身份    IBinder binder = connection.asBinder();// 按 Binder 查询全局连接表    ArrayList<ConnectionRecord> clist = mServiceConnections.get(binder);// 找不到对应连接时直接失败    if (clist == null) {// 记录未找到连接        Slog.w(TAG, "Unbind failed: could not find connection for "// 附加 Binder 信息                + connection.asBinder());// 返回失败        return false;// 结束早退分支    }// 清除调用身份以执行系统侧工作    final long origId = Binder.clearCallingIdentity();// 捕获清理过程异常    try {// 逐条清空该 Binder 对应的连接列表        while (clist.size() > 0) {// 取列表中第一条连接            ConnectionRecord r = clist.get(0);// 从各张表中移除该连接            removeConnectionLocked(r, null, null);// 仍在原位时兜底移除,避免死循环            if (clist.size() > 0 && clist.get(0) == r) {// 记录异常状态                Slog.wtf(TAG, "Connection " + r + " not removed for binder " + binder);// 强制移除                clist.remove(0);// 结束兜底分支            }

关系变化后,ActiveServices 用 isServiceNeeded() 判断 Service 是否仍有存在理由。AOSP 6.0 的核心条件是 startRequested 为真,或者仍存在 BIND_AUTO_CREATE 连接。只有两者都不成立,系统才有条件执行 bring-down。

java
// 代码位置 services/core/java/com/android/server/am/ActiveServices.java 第 1647—1662 行// 定义 Service 是否仍需要存在的判断private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn, boolean hasConn) {// 显式启动请求未清除时仍需要    if (r.startRequested) {// 直接判定需要        return true;// 结束启动请求判断    }// 调用方未提供绑定结论时现查    if (!knowConn) {// 查询是否存在自动创建式绑定        hasConn = r.hasAutoCreateConnections();// 结束现查分支    }// 存在自动创建式绑定时仍需要    if (hasConn) {// 判定需要        return true;// 结束绑定判断    }// 两个条件都不成立时不再需要    return false;// 结束方法}

这个条件解释了常见的两种“停不下来”:调用过 startService() 又绑定时,只解绑仍保留启动态;只调用 stopService() 时,自动创建式绑定仍然保留绑定态。要完整释放同时处于两种状态的 Service,调用方需分别解除自己建立的状态。

7.3 线程、静态变量与 GC root

Service 实例从 ActivityThread.mServices 删除并完成 onDestroy() 后,普通对象仍要等到不可达才会被 GC。若业务自行创建的线程还在运行,线程对象及其引用链可以成为 GC root 的可达路径;静态字段也由类加载器关联。Framework 不会替业务代码终止这些线程,也不会把所有静态字段清空。

因此,“Service 已销毁”“Service 对象可回收”“目标进程退出”是三个层级:

层级 直接判据 不能据此推出
组件生命周期结束 onDestroy() 已返回,AMS 完成停止记账 进程已经退出
Java 对象可回收 没有任何强引用路径 GC 已经立刻执行
进程退出 进程被系统或应用终止 静态状态会以某种业务协议持久化

若 onDestroy() 后仍看到工作继续,排查重点应放在线程、线程池、Handler 消息、Binder 回调和静态引用,而不是假设 Framework 没有调用销毁回调。

八、小结

8.1 dumpsys、logcat 与 am 命令

plain text
startService:ContextImpl.startServiceCommon  → AMS.startService  → ActiveServices.startServiceLocked  → retrieveServiceLocked  → bringUpServiceLocked / realStartServiceLocked  → scheduleCreateService → handleCreateService → onCreate  → scheduleServiceArgs → handleServiceArgs → onStartCommand  → serviceDoneExecuting bindService:ContextImpl.bindServiceCommon  → LoadedApk.getServiceDispatcher  → AMS.bindService  → ActiveServices.bindServiceLocked  → retrieveServiceLocked + ConnectionRecord  → bringUpServiceLocked / realStartServiceLocked  → scheduleBindService → handleBindService → onBind  → publishService → c.conn.connected  → InnerConnection → RunConnection → doConnected  → onServiceConnected

设备或模拟器上可以用下面的入口交叉验证。命令中的包名和组件名需要替换成实际应用。

bash
# 查看 AMS 当前保存的 ServiceRecord、启动态与绑定关系adb shell dumpsys activity services com.example.service # 过滤 Service 生命周期、AMS 超时和 ANR 相关日志adb logcat -v threadtime | grep -E "ActivityManager|ActiveServices|MessageService|ANR|am_anr" # 从 shell 发起显式启动adb shell am startservice -n com.example.service/.MessageService # 从 shell 停止显式 Serviceadb shell am stopservice -n com.example.service/.MessageService

验证启动复用时,可以连续执行两次 am startservice,对照日志中 onCreate() 与 onStartCommand() 的次数,再从 dumpsys 查看 startRequested、最近 startId 与连接信息。验证绑定链需要由测试 Activity 调用 bindService(),因为 shell 的 am 命令不能替应用提供一个可接收 onServiceConnected() 的 ServiceConnection 对象。

分析 ANR 时,不要只截取弹窗。至少保留以下证据:

  • ANR reason,确认是 executing service 还是输入分发、Broadcast 等其他类型;
  • traces 中主线程栈,确认阻塞点位于哪个生命周期或锁等待;
  • ANR 前后的 AMS 与业务日志,寻找 scheduleCreateService、onCreate、serviceDoneExecuting 对应的时间边界;
  • dumpsys activity services 中的 executing、startRequested 和 connections 状态。

核心问题由此可以落到具体状态变化上:两条入口共享 ContextImpl → AMS → ActiveServices → ActivityThread 的调度骨架;startService 维护启动项并投递 CREATE_SERVICE、SERVICE_ARGS,bindService 额外维护连接记录并投递 BIND_SERVICE。同一段耗时是否形成 Service ANR,取决于它是否落在 bumpServiceExecutingLocked() 与对应完成回报之间,以及进程采用 20 秒还是 200 秒窗口,而不取决于方法名字看起来是否“属于 Service”。


本文由 AgentWorkFlow 生成

觉得不错?给作者点个赞吧

分类:Android系统

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Android-Framework(十七)-startService 与 bindService 的启动流程 · Yinchen 的博客