Android 应用进程 ServiceManager 的实现

Android 应用进程 ServiceManager 的实现


实现一个普通应用进程中的 ServiceManager,可自由注册和获取 Binder 服务。

文末给出开源仓库地址。

Binder 相关基础可参考:android-binder-设计分析

实名 Binder 与匿名 Binder

实名 Binder

在 Binder 通信模型中,存在一个 ServiceManager 的角色,它作为 Android 系统的服务总管,负责建立 Binder 名字和 Binder 实体的映射。

提示:ServiceManager 中的 Service 和 android.app.Service 组件不是同一个概念。

ServiceManager 中存在一个 addService 方法,使用它可将一个 Binder 实体通过一个唯一的字符串标识注册到 Binder 驱动中,同时 ServiceManager 会缓存 Binder 的引用,下次通过一个字符串标识来请求 ServiceManager 即可获得对应的 Binder 实体的引用。

Android Framework 层中的系统服务,都是通过 Java 层 ServiceManager 的 addService 方法将自己注册为服务的,例如著名的 ActivityManagerServiceWindowManagerService

// AOSP 9.0.0_r3: android.server.am.ActivityManagerService.java

public void setSystemProcess() {
   
  ...
  ServiceManager.addService(Context.ACTIVITY_SERVICE, this, /* allowIsolated= */ true,
         DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
  ...
}
// AOSP 9.0.0_r3: android.server.SystemServer.java

private void startOtherServices() {
   
  ...
  wm = WindowManagerService.main(context, inputManager,
         mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
         !mFirstBoot, mOnlyCore, new PhoneWindowManager());
  ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
         DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
  ...
}

当注册完成后,就可以通过字符串标识 Context.ACTIVITY_SERVICEContext.WINDOW_SERVICE 来通过 ServiceManager 的 getService 方法获取相应 Binder 服务端的引用,对系统服务发起请求了。

像这样使用字符串标识明确的向 ServiceManager 注册的 Binder 实体,被称为实名 Binder。

提示:在 Android 系统中,四大组件的启动均是通过 ActivityManagerProxy(Binder 客户端)向 ActivityManagerService(Binder 服务端)发送进程间通信请求实现的。

匿名 Binder

还存在一种匿名 Binder,匿名 Binder 的创建依赖于一条已经建立的 Binder 连接,通过已建立的 Binder 连接,将 Binder 服务端的引用从服务端进程传递至另一端,此时另一端持有 Binder 引用就可以通过 Binder 驱动与服务端进行沟通了。

匿名 Binder 不通过明确的字符串标识进行注册,而是通过私有 Binder 连接通道传递 Binder 引用,所以匿名 Binder 无法通过枚举或者猜测得到。

除了 ServiceManager 自身通过 0 号引用注册的 Binder 连接(仅服务于实名 Binder 的注册),其他的就只有系统服务注册的实名 Binder 连接了。

所以匿名 Binder 的创建必须通过实名 Binder 来实现。

需求分析

如果现在要实现自己的 Binder 服务,服务存在于独立的进程中,且客户端可以获取服务端的 Binder 引用,自由地向服务端发出任务请求,要怎么办呢。

还要考虑动态注册 Binder 服务的需求,现实情况下,一个 Binder 服务可能无法满足业务需求,所以可以考虑以字符串标识对 Binder 服务端进行注册,那么客户端可以利用字符串标识查询对应的 Binder 服务端(类似于 Android 系统的 ServiceManager)。

那么需要分两部分考虑:

  1. 实现的 Binder 服务需要在一个进程中运行,在 Android 中四大组件均可在独立进程中运行,首先 Activity 不能在后台运行,所以 Binder 服务不能在 Activity 中运行,BroadcastReceiver 只能执行短时间的任务,所以也不能放置 Binder 服务,那么放在 Service 和 ContentProvider 是可行的。

  2. 如果传递 Binder 服务的引用给客户端,客户端持有 Binder 引用后即可向 Binder 服务端发起请求,那么有如下分析:

首先我们的应用处于 Android 普通应用进程中,并没有系统权限,所以不可能通过 Android 系统的 ServiceManager 注册自己的 Binder 服务,那么直接注册成为实名 Binder 服务的方法就不能采用了。

那么现在只能考虑通过匿名 Binder 来实现,先找到一个已建立的实名 Binder 连接,使用这个连接来传递自己的 Binder 服务端的引用给客户端使用即可。

Android 系统服务都是实名 Binder 连接,但是它们提供的接口功能都是固定的,肯定不能自由的被利用,所以只能先找到一个与匿名 Binder 相关的 API。同时为了实现 Binder 服务端,所以还要考虑后台运行的支持。

系统中的 Bundle 可以携带 Binder(使用 putBinder 方法),即 Intent 也能携带 Binder(Intent 可携带 Bundle);还有 Service 组件的 onBind 方法,在客户端绑定时可以返回 Binder 引用。

那么基于这个条件就可以让的 Service 和 ContentProvider 组件具有了传送匿名 Binder 的功能,如下:

  1. 对于 Service 组件,开发者可实现一个远程进程中的 Service,然后实现它的 onBind 方法,返回一个 Binder 对象,这个 Binder 对象的引用将会传递给客户端,客户端(例如 Activity)使用 bindService 方法,通过 ServiceConnection 中的 onServiceConnected 回调获取这个 Binder 引用后,即可向 Service 中的 Binder 对象发送处理相关任务的请求,即通过 bindService 可以获得一个匿名 Binder;同时也可以在启动 Service 时,通过 Intent 携带一个 Bundle 对象,Bundle 对象中携带一个 Binder 对象进行传递,即在启动服务时可通过 Intent 传递 Binder 对象。

  2. 对于 ContentProvider 组件,开发者在实现了 ContentProvider 之后,可以在客户端使用 context.getContentResolver() 获取一个 ContentResolver 对象,利用这个 ContentResolver 对象向 ContentProvider 发出增删查改请求(querydeleteinsertupdate),还可以通过 call 方法发出自定义命令,其中 call 方法最后一个参数可以携带一个 Bundle 对象,这个 Bundle 允许携带 Binder 对象,即通过 call 方法可传递 Binder;同时 call 方法返回一个 Bundle 对象,即 ContentProvider 端也可直接返回 Binder 对象。

即 Service 和 ContentProvider 可以实现独立的 Binder 服务,或者有能力对 Binder 服务进行统一的管理。

分析了需求和实现方法,下面确定一下具体实施方案。

实现方案

要通过匿名 Binder 实现一个 Binder 服务,那么首先需要实现一个 Binder 服务端对象,将其放置在远程进程中,再通过匿名 Binder 通道,将 Binder 服务端引用传递给客户端,此时客户端使用 Binder 服务端的引用即可向服务端发起请求,这时就完成了一个 Binder 服务的建立;

如果要实现 Binder 服务的动态注册,Binder 服务可能分布在各个进程中,如果需要统一管理,需要首先建立一个类似于系统 ServiceManager 的角色,ServiceManager 运行在一个独立的进程,Binder 服务可通过进程间通信的方法将自己的 Binder 引用注册到 ServiceManager 中,客户端可以通过 ServiceManager 自由获取 Binder 服务端的引用。

现在有两种办法可以实现匿名 Binder,需要采用哪一种方案实现 Binder 服务呢。

Service 方案

如果采用 Service 的方案实现一个 Binder 服务,首先需要实现一个 Service,然后在清单文件中配置它,然后实现一个自己的 Binder 服务端,放在这个远程 Service 中,客户端使用 bindService 获取 Binder 服务端的 Binder 引用即可,不过这样的话看起来和直接使用 Service 组件没有任何区别,属于脱裤子放屁;

如果实现 Binder 服务端的动态注册,将需要使用到的 Binder 服务端对象动态传递至 Service 中,然后客户端再想办法查询 Binder 服务端的引用。那么考虑借助 Intent 对象,利用 ContextstartServicebindService 方法动态传递 Binder 对象至 Service 中,在 Intent 中携带字符串标识,然后在 Service 中建立一个用字符串作为标识的 Map<String, IBinder> 结构缓存这些 Binder 服务端,当客户端获取时,首先需要使用 bindService 方法,绑定 Service,然后 Service 返回一个 Binder 引用,不过这个 Binder 引用并不能是客户端想要获取的 Binder 服务端引用,因为每次绑定 Service,它都始终返回同一个 Binder,就是 Service 的 onBinder 方法第一次返回的那个 Binder,所以这个 Binder 需要作为一个中转的 Binder 服务端,需要先使用 AIDL 定义一个服务获取客户端想要的 Binder 服务端的方法,例如:

// IBridge.aidl

interface IBridge {
   
  IBinder getBinder(String name);
}

那么客户端通过获取这个 IBridge 对象的 Binder 引用之后,再调用 getBinder 查询 Service 中 Map<String, IBinder> 缓存的 Binder 服务端引用即可。

这是使用 Service 组件的方案,看起来挺麻烦,下面再说使用 ContentProvider 组件的实现方案。

ContentProvider 方案

如果采用 ContentProvider 的方案实现一个 Binder 服务,那么首先需要实现一个 ContentProvider,然后也需要清单文件中配置它,然后实现一个自己的 Binder 服务端,放在这个远程进程的 ContentProvider 中,客户端使用 call 获取返回的 Bundle 数据包,然后读取出其中携带的 Binder 服务端的 Binder 引用即可,ContentProvider 的本意是为了共享数据,但这样一来就被赋予了新的功能,用来做 Binder 服务端的支持。不过相对于使用 Service 实现一个 Binder 服务来说,也没有体现出太多优势;

如果实现 Binder 服务端的动态注册,将需要使用到的 Binder 服务端对象动态传递至 ContentProvider 中,然后客户端再想办法查询 Binder 服务端的引用。那么可以直接使用 call 方法将字符串标识和通过 Bundle 数据包将服务端 Binder 发送至 ContentProvider 中,ContentProvider 使用一个 Map<String, IBinder> 缓存即可,下次客户端可以直接使用字符串标识通过 call 方法请求获取对应的 Binder 服务端,那么 ContentProvider 直接返回携带对应 Binder 服务端的引用即可,这样来看就比 Service 实现动态注册 Binder 服务有优势的多。

最终结论

通过对两种方案的描述,谁更有优势显而易见。

对于 Service 来说,它还存在诸多限制,例如 bindService 绑定存在限制,BroadcastReceiver 组件无法绑定、绑定后必须取消绑定、或者服务不允许在后台运行,除非加上前台通知才允许后台运行,即使抛开这些限制,使用它实现 Binder 服务也是很麻烦的(需要借助 ServiceConnectation)。

反观 ContentProvider 均不存在上述限制,无需绑定,直接使用 call 请求即可,API 足够简洁,看起来可以完美的实现 Binder 服务。

实现

根据上面的方实现案分析(最终确定了使用 ContentProvider 组件实现),下面来真正实现 Android 应用进程中的 Binder 服务。

一个 Binder 服务

首先定义一个 Binder 服务的 AIDL 协议文件:

// IFoo.aidl
package io.l0neman.example;

interface IFoo {
   
    int add(int x, int y);
}

它是一个叫 IFoo 的 Binder 服务,它内部有一个负责两数相加的 add 方法

然后实现一个 ContentProvider,叫做 ServiceProvider,然后直接将 IFoo 服务内置进去。

public class ServiceProvider extends ContentProvider {
   

  public static final String ACTION_GET_SERVICE = "act.ser";
  public static final String KEY_SERVER = "ser";

  public static final String URI = "content://io.l0neman.example.binder";

  private FooService mFooService = new FooService();

  // Binder 服务端的实现
  private static final class FooService extends IFoo.Stub {
   
    @Override public int add(int x, int y) {
   
      return x + y;
    }
  }

  @Nullable @Override
  public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
   
    if (ACTION_GET_SERVICE.equals(method)) {
   
      // 获取 Service 则返回携带 FooService 的 Bundle
      Bundle result = new Bundle();
      BundleCompat.putBinder(result, KEY_SERVER, mFooService);
      return result;
    }

    return null;
  <
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值