Android 进程间通信

什么是进程间通信

百度百科中对进程间通信的解释:进程间通信(IPC,Interprocess communication)是一组编程接口。能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息,
简单来说就是让我们能够在不同进程间,进行数据的交互。

为什么要用进程间通信

在理解了什么是进程间通信,就有一个值得思考的问题。为什么要有进程间通信,放在一起不行么?在回答这个问题之前,想象一下,所有的用户进程都认为是一个小房子,原本大家都住在自己的房间里,没有了障碍,我就能去你家,随意的拿走你家的任何东西,

操作系统为了阻挡你去串门,给你设置了一个障碍,让你不能够所以的访问其他进程的信息,这个障碍在操作系统中有一个专门的名字: 进程隔离,其目的就是为保护操作系统中进程互不干扰。

那么为什么就一定需要进程间通信呢?设想一下,大家都住在一个楼里面,难免会遇到家里没酱油了,就需要去隔壁家借一点酱油,操作系统就不能不让你去借,这就是所谓的进程间通信。

进程间通信有哪些方式

ContentProvider为存储和获取数据了提供统一的接口,它可以在不同的应用程序之间共享数据,本身就是适合进程间通信的。ContentProvider底层实现也是Binder,但是使用起来比AIDL要容易许多。系统中很多操作都采用了ContentProvider,例如通讯录,音视频等,这些操作本身就是跨进程进行通信。

  • 文件共享

两个进程通过读写同一个文件来进行数据共享,共享的文件可以是文本、XML、JOSN。文件共享适用于对数据同步要求不高的进程间通信。

  • 序列化

序列化指的是Serializable/Parcelable,Serializable是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。Parcelable接口是Android中的序列化方式,更适合在Android平台上使用,用起来比较麻烦,效率很高。

  • Bundle
    Bundle实现了Parcelable接口,所以它可以方便的在不同的进程间传输。Acitivity、Service、Receiver都是在Intent中通过Bundle来进行数据传递。

上面的方式。除了文件共享,看一下其他的几种方法,都是使用Binder去实现进程间通信。

Binder是什么

Binder 前生是Be Inc公司开发的OpenBinder框架,后来后来OpenBinder的作者Dianne Hackborn加入了Google公司,并负责Android平台的开发工作,顺便把这项技术也带进了Android。Binder本身是Android上的进程间通信技术,

Android平台绝大部分的跨进程都是通过Binder完成的.而他也承载着极其重要的作用。

为什么要用Binder

回答为什么要用Binder 之前,其实还有一个问题,除了Binder 我们还有哪些选择?

众说周知,Android是基于Linux内核的,Linux 本身就提供了很多IPC机制,Linux IPC通信的过程,进程A通过copy_from_user的函数将数据拷贝到内核空间的缓冲区,内核空间通过copy_to_user的函数将数据在拷贝给进程B,一共经历了2次数据拷贝

👆Linux IPC 通信过程

既然已经可以通过Linux这种IPC机制去做进程间通信,为什么还要搞出Binder机制呢。先放上一个Binder的流程图,与Linux 传统的IPC 通信进行对比

👆Binder 通信过程

性能

binder,通过一次copy将数据复制到内核空间的缓冲区,然后内核空间通过内存映射(mmap)的方式,将数据映射到数据接收缓存区,在以同样的方式将数据接收缓存区的数据映射到进程B,相比Linux 少了一次copy 的过程,其性能也就更高了。相比于内存共享,多了一次拷贝

IPC方式数据copy次数
共享内存0
Binder1
Linux 传统方式2

其次,Linux方式 将数据冲内核空间复制到进程B的过程中,不知道其需要多大的空间,所以需要竟可能申请更大的内存去使用。会造成一定的内存浪费。

稳定性

Binder 基于 C/S 架构,架构清晰,职责明确,其稳定性完全高于全依赖于内存共享的方式,共享内存没有分层,难以控制,并发同步访问临界资源时,可能还会产生死锁

安全性

传统的 IPC 接收方无法获得对方可靠的进程用户ID/进程ID(UID/PID),从而无法鉴别对方身份,Android 为每个安装好的 APP 分配了自己的 UID,故而进程的 UID 是鉴别进程身份的重要标志

总结

