一、AIDL(Android Interface Definition Language,安卓接口定义语言)
1. AIDL 概念
不同程序和不同进程【即不同工程】之间的通信
设计AIDL这门语言的目的就是为了实现进程间通信。在Android系统中,每个进程都运行在一块独立的内存中,在其中完成自己的各项活动,与其他进程都分隔开来。可是有时候我们又有应用间进行互动的需求,比较传递数据或者任务委托等,AIDL就是为了满足这种需求而诞生的。通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求。
AIDL 语法很简单,只是定义两个进程间的通信接口而已
而生成符合通信协议的 Java
代码则是由 Android SDK
的 platform-tools/aidl.exe
工具生成
生成对应的接口文件在: java(generated) 目录下,一般是 Xxx.java
的接口
在该接口中包含一个 Stub
的内部类,该类中实现了在该类中实现了 IBinder
接口与自定义的通信接口
Sub
类会作为远程 Service
的回调类——实现了 IBinder
接口,所以可作为 Service
的 onBind()
方法的返回值
创建 AIDL 文件:
2. AIDL 支持的数据类型
- java 的 8 种数据类型:byte、short、int、long、float、double、boolean、char
- 除此之外支持 String、charSequence、List、Map
- 自定义数据类型,实现Parcelabel的引用类型。
如果业务方法中参数或返回值类型为 List 或 Map 时:
List 中的所有元素都必须是 AIDL 支持的数据类型、其他 AIDL 生成的接口或自己声明的可打包类型。可选择将 List 用作“通用”类(例如,List<String>)。另一端实际接收的具体类始终是 ArrayList,但生成的方法使用的是 List 接口。
3, 创建AIDL
在工程的 src 目录下创建 .aidl 文件:
Book.aidl -没有定义方法
// Book.aidl
package com.example.newdemo.myaidlserver;
// Declare any non-default types here with import statements
parcelable Book;
BookController.aidl 指定了方法
// BookControler.aidl
package com.example.newdemo.myaidlserver;
import com.example.newdemo.myaidlserver.Book;
// 用于在不同进程中,获取 书籍信息和 添加书籍信息
interface BookControler {
// 获取书籍
List<Book> getBoolList();
void addBookInout(inout Book book); // 双向处理数据
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
点击 工具栏中 Build ---> make project ,Android SDK 工具会在相应的目录生成对应的与 .aidl 文件同名的 .java 接口文件
BookControler接口信息如下:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.newdemo.myaidlserver;
// 用于在不同进程中,获取 书籍信息和 添加书籍信息
public interface BookControler extends android.os.IInterface
{
/** Default implementation for BookControler. */
public static class Default implements com.example.newdemo.myaidlserver.BookControler
{
// 获取书籍
@Override public java.util.List<com.example.newdemo.myaidlserver.Book> getBoolList() throws android.os.RemoteException
{
return null;
}
@Override public void addBookInout(com.example.newdemo.myaidlserver.Book book) throws android.os.RemoteException
{
}
// 双向处理数据
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
}
@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.example.newdemo.myaidlserver.BookControler
{
private static final java.lang.String DESCRIPTOR = "com.example.newdemo.myaidlserver.BookControler";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.newdemo.myaidlserver.BookControler interface,
* generating a proxy if needed.
*/
public static com.example.newdemo.myaidlserver.BookControler asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.newdemo.myaidlserver.BookControler))) {
return ((com.example.newdemo.myaidlserver.BookControler)iin);
}
return new com.example.newdemo.myaidlserver.BookControler.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;
}
case TRANSACTION_getBoolList:
{
data.enforceInterface(descriptor);
java.util.List<com.example.newdemo.myaidlserver.Book> _result = this.getBoolList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBookInout:
{
data.enforceInterface(descriptor);
com.example.newdemo.myaidlserver.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.newdemo.myaidlserver.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBookInout(_arg0);
reply.writeNoException();
if ((_arg0!=null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
else {
reply.writeInt(0);
}
return true;
}
case TRANSACTION_basicTypes:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0!=data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.newdemo.myaidlserver.BookControler
{
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 java.util.List<com.example.newdemo.myaidlserver.Book> getBoolList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.newdemo.myaidlserver.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getBoolList, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getBoolList();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.example.newdemo.myaidlserver.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBookInout(com.example.newdemo.myaidlserver.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addBookInout, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addBookInout(book);
return;
}
_reply.readException();
if ((0!=_reply.readInt())) {
book.readFromParcel(_reply);
}
}
finally {
_reply.recycle();
_data.recycle();
}
}
// 双向处理数据
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean)?(1):(0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
boolean _status = mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().basicTypes(anInt, aLong, aBoolean, aFloat, aDouble, aString);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.example.newdemo.myaidlserver.BookControler sDefaultImpl;
}
static final int TRANSACTION_getBoolList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBookInout = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
public static boolean setDefaultImpl(com.example.newdemo.myaidlserver.BookControler 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.example.newdemo.myaidlserver.BookControler getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
// 获取书籍
public java.util.List<com.example.newdemo.myaidlserver.Book> getBoolList() throws android.os.RemoteException;
public void addBookInout(com.example.newdemo.myaidlserver.Book book) throws android.os.RemoteException;
// 双向处理数据
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}
Stub 实现了本地接口且继承了 Binder 对象,介于 Binder 对象在系统底层的支持下,Stub 对象就具有了远程传输数据的能力,在生成 Stub 对象的时候会调用 asInterface 方法
public static abstract class Stub extends android.os.Binder implements com.example.newdemo.myaidlserver.BookControler
{
private static final java.lang.String DESCRIPTOR = "com.example.newdemo.myaidlserver.BookControler";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
public static com.example.newdemo.myaidlserver.BookControler asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.newdemo.myaidlserver.BookControler))) {
return ((com.example.newdemo.myaidlserver.BookControler)iin);
}
return new com.example.newdemo.myaidlserver.BookControler.Stub.Proxy(obj);
}
4. 参数修饰符
- in: 表示参数数据只能由客户端传递到服务端,基本类型就默认只支持in修饰符。
- out:表示参数数据只能由服务端传递到客户端。即服务端如果修改了参数对象的值,那么客户端的值也会变化,但是服务端无法读取到客户端对象的值。
- inout:表示参数数据能够双向传递。
具体使用详情可以查看:AIDL详细解释
二、Binder机制
1. 产生的背景
Android 很多核心功能都是由一系列 Services 支持的,比如 ActivityService、WindowService 等等等等,应用需要频繁地与这些 Services 发生交互,正是基于这种场景,就亟需一种好的 IPC 解决方案。
拿Activity举例从上图可以看出来:Activity是由ActivityManager来控制的,而ActivityManager其实是通过Binder获取ActivityManagerService服务来控制Activity的,并且ActivityManager是Android系统FrameWork层的,和应用中的activity不是同一个进程。
重点:
1、Binder是Android提供的一套进程间通信框架。
2、系统服务ActivityManagerService,LocationManagerService,等都是在单独进程中的,使用binder和应用进行通信。
如上图,Android系统分成三层。最上层是application应用层,第二层是Framework层,第三层是native层。 由下图可知几点:
1、Android中的应用层和系统服务层不在同一个进程,系统服务在单独的进程中。
2、Android中不同应用属于不同的进程中。
Android应用和系统services运行在不同进程中是为了安全,稳定,以及内存管理的原因,但是应用和系统服务需要通信和分享数据。
优点
安全性:每个进程都单独运行的,可以保证应用层对系统层的隔离。
稳定性:如果某个进程崩溃了不会导致其他进程崩溃。
内存分配:如果某个进程以及不需要了可以从内存中移除,并且回收相应的内存。
2. Binder工作原理
可以把 Binder 驱动看作一个机器,它连接着所有 Services(假设 app 只调用 Services 提供的接口)。
一个 app(客户端)想要找一个 Service 办点事,它就要去操作这个机器,而这个机器会检测 app 的身份,如果身份合法,则可以继续操作。假设 app 要调用 A 服务的 foo
函数,那么它可以告诉这个机器,这个机器随后就会检查连在它身上的所有 Services,找到 app 需要的那一个,让它执行 foo
函数,并且再将返回值告诉 app,这样 app 就成功隔着进程操作到 A 服务了。
这当然是很抽象的说法,系统在 Framework 层做了很好地封装,让我们可以友好地使用 Binder 驱动来进行 IPC 操作
2.1 binder架构
a:Binder通信采用C/S架构,从组件视图来看,包含Client,Server,Binder驱动和ServicerManager,其中ServicerManager负责管理各种服务
b:Binder在Framework层进行了封装,通过JNI技术调用Native层的Binder架构
c:Binder在Native层通过ioctl方式与Binder驱动通信
2.2 Binder通信模式
Binder基于C/S的结构下,定义了4个角色:Server、Client、ServerManager、Binder驱动,其中前三者是在用户空间的,也就是彼此之间无法直接进行交互,Binder驱动是属于内核空间的,属于整个通信的核心,虽然叫驱动,但是实际上和硬件没有太大关系,只是实现的方式和驱动差不多,驱动负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持。
Binder本质上只是提供了一种通信的方式,和我们具体要实现的内容没有关系,为了实现这个服务,我们需要定义一些接口,让client能够远程调用服务,因为是跨进程,这时候就要设计到代理模式,以接口函数位基准,client和server去实现接口函数,Server是服务真正的实现,client作为一个远程的调用。
- 从Server进程来看,Binder是存在的实体对象,client通过transact()函数,经过Binder驱动,最终回调到Binder实体的onTransact()函数中。
- 从 Client进程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个远程代理,通过Binder驱动进行交互
3 进程间通信的一个例子
为什么 IMyService
要分 Stub
和 Proxy
呢?这是为了要适用于本地调用和远程调用两种情况。如果 Service 运行在同一个进程,那就直接用 Stub
,因为它直接实现了 Service 提供的功能,不需要任何 IPC 过程。如果 Service 运行在其他进程,那客户端使用的就是 Proxy
,这里这个 Proxy
的功能大家应该能想到了吧,就是把参数封装后发送给 Binder 驱动,然后执行一系列 IPC 操作最后再取出结果返回。
1. 定义一个接口服务,一个接口继承IInterface,代表了服务端的能力
public interface PersonManger extends IInterface {
void addPerson(Person mPerson);
List<Person> getPersonList();
}
2. 定义一个Server中的Binder实体对象,继承Binder类,实现上述服务端接口
public abstract class BinderObj extends Binder implements PersonManger {
public static final String DESCRIPTOR = "com.example.taolin.hellobinder";
public static final int TRANSAVTION_getPerson = IBinder.FIRST_CALL_TRANSACTION;
public static final int TRANSAVTION_addPerson = IBinder.FIRST_CALL_TRANSACTION + 1;
public static PersonManger asInterface(IBinder mIBinder){
IInterface iInterface = mIBinder.queryLocalInterface(DESCRIPTOR);
if (null!=iInterface&&iInterface instanceof PersonManger){
return (PersonManger)iInterface;
}
return new Proxy(mIBinder);
}
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
switch (code){
case INTERFACE_TRANSACTION:
reply.writeString(DESCRIPTOR);
return true;
case TRANSAVTION_getPerson:
data.enforceInterface(DESCRIPTOR);
List<Person> result = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(result);
return true;
case TRANSAVTION_addPerson:
data.enforceInterface(DESCRIPTOR);
Person arg0 = null;
if (data.readInt() != 0) {
arg0 = Person.CREATOR.createFromParcel(data);
}
this.addPerson(arg0);
reply.writeNoException();
return true;
}
return super.onTransact(code, data, reply, flags);
}
@Override
public IBinder asBinder() {
return this;
}
}
2.1 asInterface方法:
Binder驱动传来的IBinder对象,通过queryLocalInterface方法,查找本地Binder对象,
如果返回的就是PersonManger,说明client和server处于同一个进程,直接返回,
如果不是,返回给一个代理对象,说明不是在同一个进程。
2.2 onTransact 方法:
在Server进程里面,onTransact根据调用code会调用相关函数,接着将结果写入reply并通过super.onTransact返回给驱动,驱动唤醒挂起的Client进程里面的线程并将结果返回
boolean onTransact(int code, Parcel data, Parcel reply, int flags);
// 作用:执行Client进程所请求的目标方法(子类需要复写)
// 参数说明: code:Client进程请求方法标识符。即Server进程根据该标识确定所请求的目标方法
// data:目标方法的参数。(Client进程传进来的,此处就是整数a和b)
// reply:目标方法执行后的结果(返回给Client进程)
// 注:运行在Server进程的Binder线程池中;当Client进程发起远程请求时,远程请求会要求系统底层执行回调该方法
2.3 其中 new Proxy(mIBinder),需要创建代理对象,也需要实现服务接口
public class Proxy implements PersonManger {
private IBinder mIBinder;
public Proxy(IBinder mIBinder) {
this.mIBinder =mIBinder;
}
@Override
public void addPerson(Person mPerson) {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if (mPerson != null) {
data.writeInt(1);
mPerson.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
mIBinder.transact(BinderObj.TRANSAVTION_addPerson, data, replay, 0);
replay.readException();
} catch (RemoteException e){
e.printStackTrace();
} finally {
replay.recycle();
data.recycle();
}
}
@Override
public List<Person> getPersonList() {
Parcel data = Parcel.obtain();
Parcel replay = Parcel.obtain();
List<Person> result = null;
try {
data.writeInterfaceToken(DESCRIPTOR);
mIBinder.transact(BinderObj.TRANSAVTION_getPerson, data, replay, 0);
replay.readException();
result = replay.createTypedArrayList(Person.CREATOR);
}catch (RemoteException e){
e.printStackTrace();
} finally{
replay.recycle();
data.recycle();
}
return result;
}
@Override
public IBinder asBinder() {
return null;
}
}
这里的代理对象实质就是client最终拿到的代理服务,通过这个就可以和Server进行通信了,首先通过Parcel将数据序列化,然后调用 remote.transact()将方法code,和data传输过去,对应的会回调在在Server中的onTransact()中
2.4 创建服务进程 onBind方法返回mStub对象,也就是Server中的Binder实体对象
public class ServerSevice extends Service {
private static final String TAG = "ServerSevice";
private List<Person> mPeople = new ArrayList<>();
@Override
public void onCreate() {
mPeople.add(new Person());
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mStub;
}
private BinderObj mStub = new BinderObj() {
@Override
public void addPerson(Person mPerson) {
if (mPerson==null){
mPerson = new Person();
Log.e(TAG,"null obj");
}
mPeople.add(mPerson);
Log.e(TAG,mPeople.size()+"");
}
@Override
public List<Person> getPersonList() {
return mPeople;
}
};
}
2.5 最后,我们在客户端进程,bindService传入一个ServiceConnection对象,在与服务端建立连接时,通过我们定义好的BinderObj的asInterface方法返回一个代理对象,再调用方法进行交互
public class MainActivity extends AppCompatActivity {
private boolean isConnect = false;
private static final String TAG = "MainActivity";
private PersonManger personManger;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (personManger==null){
Log.e(TAG,"connect error");
return;
}
personManger.addPerson(new Person());
Log.e(TAG,personManger.getPersonList().size()+"");
}
});
}
private void start() {
Intent intent = new Intent(this, ServerSevice.class);
bindService(intent,mServiceConnection,Context.BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG,"connect success");
isConnect = true;
personManger = BinderObj.asInterface(service);
List<Person> personList = personManger.getPersonList();
Log.e(TAG,personList.size()+"");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG,"connect failed");
isConnect = false;
}
};
}
- 最后,客户端与服务端交互的流程
为什么Binder机制可以只是拷贝一次
Binder IPC 是基于内存映射(mmap)来实现的,但是 mmap() 通常是用在有物理介质的文件系统上的。
比如进程中的用户区域是不能直接和物理设备打交道的,如果想要把磁盘上的数据读取到进程的用户区域,需要两次拷贝(磁盘-->内核空间-->用户空间);通常在这种场景下 mmap() 就能发挥作用,通过在物理介质和用户空间之间建立映射,减少数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。
而 Binder 并不存在物理介质,因此 Binder 驱动使用 mmap() 并不是为了在物理介质和用户空间之间建立映射,而是用来在内核空间创建数据接收的缓存空间。
一次完整的 Binder IPC 通信过程通常是这样:
首先 Binder 驱动在内核空间创建一个数据接收缓存区; 接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区之间的映射关系,以及内核中数据接收缓存区和接收进程用户空间地址的映射关系; 发送方进程通过系统调用 copyfromuser() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。 如下图: