从 AIDL 看 Android Binder

Android 应用开发中 Binder 的重要性有时不能体现出来。但深入理解 Binder 是确实了解 Android Framework AmS 和 PmS 这些最重要模块、提升 Android 开发能力的必经之路。从 AIDL 学习 Binder 是一个不错的切入点。

大部分 Android 应用是单进程的,常规应用开发中 Android Binder 的重要性很难体现出来。对应用开发者来说,能直接跟 Binder 打交道的唯一场景就是使用 bindService() 绑定服务。如果目标服务跟 UI 组件运行在同一个进程,这个跟 Binder 打交道的唯一场景,其实也并不真的涉及 IPC。考虑官方文档绑定服务概览中的这个例子,正是一个没有 IPC 能力的 Binder。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class LocalService : Service() {
// Binder given to clients.
private val binder = LocalBinder()

// Random number generator.
private val mGenerator = Random()

/** Method for clients. */
val randomNumber: Int
get() = mGenerator.nextInt(100)

/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
inner class LocalBinder : Binder() {
// Return this instance of LocalService so clients can call public methods.
fun getService(): LocalService = this@LocalService
}

override fun onBind(intent: Intent): IBinder {
return binder
}
}

这么看来,Binder IPC 机制并不是一个必需的知识。但实际并非如此,深入理解 Binder 是了解 Android Framework 中 AmS 和 PmS 这两个最重要模块、提升 Android 开发能力的必经之路。

对 Android 应用开发者来说,个人认为理解 Binder 的最佳切入点是 AIDL。

从 AIDL 到 Binder

AIDL 的用法非常简单,无非就是创建 .aidl 文件、基于 aidl 生成 Java 代码、在 Service.onBind() 中向客户端暴露 Stub。具体步骤可以参考Android 接口定义语言 (AIDL),这里不赘述。

那么 AIDL 到底解决了什么问题?我理解最关键的有两个:

  • 第一是通信协议问题。Binder 是 C/S 构架,客户端和服务端需要通信,AIDL 直接在生成的代码中把数据打包和解包搞定了,为我们免去了手工这些样板代码的痛苦。
  • 第二是编程接口问题。如果愿意,我们也可自己从 Binder/IBinder/IIterface 这些类和接口手写 StubProxy 从而实现”远程对象的本地代理”这一编程模式,但 AIDL 为我们生成这些编程接口岂不更好?

这里假定我们 aidl 文件是 IFakeService.aidl,文件内容如下:

1
2
3
4
5
6
7
package com.example.myapplication.aidl;

interface IFakeService {
int getValue();
void setValue(int value);
void addValue(int value);
}

Aidl 工具从上述文件生成对应的 IFakeService.java 文件,文件中的 IFakeService 是 Java interface,其中包括嵌套类 Stub,而 Stub 中又有一个私有的嵌套类 Proxy

这些生成的代码用法很简单,而且似乎永远也不会出错。但当我们仔细审视这些代码时,就会产生很多疑问:

  • StubProxy 分别代表什么?
  • 为什么 Stub 是抽象的?
  • asInterface() 有什么含义?它的实现逻辑是什么?

能力模型

一开始可能有些难以理解以上问题。不过 Binder学习指南 | Weishu’s Notes 中的一种描述方式或许能让人茅塞顿开,这里借鉴一下。如果说接口代表的是一种能力,那么不同的接口对应不同的能力。

  • IInterface 代表通用业务能力
  • IBinder 代表跨进程传输能力

当我们需要在这两种能力之间转换时,使用IInterface.asBinder()IBinder.asInterface() 即可。

编程模型

下面的类图展示了从两个基础能力衍生出来的各个类和接口,它们是基础能力的组合。图中最重要的是 StubProxy

IInterfaceIBinder 这两个基础能力出发,沿着类图从上往下,剩下的类或接口就好理解多了:它们使用继承方式(包括实现接口)或者组合方式,将”跨进程传输”和”业务能力”这两个基础能力结合起来

  • Binder 是本地对象。它通过继承方式获得了 IBinder 跨进程传输能力
  • IFakeService 代表我们的业务能力。这种能力由 .aidl 文件来定义最为简单不过