从性能上,内存共享肯定是最好的。Binder 其次,最差的是Linux 传统的方式。从稳定性上和安全性分析,Binder 最好。综合考虑,Binder 就成为了Android上跨进程通讯的首选方式

Binder 如何通信的

架构分析

在分析Binder 之前,要知道Binder 是基于C/S 架构。即 Client/Servcie ,Service 需要先注册到ServiceManager. Client 在通过ServiceManager 去获取服务,从而完成连接关系。如下图所示架构

但是发现,ServiceManager属于一个单独的进程,Client/Service和ServiceManager都存在着进程隔离,如此,Servcie注册到ServiceManager进程依然需要Binder去完成,所以最终的进程间通信应该如下所示

如何发送数据

借用之前写 使用AIDL进程间通信 中的示例 传递一个int 类型的参数观察其传递过程,了解去传递数据的过程是如果进行的调用自己创建的方法。将对象传入

JAVA层

先看到JAVA层。执行代理类,数据会被包装成Parcel对象,(注意 Parcel 是 java 的 Parcel 对象).然后执行BinderProxy代理对象,最终执行native 方法

1
2
3
//执行自己写的接口方法
var info = 100
aidl?.getAge(info)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//会执行代理中的updateBookInOut方法
private static class Proxy implements com.starot.lib_intent.aidl.ITest{
private android.os.IBinder mRemote;
@Override public com.starot.lib_intent.aidl.BookInfo updateBookOut(com.starot.lib_intent.aidl.BookInfo book) throws android.os.RemoteException
{
//创建了java 的 Parcel 对象
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();

//token 示例中的为 `com.starot.lib_intent.aidl.ITest` 可以看到是自己写的AIDL对象的路劲
_data.writeInterfaceToken(DESCRIPTOR);
//设置参数到 Parcel中
_data.writeInt(age);
//会执行到 BinderProxy.transact 方法
boolean _status = mRemote.transact(Stub.TRANSACTION_updateBookOut, _data, _reply, 0);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
//BinderProxy
public final class BinderProxy implements IBinder {
/**
* Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//最终会执行native 方法
return transactNative(code, data, reply, flags);
}
}

数据继续往下层传递。此时就进入Navite层,在这里原先的 java 层 Parcel 就转成了 native 的 Parcel。通过BpBinder将数据传入到IPCThreadState中进行处理,处理的方式是将原先native的数据通过memcpy进行复制,保存在mOut变量中。这个mOut 也是一个Parcel对象。最终走到 ioctl 方法 调用驱动层的代码.将数据进行内存映射。

frameworks/base/core/jni/android_util_Binder.cpp

1
2
3
4
5
6
7
8
9
10
11
12
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
//对象转换
Parcel* data = parcelForJavaObject(env, dataObj);
Parcel* reply = parcelForJavaObject(env, replyObj);
//创建IBinder
IBinder* target = getBPNativeData(env, obj)->mObject.get();
//执行BpBinder.transact
status_t err = target->transact(code, *data, reply, flags);
return JNI_FALSE;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
status_t BpBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
//状态活跃
if (mAlive) {
//使用IPCThreadState.transact方法将数据传递
status_t status = IPCThreadState::self()->transact(
mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
status_t IPCThreadState::transact(int32_t handle,
uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags)
{
//写入数据,将数据写入到内存当中,不会去触发将数据给binder驱动
err = writeTransactionData(BC_TRANSACTION_SG, flags, handle, code, data, nullptr);

//等待数据 根据不同状态retrun 不同
err = waitForResponse(reply);
err = waitForResponse(&fakeReply);
err = waitForResponse(nullptr, nullptr);
return err;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
//申明了 binder_transaction_data_sg
binder_transaction_data_sg tr_sg;
//将数据保存在 tr_sg 中
tr_sg.transaction_data.data_size = data.ipcDataSize();
tr_sg.transaction_data.data.ptr.buffer = data.ipcData();
tr_sg.transaction_data.offsets_size = data.ipcObjectsCount()*sizeof(binder_size_t);
tr_sg.transaction_data.data.ptr.offsets = data.ipcObjects();
tr_sg.buffers_size = data.ipcBufferSize();

//这里的mOut 是定义的一个Parcel对象。
//他主要是将我们要发送的数据复制一份拷贝到 mOut 当中, mOut 也是定义的一个类Parcel
mOut.write(&tr_sg, sizeof(tr_sg));

return NO_ERROR;
}
1
2
3
4
5
6
7
8
9
10
status_t Parcel::write(const void* data, size_t len)
{
void* const d = writeInplace(len);
if (d) {
//这里是将数据内存复制一份给到 mOut
memcpy(d, data, len);
return NO_ERROR;
}
return mError;
}

👆system/libhwbinder/Parcel.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
while (1) {
//执行talkWithDriver 方法
if ((err=talkWithDriver()) < NO_ERROR) break;
//获取到返回的对象
reply->ipcSetDataReference(
reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),
tr.data_size,
reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),
tr.offsets_size/sizeof(binder_size_t),
freeBuffer, this);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
status_t IPCThreadState::talkWithDriver(bool doReceive)
{
//定义数据
binder_write_read bwr;

//将mOut的数据写入到 bwr
bwr.write_size = outAvail;
bwr.write_buffer = (uintptr_t)mOut.data();


do {
#if defined(__ANDROID__)
//使用 ioctl 将数据传递出去
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
else
err = -errno;
#else
err = INVALID_OPERATION;
#endif
} while (err == -EINTR);

}

到这里为止,数据已经从java层传到了binder驱动。

内核层

Binder 驱动 使用mmap 的方式 内存映射,中间过程暂时没有研究出来,回头在细看一下

总结

通过一步一步的分析,可以看到数据被封装成驱动能够识别的数据类型,然后通过 ioctl 方法 将数据传入到驱动,在Binder驱动中,通过mmap 的方式,对数据进行内存映射,从而完成数据从java层到内核的过程。下面是发送流程。

如何接受数据

Java层

这里我们反推,先从Java层入手,获取client传过来的数据,往上看。一步一步分析Java层的数据哪里是源头

1
2
3
4
5
6
//step1:最终是在Service 自定义的Binder 接口中获取数据
class MyBinder : ITest.Stub() {
override fun getAge(age: Int): Int {
return 10001
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//step2:Binder继承自 Stub 在 onTransact 回调中获取数据
public static abstract class Stub extends android.os.Binder implements com.starot.lib_intent.aidl.ITest
{
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code){
case TRANSACTION_getAge:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _result = this.getAge(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
}
}
1
2
3
4
5
6
7
//step3:onTransact方法是在Binder的 execTransactInternal 返回出去
private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,
int callingUid) {
res = onTransact(code, data, reply, flags);
return res;
}
`
1
2
3
4
5
6
7
//step4:对与Java层 最开始获取的方法是Binder 的 execTransact 方法
// Entry point from android_util_Binder.cpp's onTransact
@UnsupportedAppUsage
private boolean execTransact(int code, long dataObj, long replyObj,
int flags) {
return execTransactInternal(code, dataObj, replyObj, flags, callingUid);
}

Native层

通过注释Entry point from android_util_Binder.cpp's onTransact可以知道 Java Binder.java 的 execTransact 方法是在 frameworks/base/core/jni/android_util_Binder.cpp中被触发

1
2
3
4
5
6
7
8
9
10
11
class JavaBBinder : public BBinder
{
status_t onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) override
{
//执行java Binder execTransact 方法
jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int int_register_android_os_Binder(JNIEnv* env)
{
jclass clazz = FindClassOrDie(env, kBinderPathName);

gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
//注意这里 在 register 的时候,就将execTransact 方法保存在mExecTransact中
gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor",
"()Ljava/lang/String;");
gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");

return RegisterMethodsOrDie(
env, kBinderPathName,
gBinderMethods, NELEM(gBinderMethods));
}

总结

通过倒退,能够知道在Binder虚拟机完成工作以后,执行到了 BBinder 的 onTransact 方法,然后到了 java Binder 的 execTransact 方法 最终执行到了 Stub 的 onTransact 方法。

总结

通过AIDL的方式我们能够看到数据如果从Client进程到Service进程。

最后

Binder 是个很大的东西。其中很多部分还没有看懂。以后还是需要在继续看一下。争取彻底搞懂其中每个部分的工作流程。上文的分析,完全是通过数据传递的过程分析。其实还有数据的校验,MMAP 的过程。和Binder驱动交互的过程。等很多细节。

参考