Android IPC机制

IPC(Inter-Process Communication),在日常开发中大多数场景下都用不到,而且我们在开发中就算用到了进程间通信,基本上都只是专注于通信双方接口的设计,对于底层实现的细节并不是很了解。稍微看下Android Framework层的源码,也会发现随处可见IPC的身影。所以,有必要稍稍了解下其底层实现的原理,有助于加深我们对进程间通信的认识。

AndroidIPC概述

Android采用Binder作为IPC机制,Binder由于性能较高,采用了内存映射机制(mmap方法),内存拷贝只需一次,传统的linux进程间通信(管道、消息队列、共享内存、
信号量、Socket)最少也需要两次。

内存映射的实现过程主要是通过Linux系统下的系统调用函数:mmap()
该函数的作用 = 创建虚拟内存区域 + 与共享对象建立映射关系

用户空间:进程间相互独立

内核空间:所有进程共享

所以一个用户想给另一个用户发送数据必须通过内核空间完成,一般是将用户空间的数据拷贝至内核空间,然后另一个用户再将数据从内核空间拷贝至用户空间

用户1->拷贝->内核->拷贝->用户

而内存映射只需要一次拷贝:
用户1->拷贝->内核->映射->用户2

暂且这么认为吧

Binder跨进程模型整体架构:

Client

客户端,服务的使用者

Server

服务端,服务的提供者

ServiceManager

相当于一个路由器,所有的Service都在ServiceManager处登记和查询,将字符串的Binder名字转化为Client中对该Binder的引用。

Binder驱动

一种虚拟设备驱动,连接client,server,ServiceManager的桥梁。

Binder驱动的作用:

  1. 传递进程间的数据,通过内存映射
  2. 实现线程控制,采用Binder驱动本身的线程池,并由Binder驱动自身进行管理。

Binder驱动持有每个Server进程在内核空间中的binder实体,并给client进程提供binder实体的引用。

说明

client和server以及ServiceManager之间的交互都要通过Binder。Client和Server属于应用层,ServiceManager和Binder属于Android基础架构。

Binder请求的线程管理:

  1. 注意server提供的服务方法并不是运行在主线程的。不过其本身的创建过程依旧在主线程。Server进程会创建很多线程来处理Binder请求。
  2. Binder模型的线程管理 采用Binder驱动的线程池,并由Binder驱动自身进行管理
  3. 一个进程的Binder线程数默认最大是16,超过的请求会被阻塞等待空闲的Binder线程。

具体应用流程

注册服务

server进程通过Binder驱动先在ServiceManager注册服务

代码实现

Server进程 创建 一个 Binder 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Nullable
@Override
public IBinder onBind(Intent intent) {
/*Stub是生成的代码,抽象类,也可以自己写,继承了Binder类以及实现了自己定义的服务接口*/
IMyAidlInterface.Stub stub = new IMyAidlInterface.Stub() {
@Override
public int add(int a, int b) throws RemoteException {
long id = Thread.currentThread().getId();
int sum = a + b;
return sum;
}
@Override
public int sub(int a, int b) throws RemoteException {
int sub = a - b;
return sub;
}
};
return stub;
}

获取服务

binder获取服务流程.png
这里有一个querylocal的操作,如果client和server处于同一个进程,则直接返回server服务,不用跨进程。

使用服务

使用BinderProxy的代理方法,将方法code,方法参数写入parcel对象中,调用transact方法将数据发送到Binder驱动,Service端收到数据,解包后进行处理,写入返回结果,Binder驱动根据代理对象沿原路将结果返回,并通知Client进程获取返回结果,通过代理对象接收结果(之前被挂起的线程被唤醒)。

client端发起请求之后线程会被挂起,返回结果后会被唤醒。

具体过程可以查看aidl生成的代码。

相关类和接口

IBinder(定义了进程间通信的接口规范),

Binder(实现了IBinder接口),

IInterface(自己定义的aidl接口生成的代码必须继承这个接口),

Stub(生成的代码的静态内部类,继承于Binder和自己定义的接口,注意asInterface方法是核心,这个方法用来判断到底应该直接返回Binder还是BinderProxy,onTransact用于解包和执行服务的方法),

Proxy(Stub的静态内部类,Client端使用,用于代理服务端的Binder实现类,使用代理方法时,写入所有参数后,最终会调用remote.transact方法进行提交,之后就等待返回结果,线程挂起)

in,out,inout

自定义参数类型时,需要指定数据流向,in代表从client到server,out代表从server到client,inout表示双向流动。

定向Tag为in时,server端会反序列化client传过来的参数,执行完方法后,不会将参数再次序列化传回client端。

定向Tag为out时,client端不会将参数序列化传给server,而是由server自己实例化一个初始状态的参数,方法执行完之后,server会将自己实例化的参数序列化之后传回client端,client端利用自己传入的参数接收server传回的序列化数据。这里,client端传入的参数只是起了一个接收器的作用。

定向tag为inout时,client会将参数序列化传给server,同时server执行完方法后,也会将参数序列化传给client,client端利用自己传入的参数接收server传回的序列化数据。所以这里的数据流动是双向的。

注意事项

当使用RemoteCallback方式交互时,需要注意callback之前需要调用RemoteCallbackList.beginBroadcast(否则获取不到Callback对象),callback结束后要调用RemoteCallbackList.finishBroadcast。client传给Server的callback对象实际上也是一个binder,所有服务端才能回调。

Binder会为每个参与通信的进程分配内存,跨进程传输的数据是存储在Binder分配的内存中的,每个进程只能访问自己的内存,内存不超过1M(非ServiceManager),所以跨进程数据量不能太大。

文章目录
  1. 1. AndroidIPC概述
  2. 2. Binder跨进程模型整体架构:
    1. 2.1. Client
    2. 2.2. Server
    3. 2.3. ServiceManager
    4. 2.4. Binder驱动
    5. 2.5. 说明
  3. 3. 具体应用流程
    1. 3.1. 注册服务
      1. 3.1.1. 代码实现
    2. 3.2. 获取服务
    3. 3.3. 使用服务
  4. 4. 相关类和接口
  5. 5. in,out,inout
  6. 6. 注意事项
|