Binder(七):四大组件底层的通信机制:bindService 的跨进程链路

文章内容

一、面试题:广播如何跨进程

1.1 题目的真实问法

听上去在考 BroadcastReceiver,实际问的是四大组件的底层通信机制——广播只是四个组件里被挑出来的那一个,换成 Activity 启动、bindService、ContentProvider 查询,答案的骨架完全一样。

只回答「广播走 Binder」拿不到分。面试官想听的是这条链路上有哪几站、每一站由谁完成、哪几步跨了进程。

1.2 答案骨架

四大组件由 AMS(ActivityManagerService,运行在 system_server 进程里的活动管理服务)统一管理,组件的启动、绑定、广播分发都要经过它。应用进程和 system_server 是两个进程,中间的所有交互靠 Binder。

拆开讲最清楚的例子是 bindService:调用方法和收到回调之间隔着好几次跨进程,而这些环节在日常开发里完全不可见。四个组件的差异只体现在 AMS 内部走哪条分支,进出 AMS 的方式是共用的,所以吃透一条就等于吃透四条。下面顺着 bindService 走一遍。

二、先找到 AMS:ServiceManager 的作用

四大组件由 AMS 统一管理,底层通信全走 Binder

2.1 四大组件由 AMS 管理

Activity 里调 bindService(),这个方法不会直接把请求送到目标服务所在的进程。组件的生命周期由 AMS 掌管,任何组件操作都得先报到 AMS 那里——由它决定目标进程要不要拉起、这次调用有没有权限、绑定关系记在哪。应用自己既不知道目标服务在哪个进程,也没有权限去启动别人的进程。

于是问题变成:应用进程怎么拿到 AMS?AMS 在另一个进程里,能拿到的只有它的 Binder 引用。

2.2 ServiceManager:服务名到 handle 的映射

ServiceManager:服务名到 handle 的映射,handle 固定为 0

系统服务有几十上百个,AMS、PMS(包管理)、WMS(窗口管理)各有各的 Binder 引用,而 handle(Binder 引用在进程内的整数句柄)是驱动动态分配的,没人能事先知道某个服务这次开机拿到的是几号。

ServiceManager(服务管理器)解决的就是这个查询问题:它维护「服务名 → handle」的映射,服务启动时以名字注册,使用方报名字来换引用。像手机里的联系人:号码记不住,但姓名能记住,App 负责把名字翻译成号码。要找 AMS,就用 getService("activity") 这个名字去换。

2.3 ServiceManager 为什么固定为 0

查询者自己也面临同一个问题——想找 ServiceManager,得先知道它的 handle。这个死循环靠约定打破:ServiceManager 的 handle 固定为 0,写死在框架里,任何进程都能直接用 0 号句柄找到它。

所以严格说,getService("activity") 已经是一次跨进程调用了:应用进程通过 0 号句柄向 ServiceManager 发起 Binder 事务,把服务名送过去,把 AMS 的引用换回来。这一步经常被忽略,但它确实是链路上的第一次跨进程。

三、bindService 的完整跨进程链路

bindService 完整跨进程链路:客户端 / system_server / 服务端三条泳道

3.1 拿到 AMS 代理

客户端拿到的是 IActivityManager 的代理对象,也就是 AIDL 那一套里的 Proxy。框架把这一步包装在 ActivityManager.getService() 里,内部就是从 ServiceManager 查出 AMS 的 Binder 引用再 asInterface 转成接口。

3.2 bindService 进入 AMS

拿到代理后调 bindService()——这是 IActivityManager 上的方法,参数里带着目标服务的 Intent、调用方信息和一个 ServiceConnection 对应的 Binder。调用发出去,进程切换到 system_server。

AMS 在这里做的事不少:解析 Intent 找到目标 Service 组件,检查权限和调用方身份,查目标服务所在进程是否已经启动,没启动就先拉起进程。这些检查全部在 system_server 内部完成,不再跨进程。

3.3 scheduleBindService 投递到服务端

检查通过后,AMS 调用 scheduleBindService()。这一步的方向反了过来:从 system_server 发往服务端所在的应用进程,走的是 ApplicationThread(每个应用进程注册给 AMS 的 Binder 入口)。

对目标应用来说,这是一次被动接收的 Binder 事务。Binder 线程收到后不会就地执行,而是把任务抛给主线程的 Handler——组件回调必须在主线程,这也是 onBind 里做耗时操作会卡 UI 的原因。

3.4 服务端 onBind 返回 Binder 对象

主线程执行到 Service 的 onBind(),这是开发者唯一需要自己写的一环。返回值必须是一个 IBinder——通常是 AIDL 生成的 Stub 的实现类实例,它继承 Binder,具备被跨进程调用的能力。

返回值不会直接飞到客户端手里。服务端进程和客户端进程之间没有直连通道,这个 Binder 对象还得绕回 AMS。

3.5 返回路径:publishService 回到 AMS

Binder 对象返回路径:服务端 → AMS → 客户端,拿到的是代理

服务端进程重新取一次 AMS 代理,调用 publishService(),把「哪个连接、哪个组件、对应的 Binder 是什么」上报给 AMS。这是第四次跨进程,方向是应用进程 → system_server。

AMS 收到之后在自己的连接记录里找到当初发起绑定的那个客户端,取出它注册的 ServiceConnection 对应的 Binder,准备回调。

3.6 onServiceConnected 回调与 asInterface

