一、IPC通信总览
IPC
即 Inter-Process Communication
(进程间通信)。
Android
基于 Linux
,而 Linux
出于安全考虑,不同进程间不能直接操作对方的数据,这叫做“进程隔离”。
进程隔离是为保护操作系统中进程互不干扰而设计的一组不同硬件和软件的技术。进程的隔离实现,使用了虚拟地址空间。进程A的虚拟地址和进程B的虚拟地址不同,这样就防止进程A将数据信息写入进程B
跨进程通信要求:把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。然后,返回值将沿相反方向传输回来。
在Android
中,常用的进程通信机制有:文件、AIDL
(基于Binder
)、Binder
、Messenger
(基于Binder
)、ContentProvider
(基于Binder
)、Socket
。
这里重点看一下Binder
和AIDL
二、Binder解析
2.1 Binder理解
Binder
是 Android
为我们提供的 IPC
方式。
从JAVA层面理解,Binder
其实就是一个实现了IBinder
接口的类
从Linux内核层理解,可以将 Binder
理解为一个虚拟的物理设备,而这个设备的启动、进程间数据的传输都要依赖于一个叫做 Binder Driver
(Binder
驱动)的东西。
从Android
应用层理解,Binder
是客户端和服务端通信的媒介。
知识概念
这里先理解一下用户空间等相关概念。
-
用户空间/内核空间
Linux Kernel
是操作系统的核心,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。对于
Kernel
这种高安全级别的东西,不容许其它的应用程序随便调用或访问的,需要对Kernel
提供一定的保护机制,来告知应用程序哪些资源可访问,哪些拒绝访问。将Kernel
和上层的应用程序抽像地隔离开,分为内核空间和用户空间 -
系统调用/内核态/用户态
用户空间有时需要访问内核的资源,例如应用程序访问文件。
用户空间访问内核空间的唯一方式是系统调用。通过该接口,所有的资源访问都是在内核的控制下执行,以免导致对用户程序对系统资源的越权访问,保障系统的安全和稳定性。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,称进程处于内核态。当进程执行用户自己的代码时,称为用户态。
-
内核模块/驱动
通过系统调用,用户空间可以访问内核空间。如果一个用户空间想和另一个用户空间通信,可以让操作系统内核添加支持,例如
Socket
、管道等。Binder
不是Linux
内核的一部分,其通过Linux
的动态可加载内核模块(LKM
)机制解决该问题。模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。
Android
系统可以通过添加一个内核模块运行在内核空间,用户进程之间通过这个模块为桥梁完成通信。Android
中,这个运行在内核空间,负责各个用户进程通过Binder
通信的内核模块叫做Binder
驱动。驱动就是操作硬件的接口。为了支持
Binder
通信过程,Binder
使用了一种“硬件”,因此这个模块被称之为驱动。
2.2 Binder 通信模型
对于跨进程通信的双方,一般称之为Server
进程(服务端)、Client
进程(客户端)。
日常的电话通信过程,完成通信过程需要两个角色:通信录和基站。对于Binder
,Binder
驱动功能类似基站,而通讯录的功能由ServiceManager
实现。
通信步骤主要如下所示:
-
SM
建立(建立通信录)首先有一个进程向驱动提出申请为
SM
;驱动同意之后,SM
进程负责管理Service
-
各个
Server
向SM
注册(完善通信录)每个
Server
端进程启动之后,向SM
报告名字地址,这样SM
就建立了一张通信录表 -
地址查询
Client
想要与Server
通信,首先询问SM
,SM
返回Server
的通信地址等信息。Client
收到后,利用该地址完成通信
2.3 Binder原理
通过Binder
通信模型,知道通信过程有四个角色:Client
、Server
、ServiceManager
、driver
。看一下Client
和Server
实际是如何完成通信。
内核可以访问A
和B
的所有数据,所以最简单的方式是通过内核做中转。
假设进程A
要给进程B
发送数据,先把A
的数据copy
到内核空间,然后把内核空间对应的数据copy
到B
就完成了。用户空间要操作内核空间,需要通过系统调用,这里提供两个系统调用:copy_from_user
, copy_to_user
。
上述这种IPC
通信方式存在两个问题:
-
性能低下
一次数据传递需要经历:内存缓存区 --> 内核缓存区 --> 内存缓存区,需要 2 次数据拷贝
-
时间与空间资源浪费
接收数据的缓存区由数据接收进程提供,但是接收进程并不知道需要多大的空间来存放将要传递过来的数据,因此只能开辟尽可能大的内存空间或者先调用
API
接收消息头来获取消息体的大小,这两种做法不是浪费空间就是浪费时间。
内存映射
所有的数据都存储在物理内存中,而进程访问内存只能通过虚拟地址,如果想成功访问必须得有个前提:虚拟地址和物理内存之间建立映射关系。
实现前述的映射关系,进程就可以采用指针的方式来读写操作这一段内存,进而完成对文件(或者其它被映射的对象)的操作,而不必再调用 read/write
等系统调用函数了。内存映射的该特点可以减少数据拷贝的次数,实现用户空间和内核空间的高效互动。
若想让A
进程通过(用户空间)虚拟地址访问到B
进程中的数据,最高效的方式就是修改A/B
进程中某些虚拟地址的PTE
,使得这些虚拟地址映射到同一片物理区域。这样就不存在任何拷贝,因此数据在物理空间中也只有一份。
共享内存虽然高效,但是由于物理内存只有一份,不得不考虑各种同步问题。内存拷贝的方法可以保证不同进程都拥有一块属于自己的数据区域,该区域不用考虑进程间的数据同步问题。
Android
中的Binder
是基于速度和安全性考虑后的选择。
Binder IPC
基于内存映射(mmap
)来实现的,但是 mmap()
通常是用在有物理介质的文件系统上的。
比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间);通常在这种场景下
mmap()
就能发挥作用。通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O
读写,提高文件读取效率。
Binder
并不存在物理介质,因此 Binder
驱动使用 mmap()
并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
Binder
通信原理
一次完整的Binder
通信过程大致如下:
- 首先
Binder
驱动在内核空间创建一个数据接收缓存区 - 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系;
- 发送方进程通过系统调用
copyfromuser()
将数据copy
到内核中的内核缓存区。由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
结合前述介绍的Binder
通信模型,则Binder
的通信过程为:
- 首先,一个进程使用
BINDER SET CONTEXT_MGR
命令通过Binder
驱动将自己注册成为ServiceManager
; Server
通过驱动向ServiceManager
中注册Binder
(Server
中的Binder
实体),表明可以对外提供服务。驱动为这个Binder
创建位于内核中的实体节点以及ServiceManager
对实体的引用,将名字以及新建的引用打包传给ServiceManager
,ServiceManger
将其填入查找表Client
通过名字,在Binder
驱动的帮助下从ServiceManager
中获取到对Binder
实体的引用,通过这个引用就能实现和Server
进程的通信。
2.4 Binder代理模式
Client
、Server
借助 Binder
驱动完成跨进程通信的实现机制。
如果**A
进程想要 B
进程中某个对象(object
)要如何实现**?二者分属不同的进程,无法直接使用。
当 A
进程想要获取 B
进程中的 object
时,驱动并不会真的把 object
返回给 A
,而是返回了一个跟 object
看起来一模一样的代理对象 objectProxy
。这个 objectProxy
具有和 object
一模一样的方法,但是这些方法并没有 B
进程中 object
对象那些方法的能力,这些方法只需要把请求参数交给驱动即可。对于 A
进程来说和直接调用 object
中的方法是一样的。
假设Client
进程想要调用Server
进程的object
对象的一个方法add
;对于这个跨进程通信过程,我们来看看Binder
机制是如何做的
- 首先,
Server
进程向SM
注册,SM
建立对应表 - 然后
Client
向SM
查询Server
。进程之间通信的数据都会经过运行在内核空间里面的驱动,驱动在数据流过的时候做了一点手脚。它并不会给Client
进程返回一个真正的object
对象,而是返回一个看起来跟object
一模一样的代理对象objectProxy
,这个objectProxy
也有一个add
方法,但是这个add
方法没有Server
进程里面object
对象的add
方法那个能力;objectProxy
的add
只是一个傀儡,它唯一做的事情就是把参数包装然后交给驱动 Client
获取到objectProxy
对象,然后调用add
方法。- 驱动收到该消息,发现是这个
objectProxy
,查表发现其真正要访问的是object
对象的add()
方法。于是驱动通知Server
进程,调用其object
对象的add()
方法,然后将其结果返回给驱动。 Server
进程收到该消息,执行add()
操作后返回驱动,驱动将结果返回给Client
进程,完成整个通信过程。
Client
进程只不过是持有了Server
端的代理;代理对象协助驱动完成了跨进程通信。
结合前述内容,对Binder
进行二次理解:
-
Binder
指的是一种通信机制。我们说AIDL
使用Binder
进行通信,指的就是Binder
这种IPC
机制。 -
对于
Server
进程来说,Binder
指的是**Binder
本地对象** -
对于
Client
来说,Binder
指的是**Binder
代理对象**,它只是**Binder
本地对象**的一个远程代理。对这个
Binder
代理对象的操作,会通过驱动最终转发到Binder
本地对象上去完成。对于一个拥有Binder
对象的使用者而言,它无须关心这是一个Binder
代理对象还是Binder
本地对象。对于代理对象的操作和对本地对象的操作对它来说没有区别。 -
对于传输过程而言,
Binder
是可以进行跨进程传递的对象。Binder
驱动会对具有跨进程传递能力的对象做特殊处理,自动完成代理对象和本地对象的转换。
Server
进程里面的Binder
对象指的是Binder
本地对象,Client
里面的对象值得是Binder
代理对象。
在Binder
对象进行跨进程传递的时候,Binder
驱动会自动完成这两种类型的转换,因此Binder
驱动必然保存了每一个跨越进程的Binder
对象的相关信息。在驱动中,Binder
本地对象的代表是一个叫做binder_node
的数据结构,Binder
代理对象是用binder_ref
代表的,有的地方把Binder
本地对象直接称作Binder
实体,把Binder
代理对象直接称作Binder
引用(句柄),其实指的是Binder
对象在驱动里面的表现形式。
三、AIDL 解析
实际开发时,实现进程间通信的方法一般是通过AIDL
。当我们定义好 AIDL
文件,在编译时编译器会帮我们生成代码实现 IPC
通信。
这里通过借助 AIDL
编译以后的代码能帮助我们进一步理解 Binder IPC
的通信原理。
AIDL
(Android Interface Definition Language
) 是一种IDL
语言,用于生成可以在Android
设备上两个进程之间进行进程间通信(interprocess communication,
IPC
)的代码。
仅仅是跨进程通信,还可以选择BraodcastReceiver
,Messenger
等,但是前者占用系统资源较多,后者无法并发执行,在多线程下不适用,此时就可以用AIDL
实现。
3.1 基本语法
AIDL
文件的后缀是 .aidl
,存放在名为aidl
文件夹(与java
文件夹同级)。
默认情况下AIDL
支持下列数据类型:
-
基础数据类型:
byte
、char
、int
、long
、float
、double
、boolean
,其中不支持short
类型 -
支持
String
和CharSequence
-
实现了
Parcelable
接口的数据类型 -
List
与Map
类型,其承载的数据必须是AIDL
支持的类型
定向tag
除以上数据类型外,其他类型在使用之前必须通过import
导包,即使目标与当前正在编写的 .aidl
文件在同一个包下。
所有非基本数据类型的参数在传递时需要指定一个方向tag
来指明数据的流向,可以是in
、out
、或inout
,基本数据类型、String
、CharSequence
默认且只能为in
。这三个修饰符被称为定向tag
,用于在跨进程通信时指定数据流向。
in
:表示数据只能由客户端流向服务端out
:表示数据只能由服务端流向客户端inout
:表示数据可在服务端与客户端之间双向流通
AIDL
中的核心类:
-
IBinder
是一个接口,代表了一种跨进程通信的能力。只要实现了这个接口,就能将这个对象进行传进程传输。这是驱动底层支持的;在跨进程数据流经驱动的时候,驱动会识别
IBinder
类型的数据,从而自动完成不同进程Binder
本地对象以及Binder
代理对象的转换 -
IInterface
代表
Server
进程具备什么能力。(能提供哪些方法,其实对应的就是AIDL
文件中定义的接口) -
Binder
Java
层的Binder
类,代表的其实就是Binder
本地对象。BinderProxy
类是Binder
类的一个内部类,它代表远程进程的Binder
对象的本地代理;这两个类都继承自IBinder
, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder
驱动会自动完成这两个对象的转换。 -
Stub
AIDL
的时候,编译工具会给我们生成一个名为Stub
的静态内部类。这个类继承了Binder
, 说明它是一个Binder
本地对象。它实现了IInterface
接口,表明它具有Server
承诺给Client
的能力。Stub
是一个抽象类,具体的IInterface
的相关实现需要开发者自己实现。
3.2 基本使用
整个AIDL
通信分为客户端与服务端两个方面:
-
服务端
创建一个
Service
监听客户端连接请求。创建一个AIDL
文件,在该文件中声明暴露给客户端的接口,然后在Service
中实现这个AIDL
接口 -
客户端
绑定服务端
Service
,将服务端返回的IBinder
对象转成AIDL
接口所属类型,调用AIDL
中方法。
服务端执行步骤:
1、定义传输数据
首先确定传输的数据。假设这里要传输的数据是User
类对象,该对象实现Parcelabel
@Parcelize
data class User(
var age: Int,
var name: String
):Parcelable
如果在AIDL
中用到了自定义的Parcelable
对象,必须新建一个和它同名的AIDL
文件,在其中声明它为Parcelable
类型。示例用到了User
类,所以声明一个User.aidl
// User.aidl
package com.zhewen.aidlserverstudy;
parcelable User;
2、创建AIDL
文件,声明暴露的接口
定义完传输的数据类,需要创建AIDL
文件,在该文件中声明暴露给客户端的接口
package com.zhewen.aidlserverstudy;
import com.zhewen.aidlServerstudy.User;
interface ICommandServer {
int add(int value1, int value2);
List<User> addUser(in User user);
}
自定义
Parcelabel
对象和AIDL
对象必须要显示import
进来,不管是否和当前AIDL
文件在一个包
声明完暴露给客户端的接口,用编译工具进行编译,则可得到对应的Java
实现类
//ICommandServer.java
package com.zhewen.aidlserverstudy;
public interface ICommandServer extends android.os.IInterface
{
/** Default implementation for ICommandServer. */
public static class Default implements com.zhewen.aidlserverstudy.ICommandServer
{
@Override public int add(int value1, int value2) throws android.os.RemoteException
{
return 0;
}
@Override public java.util.List<com.zhewen.aidlserverstudy.User> addUser(com.zhewen.aidlserverstudy.User user) throws android.os.RemoteException
{
return null;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.zhewen.aidlserverstudy.ICommandServer
{
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.zhewen.aidlserverstudy.ICommandServer interface,
* generating a proxy if needed.
*/
public static com.zhewen.aidlserverstudy.ICommandServer asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.zhewen.aidlserverstudy.ICommandServer))) {
return ((com.zhewen.aidlserverstudy.ICommandServer)iin);
}
return new com.zhewen.aidlserverstudy.ICommandServer.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@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 INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
}
switch (code)
{
case TRANSACTION_add:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_addUser:
{
data.enforceInterface(descriptor);
com.zhewen.aidlserverstudy.User _arg0;
if ((0!=data.readInt())) {
_arg0 = com.zhewen.aidlserverstudy.User.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
java.util.List<com.zhewen.aidlserverstudy.User> _result = this.addUser(_arg0);
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.zhewen.aidlserverstudy.ICommandServer
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public int add(int value1, int value2) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(value1);
_data.writeInt(value2);
boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
if (!_status) {
if (getDefaultImpl() != null) {
return getDefaultImpl().add(value1, value2);
}
}
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public java.util.List<com.zhewen.aidlserverstudy.User> addUser(com.zhewen.aidlserverstudy.User user) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.zhewen.aidlserverstudy.User> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((user!=null)) {
_data.writeInt(1);
user.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
if (!_status) {
if (getDefaultImpl() != null) {
return getDefaultImpl().addUser(user);
}
}
_reply.readException();
_result = _reply.createTypedArrayList(com.zhewen.aidlserverstudy.User.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.zhewen.aidlserverstudy.ICommandServer sDefaultImpl;
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.zhewen.aidlserverstudy.ICommandServer impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.zhewen.aidlserverstudy.ICommandServer getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public static final java.lang.String DESCRIPTOR = "com.zhewen.aidlserverstudy.ICommandServer";
public int add(int value1, int value2) throws android.os.RemoteException;
public java.util.List<com.zhewen.aidlserverstudy.User> addUser(com.zhewen.aidlserverstudy.User user) throws android.os.RemoteException;
}
系统生成该文件后,只需继承其中的Stub
抽象类,实现相关方法,在Service
的onBind
里返回就实现了AIDL
。
具体分析一下系统生成的这个Java
文件。
public static abstract class Stub extends android.os.Binder implements com.zhewen.aidlserverstudy.ICommandServer {
//......
private static class Proxy implements com.zhewen.aidlserverstudy.ICommandServer{
//......
}
}
Stub
类继承自Binder
,即这个Stub
是一个Binder
本地对象,其实现了ICommandServer
接口。ICommandServer
接口即我们定义的暴露给客户端的接口,因此其携带客户端需要的能力。
Stub
内部有一个内部类Proxy
,即Binder
代理对象
-
asInterface()
Client
端创建服务端连接时,调用bindService
时需要创建一个ServiceConnection
对象作为入参。在ServiceConnection
的回调方法onServiceConnected
中会通过asInterface(android.os.IBinder obj)
拿到ICommandServer
对象/** * Cast an IBinder object into an com.zhewen.aidlserverstudy.ICommandServer interface, * generating a proxy if needed. */ public static com.zhewen.aidlserverstudy.ICommandServer asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } //查找本地Binder对象 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.zhewen.aidlserverstudy.ICommandServer))) { return ((com.zhewen.aidlserverstudy.ICommandServer)iin); } //创建Binder代理对象 return new com.zhewen.aidlserverstudy.ICommandServer.Stub.Proxy(obj); }
函数的参数为
IBinder
类型对象,这是驱动提供的。根据注释可知,如果是Binder
本地对象,其为Binder
类型;如果是Binder
代理对象,其为BinderProxy
类型。asInterface()
方法主要内容为:先查找Binder
本地对象。如果找到,说明Client
与Server
在同一进程,函数参数为本地对象,可直接强转类型返回。否则就是远程对象,需要创建一个Binder
代理对象,实现对远程对象的访问。 -
Proxy
对于示例中提供的
add()
与addUser()
方法,如果Client
与Server
在同一进程,则可直接调用相关方法。如果是远程调用,需要通过Binder
代理完成,即这里的Proxy
类private static class Proxy implements com.zhewen.aidlserverstudy.ICommandServer { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public int add(int value1, int value2) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); int _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeInt(value1); _data.writeInt(value2); boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0); if (!_status) { if (getDefaultImpl() != null) { return getDefaultImpl().add(value1, value2); } } _reply.readException(); _result = _reply.readInt(); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public java.util.List<com.zhewen.aidlserverstudy.User> addUser(com.zhewen.aidlserverstudy.User user) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.zhewen.aidlserverstudy.User> _result; try { _data.writeInterfaceToken(DESCRIPTOR); if ((user!=null)) { _data.writeInt(1); user.writeToParcel(_data, 0); } else { _data.writeInt(0); } boolean _status = mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0); if (!_status) { if (getDefaultImpl() != null) { return getDefaultImpl().addUser(user); } } _reply.readException(); _result = _reply.createTypedArrayList(com.zhewen.aidlserverstudy.User.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } public static com.zhewen.aidlserverstudy.ICommandServer sDefaultImpl; }
看一下其中的
add()
方法。其首先用Parcel
将数据序列化了,然后调用transact()
方法。Proxy
类在asInterface
方法中被创建。前述提到驱动返回的IBinder
实际是BinderProxy
。所以Proxy
中的mRemote
实际类型为BinderProxy
。看一下
BinderProxy
的transact()
方法public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { //...... //调用了一个native方法 return transactNative(code, data, reply, flags); }
transact()
方法调用了一个native
方法,具体实现在frameworks/base/core/jni/android_util_Binder.cpp
文件,其进行一系列函数调用,最终调用到了talkWithDriver
函数,即将通信过程交给驱动完成。这个函数最后通过
ioctl
系统调用,Client
进程陷入内核态。Client
调用add
方法的线程挂起等待返回。驱动完成一系列操作后唤醒Server
进程,调用Server
进程本地对象的onTransact
函数(实际由Server
端线程池完成) -
onTransact()
@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 INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } } switch (code) { case TRANSACTION_add: { data.enforceInterface(descriptor); int _arg0; _arg0 = data.readInt(); int _arg1; _arg1 = data.readInt(); int _result = this.add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; } case TRANSACTION_addUser: { data.enforceInterface(descriptor); com.zhewen.aidlserverstudy.User _arg0; if ((0!=data.readInt())) { _arg0 = com.zhewen.aidlserverstudy.User.CREATOR.createFromParcel(data); } else { _arg0 = null; } java.util.List<com.zhewen.aidlserverstudy.User> _result = this.addUser(_arg0); reply.writeNoException(); reply.writeTypedList(_result); return true; } default: { return super.onTransact(code, data, reply, flags); } } }
onTransact()
主要内容:根据code
编号确认Client
调用哪个函数(每个AIDL
函数都有一个编号,在跨进程的时候,不会传递函数,而是传递编号指明调用哪个函数),然后从data
中取出目标方法需要的参数,执行完后之后向reply
写入返回值(如果目标方法有返回值),之后驱动唤醒挂起的Client
进程里的线程,将结果返回,完成一次跨进程调用。
3、创建Service
,实现接口
完成前述操作后,服务端还需创建一个Service
,并实现前述暴露给客户端的接口
class CommandService: Service() {
override fun onBind(p0: Intent?): IBinder? {
return mRemoteBinder
}
private val mRemoteBinder:ICommandServer.Stub = object: ICommandServer.Stub() {
override fun add(value1: Int, value2: Int): Int {
return CommandServer.sInstance.add(value1,value2)
}
override fun addUser(user: User?): MutableList<User> {
return CommandServer.sInstance.addUser(user)
}
}
客户端执行步骤:
1、文件拷贝
将服务端aidl
文件复制到客户端与java
同级的目录下,保持目录结构一致。如果有传输自定义的Parcelabel
对象,需要将该类拷贝到相应的包名路径下
2、进行绑定服务端Service
的操作
class MainActivity : AppCompatActivity(), View.OnClickListener {
private var mRemoteService: ICommandServer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<TextView>(R.id.bind_service).setOnClickListener(this)
bindService()
}
private val connection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
Log.d("MainActivity","onServiceConnected")
mRemoteService = ICommandServer.Stub.asInterface(service)
//绑定服务成功回调
}
override fun onServiceDisconnected(name: ComponentName) {
//服务断开时回调
}
}
private fun bindService() {
val intent = Intent()
//Android 5.0开始,启动服务必须使用显示的,不能用隐式的
intent.action = "com.zhewen.aidlserverstudy.aidl.commandservice"
intent.`package` = "com.zhewen.aidlserverstudy"
intent.component = ComponentName("com.zhewen.aidlserverstudy", "com.zhewen.aidlserverstudy.aidl.CommandService")
val result = bindService(intent, connection, BIND_AUTO_CREATE)
Log.d("MainActivity", "bindService$result")
}
override fun onClick(p0: View?) {
when(p0?.id) {
R.id.bind_service -> {
bindService()
}
R.id.add_view -> {
val result = mRemoteService?.add(11,14)
findViewById<TextView>(R.id.add_view_result_show).text = "结果——>$result"
}
}
}
}
定向Tag
在基本语法部分简单说明了定向tag
的作用,这里进行详细的演示。
实现步骤与前述介绍的基本使用步骤一致
1、定义数据类接
@Parcelize
data class User(
var age: Int = 0,
var name: String = ""
):Parcelable {
fun readFromParcel(parcel: Parcel) {
this.name = parcel.readString().toString()
this.age = parcel.readInt()
}
}
2、定义aidl
接口并编译实现
interface ICommandServer {
void addUserIn(in User user);
void addUserOut(out User user);
void addUserInOut(inout User user);
}
private val mRemoteBinder:ICommandServer.Stub = object: ICommandServer.Stub() {
override fun addUserIn(user: User?) {
Log.d(TAG,"addUserIn,userName = ${user?.name}")
user?.name = "addUserIn 修改"
}
override fun addUserOut(user: User?) {
Log.d(TAG,"addUserOut,userName = ${user?.name}")
user?.name = "addUserOut 修改"
}
override fun addUserInOut(user: User?) {
Log.d(TAG,"addUserInOut,userName = ${user?.name}")
user?.name = "addUserInOut 修改"
}
}
3、客户端侧获取服务端Binder
对象并调用相关方法
fun addUserIn(){
val user = User(11,"ClientAddUserIn")
Log.d(TAG,"Client 调用 addUserIn 之前 User.name = ${user.name}")
mRemoteService?.addUserIn(user)
Log.d(TAG,"Client 调用 addUserIn 之后 User.name = ${user.name}")
}
fun addUserOut(){
val user = User(22,"ClientAddUserOut")
Log.d(TAG,"Client 调用 addUserOut 之前 User.name = ${user.name}")
mRemoteService?.addUserOut(user)
Log.d(TAG,"Client 调用 addUserOut 之后 User.name = ${user.name}")
}
fun addUserInOut(){
val user = User(33,"ClientAddUserInOut")
Log.d(TAG,"Client 调用 addUserInOut 之前 User.name = ${user.name}")
mRemoteService?.addUserInOut(user)
Log.d(TAG,"Client 调用 addUserInOut 之后 User.name = ${user.name}")
}
看一下调用后的日志输出:
//in 方式,服务端对数据进行修改,但是客户端侧感知不到
CommandClient: Client 调用 addUserIn 之前 User.name = ClientAddUserIn
CommandService: addUserIn,userName = ClientAddUserIn
CommandClient: Client 调用 addUserIn 之后 User.name = ClientAddUserIn
//out 方式,客户端可以感知到服务端的修改,但是无法向服务端传数据,所以服务端未拿到客户端数据
CommandClient: Client 调用 addUserOut 之前 User.name = ClientAddUserOut
CommandService: addUserOut,userName =
CommandClient: Client 调用 addUserOut 之后 User.name =
//inout方式
CommandClient: Client 调用 addUserInOut 之前 User.name = ClientAddUserInOut
CommandService: addUserInOut,userName = ClientAddUserInOut
CommandClient: Client 调用 addUserInOut 之后 User.name = null
注意:关于inout
方式,个人验证时发现服务端可以拿到客户端数据,服务端修改数据客户端也能够感知到,但是拿不到修改数据。看相关文章都可以拿到数据,具体原因待进一步分析
3.3 进阶用法
3.3.1 权限验证
如果处于安全考虑需要对使用自己服务的客户端进行验证,则可以进行权限验证操作。
方式一
在服务端Manifest
文件中自定义一个权限并申请该权限,并在组件Service
上指定permission
属性权限
<permission android:name="com.zhewen.aidlService.permission.ACCESS_SERVICE"
android:protectionLevel="normal"/>
<uses-permission android:name="com.zhewen.aidlService.permission.ACCESS_SERVICE"/>
<!-- //......-->
<service android:name=".aidl.CommandService"
android:permission="com.zhewen.aidlService.permission.ACCESS_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="com.zhewen.aidlserverstudy.aidl.commandservice"/>
</intent-filter>
</service>
然后在客户端申请这个权限。如果客户端没有申请,则客户端将报错,无法正常启动或绑定该服务。
方式二
在方式一的基础上,进行进一步验证。
在服务端Service
类的onBind()
方法中进行权限验证
override fun onBind(p0: Intent?): IBinder? {
// 权限验证
Log.d(TAG,"onBind")
if(Binder.getCallingPid() == Process.myPid()) {
return mRemoteBinder
}
val check = checkCallingOrSelfPermission("com.zhewen.aidlService.permission.ACCESS_SERVICE")
if (check == PackageManager.PERMISSION_DENIED) {
Log.d(TAG,"onBind,null")
return null
}
Log.d(TAG,"onBind 权限验证通过")
return mRemoteBinder
}
方式三
方式三是在方式二的基础上进行了包名验证。每一次IPC
通信都会调用onTransact()
,可以在该方法中进行客户端的包名验证
private val mRemoteBinder:ICommandServer.Stub = object: ICommandServer.Stub() {
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
val clientPkgName = getCallingClientPkgName(getCallingUid())
Log.d(TAG,"calling pkgName is $clientPkgName")
return super.onTransact(code, data, reply, flags)
}
//......
}
//......
private fun getCallingClientPkgName(callingUid:Int) : String {
var packageName = ""
val packages = packageManager.getPackagesForUid(callingUid)
if (packages != null && packages.isNotEmpty()) {
packageName = packages[0]
}
return packageName
}
3.3.2 死亡代理
Binder
运行在服务端进程,若服务端进程因为某些原因死亡,Binder
也随之死亡,此时远程调用将会失败,继而影响到客户端功能。
Binder
提供了linkToDeath
方法可在客户端设置死亡代理,当服务端的Binder
对象“死亡”,客户端可以收到死亡通知,此时我们可以重新恢复链接。
class CommandClient private constructor() {
private var mRemoteService: ICommandServer? = null
private var mContext:WeakReference<Context>? = null
fun init(context:Context) {
mContext = WeakReference(context)
}
private val mDeathRecipient:IBinder.DeathRecipient = object : IBinder.DeathRecipient {
override fun binderDied() {
Log.d(TAG,"binder died")
mRemoteService?.asBinder()?.unlinkToDeath(this,0)
mRemoteService = null
connectService()
}
}
private val mServiceConnection:ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName?, IBinder: IBinder?) {
Log.d(TAG,"onServiceConnected")
try {
//设置死亡代理
IBinder?.linkToDeath(mDeathRecipient,0)
mRemoteService = ICommandServer.Stub.asInterface(IBinder)
} catch (e : RemoteException) {
e.printStackTrace()
}
}
override fun onServiceDisconnected(componentName: ComponentName?) {
Log.d(TAG,"onServiceDisconnected")
mRemoteService = null
}
}
fun add(value1:Int, value2:Int):Int {
return mRemoteService?.add(value1,value2)?:-1
}
fun connectService() : Boolean? {
val intent = Intent()
intent.action = "com.zhewen.aidlserverstudy.aidl.commandservice"
intent.`package` = "com.zhewen.aidlserverstudy"
intent.component = ComponentName("com.zhewen.aidlserverstudy", "com.zhewen.aidlserverstudy.aidl.CommandService")
return mContext?.get()?.bindService(intent,mServiceConnection, BIND_AUTO_CREATE)
}
companion object {
val sInstance = SingletonHolder.holder
const val TAG = "CommandClient"
}
private object SingletonHolder{
val holder = CommandClient()
}
}
3.3.3 oneway
为了防止调用线程被阻塞,可以在aidl
接口方法添加oneway
关键字,则该方法为异步调用。当客户端调用服务端方法时不需要知道返回结果,则可以使用异步调用提高执行效率。
注意:
关于oneway
的使用在某些场景下可能存在问题。
Binder
驱动对oneway
的调用类似handler sendmessage
,如果服务端的oneway
接口处理太慢而客户端调用太多,来不及处理的调用会占满binder
驱动缓存,导致其他调用抛出 transaction failed
异常。
所以,oneway
不适用于客户端高频调用且服务端处理耗时的场景。否则可能出现上述异常,导致业务逻辑缺失。
3.3.4 双向通信
双向通信即服务端也可以主动向客户端返回数据,其核心就是客户端也要创建一个Binder
对象,并将其交付给服务端。
1、接口定义
//用于服务端往客户端回传数据
interface IClientCallback{
void doClientCallback(String value);
}
interface ICommandServer {
void registerClientCallback(IClientCallback client,String pkgName);
void unregisterClientCallback(IClientCallback client,String pkgName);
}
上述接口主要用于客户端注册和解注册回调接口,使得服务端可以获取到客户端的Binder
对象。
2、在服务端实现注册/反注册
在服务端实现上述定义的注册/反注册方法。Android
提供了RemoteCallbackList
,专门用于存放监听接口的集合。其内部将数据存储于一个ArrayMap
中,key
为我们传输的Binder
,value
是监听接口的封装。
RemoteCallbackList
内部操作数据时已做了线程同步操作。
private val mRemoteBinder:ICommandServer.Stub = object: ICommandServer.Stub() {
override fun registerClientCallback(client: IClientCallback?) {
client?.let {
CommandServer.sInstance.registerClientCallback(it)
}
}
override fun unregisterClientCallback(client: IClientCallback?) {
client?.let {
CommandServer.sInstance.unRegisterClientCallback(it)
}
}
}
class CommandServer private constructor(){
companion object {
val sInstance = SingletonHolder.holder
}
private object SingletonHolder{
val holder = CommandServer()
}
//如果客户端异常死亡了,这个对应的回调会自己消失,可在这里进行死亡监听
private val mClientCallbackList = object :RemoteCallbackList<IClientCallback>(){
override fun onCallbackDied(callback: IClientCallback?, cookie: Any?) {
super.onCallbackDied(callback, cookie)
}
}
fun registerClientCallback(client: IClientCallback) {
mClientCallbackList.register(client)
}
fun unRegisterClientCallback(client: IClientCallback) {
mClientCallbackList.unregister(client);
}
}
3、客户端实现暴露的接口和注册与反注册
private val mClientCallback: IClientCallback.Stub = object : IClientCallback.Stub() {
override fun doClientCallback(value: String?) {
Log.d(TAG,"doClientCallback,value = $value")
}
}
private val mServiceConnection:ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName?, IBinder: IBinder?) {
Log.d(TAG,"onServiceConnected")
try {
//设置死亡代理
IBinder?.linkToDeath(mDeathRecipient,0)
mRemoteService = ICommandServer.Stub.asInterface(IBinder)
mRemoteService?.registerClientCallback(mClientCallback);
} catch (e : RemoteException) {
e.printStackTrace()
}
}
override fun onServiceDisconnected(componentName: ComponentName?) {
Log.d(TAG,"onServiceDisconnected")
mRemoteService?.unregisterClientCallback(mClientCallback)
mRemoteService = null
}
}
4、在服务端模拟回调
//CommandService.kt
//死循环 每隔5秒添加一次person,通知观察者
private val serviceWorker = Runnable {
while (!Thread.currentThread().isInterrupted) {
Thread.sleep(20000)
val user = User(Random().nextInt(100),"server")
Log.d(TAG, "服务端 onDataChange() 生产的 user = $user}")
CommandServer.sInstance.onDataChange(user)
}
}
override fun onDestroy() {
CommandServer.sInstance.clean()
super.onDestroy()
}
fun onDataChange(user: User) {
val callbackCount = mClientCallbackList.beginBroadcast()
for (i in 0 until callbackCount) {
try {
mClientCallbackList.getBroadcastItem(i)?.doClientCallback(user.age.toString())
} catch (e:RemoteException) {
e.printStackTrace()
}
}
mClientCallbackList.finishBroadcast()
}
fun clean() {
mClientCallbackList.kill()
}