Stub 继承 Binder 并实现了 IFakeService,这表示它既具备跨进程传输能力,还具备业务能力。但它是抽象的,这意味着其业务能力尚未完全实现。我们在 Stub 的子类 FakeService 中实现了完整的业务能力。

最后是 Proxy

  • 从接口定义来看,它具备业务能力(✅),但是看不出是否有跨进程传输能力。实际上,能否跨进程传输对 Proxy 的客户来说并不重要(客户并不关心 Proxy 是什么)。
  • 另一方面,从具体实现来看,Proxy 既有远程传输能力✅又有业务能力✅。这对 Proxy 客户来说就足够了(客户更关心 Proxy 能做什么)
Proxy 跨进程传输能力 业务能力
从接口定义来看
从具体实现来看

Proxy 特别之处在于它的两种能力上都是通过组合方式得到的,它只是 mRemote 成员的代理。mRemote 正是 Binder IPC 发生关键作用的地方。

aidl 给我们生成了一个易用的编程模型,我们只需要简单地使用 StubProxy 就能完成 Binder IPC。但问题来了,我们如何拿到这里的 mRemote 以便生成 Proxy 对象?

从 Proxy 到 mRemote

远程对象的本地代理

当我们说“远程对象的本地代理”时,我们到底指的是什么?

多数时候,我们可以笼统地认为 Proxy 就是远程对象的本地代理。但准确来说,Proxy.mRemote 才是真正的远程对象的本地代理。在发生 IPC 时, Proxy.mRemote 的真正类型是 BinderProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Java proxy for a native IBinder object.
* Allocated and constructed by the native javaObjectforIBinder function. Never allocated
* directly from Java code.
*
* @hide
*/
public final class BinderProxy implements IBinder {
// See android_util_Binder.cpp for the native half of this.

private BinderProxy(long nativeData) {
mNativeData = nativeData;
}

/**
* C++ pointer to BinderProxyNativeData. That consists of strong pointers to the
* native IBinder object, and a DeathRecipientList.
*/
private final long mNativeData;
}

所以 Proxy 类其实有两层代理关系:

  • 第一层是 Proxy 对其成员变量 mRemote 的代理,这个成员是个 BinderProxy 对象
  • 第二层是 BinderProxy 对某个 native IBinder 对象的代理

如何获取 mRemote

前面提到过 asInterface()IBinder 转换到 IFakeService 代表的含义是将跨进程传输能力转换成业务能力。aidl 生成的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
abstract class Stub extends android.os.Binder implements IFakeService {
public static IFakeService asInterface(android.os.IBinder obj) {
if ((obj==null)) {
return null;
}
// 转换成 "通用业务能力"
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
// 如果没有跨进程,则优先转换成 "业务能力接口"
if (((iin!=null)&&(iin instanceof com.example.myapplication.aidl.IFakeService))) {
return ((com.example.myapplication.aidl.IFakeService)iin);
}
// 否则转换成 "远端对象的本地代理"
return new com.example.myapplication.aidl.IFakeService.Stub.Proxy(obj);
}

}

刚开始我感觉这个生成的代码似乎没什么逻辑性。但对照着这个图看,容易理解一些了:无非就是沿着左侧这棵树找到一个合适的、可以代表业务能力的节点。

  • 如果没有 IPC,那么走到位置2就行了(虽然 obj 的实际类型是 Stub),客户端拿到 IFakeService 引用就足够调用业务能力了;
  • 如果有 IPC,那么需要走到位置3:obj 参数变成了 Proxy.mRemote

接下来分两种情况考虑:

  • 应用服务中 mRemote 怎么来的
  • 系统服务中 mRemote 怎么来的,例如 ActivityManager

应用服务

ActivityThread.handleBindService() 第14行从 Service.onBind() 拿到 IBinder 对象,随后发布服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ActivityThread.java
private void handleBindService(BindServiceData data) {
CreateServiceData createData = mServicesData.get(data.token);
Service s = mServices.get(data.token);
if (DEBUG_SERVICE)
Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);
if (s != null) {
try {
data.intent.setExtrasClassLoader(s.getClassLoader());
data.intent.prepareToEnterProcess(isProtectedComponent(createData.info),
s.getAttributionSource());
try {
if (!data.rebind) {
IBinder binder = s.onBind(data.intent);
ActivityManager.getService().publishService(
data.token, data.intent, binder);
} else {
s.onRebind(data.intent);
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_REBIND, 0, 0, data.intent);
}
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
"Unable to bind to service " + s
+ " with " + data.intent + ": " + e.toString(), e);
}
}
}
}