AMS 通过那个 Binder 回调客户端,进程再次切换,客户端的 onServiceConnected(name, service) 被触发。参数里的 service 就是服务端 onBind() 返回的那个对象在客户端侧的代理。

客户端拿它调 Xxx.Stub.asInterface(service) 转成接口类型:同进程绑定时 queryLocalInterface 查得到,直接返回 Stub 本身;跨进程时查不到,包装成 Proxy 返回。此后每次调接口方法,都是一次 Binder 事务。

四、代码验证

4.1 服务获取与 bindService

框架把查询和转换包得很严实,把包装剥掉后是下面这几行。看它是为了确认「拿 AMS」本身就是一次 Binder 调用,而不是某种本地查表。

java
// 示意代码:框架内部拿到 AMS 并发起绑定的大致形态(这些是隐藏 API,应用层不能直接这么写)IBinder smBinder = getIServiceManager();                   // 取 ServiceManager 的引用,它的 handle 固定为 0IBinder amsBinder = smBinder.getService("activity");       // 报服务名换 AMS 的 Binder 引用,这一步已经跨进程IActivityManager ams = IActivityManager.Stub               // 拿到的是 IBinder,还需要转成接口        .asInterface(amsBinder);                           // 跨进程场景下返回的是 Proxy 代理对象 Intent intent = new Intent(context, RemoteService.class);  // 要绑定的目标服务IServiceConnection conn = mPackageInfo                     // 把 ServiceConnection 包装成一个 Binder        .getServiceDispatcher(connection, context, handler, flags);  // 供 AMS 日后回调客户端使用ams.bindService(appThread, activityToken,                  // 调用 AMS 的 bindService,进程切到 system_server        intent, resolvedType, conn, flags, callingPackage, userId);  // 参数里带上调用方身份与回调 Binder// AMS 内部:解析 Intent → 权限校验 → 目标进程未启动则先拉起 → 再向服务端投递

第一行到第三行做的都是「找到人」,真正的业务请求从 bindService 才开始。链路上的前两次跨进程都发生在这段代码里。

4.2 服务端返回与客户端回调

绑定的后半段分散在两个进程里,把两头的代码放在一起看,Binder 对象的传递路线就完整了。

java
// 示意代码:服务端与客户端两侧的关键回调// ===== 服务端进程:AMS 通过 scheduleBindService 投递过来后执行 =====public class RemoteService extends Service {               // 一个普通的服务组件    private final IPerson.Stub binder = new IPerson.Stub() {  // Stub 继承 Binder,具备被跨进程调用的能力        @Override public String getName() { return "leo"; }   // 接口方法的真正实现写在这里    };                                                     // 匿名类结束    @Override public IBinder onBind(Intent intent) {       // 绑定时被主线程调用        return binder;                                     // 返回值随后由 publishService 上报给 AMS    }                                                      // onBind 结束}                                                          // 服务类结束// 框架随后:ActivityManager.getService().publishService(token, intent, binder) —— 又一次跨进程回到 AMS // ===== 客户端进程:AMS 回调过来后执行 =====private final ServiceConnection connection = new ServiceConnection() {  // 绑定时注册的连接回调    @Override public void onServiceConnected(ComponentName name, IBinder service) {  // AMS 跨进程回调到这里        IPerson person = IPerson.Stub.asInterface(service); // service 是服务端 Binder 在本进程的代理        // 同进程返回 Stub 本身,跨进程返回 Proxy,调用方写法一致        try {                                              // 跨进程调用可能失败            String name2 = person.getName();               // 每次调用都是一次 Binder 事务        } catch (RemoteException e) {                      // 对端进程死亡等情况会抛出            e.printStackTrace();                           // 实际代码里应做降级处理        }                                                  // try 结束    }                                                      // 回调结束    @Override public void onServiceDisconnected(ComponentName name) { }  // 连接异常断开时回调};                                                         // 连接对象结束

服务端返回的 Binder 走的是「服务端 → AMS → 客户端」这条折线,中间两跳都是跨进程。客户端最终拿到的是代理,不是服务端那个对象本身。

五、总结

5.1 一句话链路

客户端用 0 号句柄找 ServiceManager 换到 AMS 引用 → 调 AMS 的 bindService → AMS 校验后 scheduleBindService 投递到服务端进程 → 服务端 onBind 返回 Binder → 服务端再经 publishService 上报 AMS → AMS 回调客户端 onServiceConnected → 客户端 asInterface 拿到代理开始正常调用。

广播的链路换的是站名:sendBroadcast 进 AMS,AMS 按 IntentFilter 匹配出接收者列表、排好队,再通过 scheduleReceiver 投递到各个目标进程,最后落到 onReceive。中间同样要过 AMS,同样是应用进程与 system_server 之间的往返,骨架没有变。

5.2 数一数跨进程点

这条链路上至少六次跨进程:查 ServiceManager 一次、bindService 一次、scheduleBindService 一次、服务端取 AMS 一次、publishService 一次、onServiceConnected 一次。面试时把这几处点出来,比笼统说「底层走 Binder」扎实得多。

想自己验证,在 onServiceConnected 里打断点,看调用栈从 ApplicationThread 一路回溯到 Binder 线程;或者用 adb shell ps -T 查看应用进程和 system_server 的 Binder:xxxx_N 线程,绑定服务时这些线程就是在处理上面这些事务。


本文由 AgentPlanFlow 生成

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

分类:Android系统

评论 (0)

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

加载评论中…

Binder(七):四大组件底层的通信机制:bindService 的跨进程链路 · Yinchen 的博客