ActiveServices.publishServiceLocked() 处理发布服务的具体过程,其中第34行执行后最终会回调 ServiceConnection.onServiceConnected(),将 IBinder 对象给到客户端。剩余工作交由 Proxy 代码完成即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
final long origId = mAm.mInjector.clearCallingIdentity();
try {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "PUBLISHING " + r
+ " " + intent + ": " + service);
if (r != null) {
Intent.FilterComparison filter
= new Intent.FilterComparison(intent);
IntentBindRecord b = r.bindings.get(filter);
if (b != null && !b.received) {
b.binder = service;
b.requested = true;
b.received = true;
ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
for (int conni = connections.size() - 1; conni >= 0; conni--) {
ArrayList<ConnectionRecord> clist = connections.valueAt(conni);
for (int i=0; i<clist.size(); i++) {
ConnectionRecord c = clist.get(i);
if (!filter.equals(c.binding.intent.intent)) {
if (DEBUG_SERVICE) Slog.v(
TAG_SERVICE, "Not publishing to: " + c);
if (DEBUG_SERVICE) Slog.v(
TAG_SERVICE, "Bound intent: " + c.binding.intent.intent);
if (DEBUG_SERVICE) Slog.v(
TAG_SERVICE, "Published intent: " + intent);
continue;
}
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Publishing to: " + c);
// If what the client try to start/connect was an alias, then we need to
// pass the alias component name instead to the client.
final ComponentName clientSideComponentName =
c.aliasComponent != null ? c.aliasComponent : r.name;
try {
c.conn.connected(clientSideComponentName, service, false);
} catch (Exception e) {
Slog.w(TAG, "Failure sending service " + r.shortInstanceName
+ " to connection " + c.conn.asBinder()
+ " (in " + c.binding.client.processName + ")", e);
}
}
}
}

serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false,
!Flags.serviceBindingOomAdjPolicy() || r.wasOomAdjUpdated()
? OOM_ADJ_REASON_EXECUTING_SERVICE : OOM_ADJ_REASON_NONE);
}
} finally {
mAm.mInjector.restoreCallingIdentity(origId);
}
}

系统服务

Android 系统服务中也大量使用类似 Proxy.mRemote 这种套路来做 IPC。以 ActivityManager 为例,其背后其实是 IPC + AMS。ActivityManager 的 mRemote 怎么来的?

看第15行。这里获取 IBinder 对象作为 mRemote 看起来非常直观,只是一个简单的 ServiceManager.getService() 调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

/**
* @hide
*/
@UnsupportedAppUsage
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}

@UnsupportedAppUsage
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};

public boolean isBackgroundRestricted() {
try {
return getService().isBackgroundRestricted(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

最后,ServiceManager 自己也是一个 Service,它也需要一个类似的 mRemote。见第10行,直接调用 BinderInternal.getContextObject() 获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public final class ServiceManager {

private static IServiceManager getIServiceManager() {
if (sServiceManager != null) {
return sServiceManager;
}

// Find the service manager
sServiceManager = ServiceManagerNative
.asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
return sServiceManager;
}

public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(rawGetService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}

private static IBinder rawGetService(String name) throws RemoteException {
final long start = sStatLogger.getTime();

final IBinder binder =
getIServiceManager().getService2(name).getServiceWithMetadata().service;
return binder;
}
}

总结

先从 aidl 出发,讨论了它解决的主要问题。aidl 解决的一个主要问题是给我们提供了一个易用的编程模型。这个模型中最重要是 StubProxy。它们都是”业务能力”和”跨进程能力”的组合。

不过 aidl 没有解决该模型中重要参数 mRemote 的输入问题(即 Proxy.mRemote 是怎么来的)。接下来对这个问题展开了讨论。

对于应用服务,Android SDK 提供对应接口保证 bindService() 调用成功后客户端可以获取到 IBinder 对象作为 Proxy.mRemote

对于系统服务,通过 BinderInternal.getContextObject() 方法获取 IBinder 对象作为 Proxy.mRemote