IPC-AIDL详解
一 前言
最近想写关于IPC方面的文章,IPC在Android中是非常重要的。今天我们主要讲IPC-AIDL,后面我们会说其它的IPC方式如Messenger,Binder,Socket等。尽量讲的详细,通俗易懂。有什么讲的不对的地方,请大家多多指教。
二 AIDL详解
1 什么是AIDL?
AIDL是一个缩写,全称是Android Interface Definition Language,也就是一种Android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口。
2 为什么需要IPC-AIDL?
Android 是基于Linux系统进行开发的,在Linux系统中进程之间是相互隔离的,不能直接进行通信。
为什么这么设计?如果不这么设计会有什么影响?
如果进程不隔离一个进程的崩溃会影响其他的进程崩溃,有可能会导致整个手机崩溃,导致Android稳定性特别差。如果进程不隔离,恶意进程可以随意访问我们的系统进程和私有进程导致我们的数据泄漏,安全得不到保障。
所以进程之间是不能直接进行通信,我们就引入跨进程通信(IPC)。AIDL是IPC一种,他的功能强大,支持一对多并发和实时通信。
3 基本语法
3.1 支持的数据类型
- 基本数据类型 boolean byte int long float double char short(不支持)。short 不支持这个要特别注意 官方文档说是所有的基本数据类型,这个是不对的,使用short编译会报错,为什么不能使用short呢?后面我们在讲代码时在具体说。
- String CharSequence 类型
- List Map类型,List,Map中的数据类型必须是AIDL支持的数据类型。List可以使用范型,Map不可以。
3.2 定向TAG (in out inout)
TAG表示数据的流向。
in 表示数据只能从 客户端流向服务端。
out表示数据只能从服务端流向客户端。
inout表示数据可以在服务端和客户端双向流动。
4 AIDL使用
4.1 创建一个Person类实现Parcelable接口
首先我们创建一个Person类,name 和age2个成员变量。实现Parcelable接口,在AndroidStudio2.2 可以自动生成 writeToParcel(Parcel parcel, int i) 和createFromParcel(Parcel in)。其他低版本Android Studio只生产了writeToParcel(Parcel parcel, int i),createFromParcel(Parcel in)需要我们自己去写。这个大家要特别注意。还有一个注意事项就是Parcel在读写数据顺序要一致。
我们先看写入
parcel.writeString(name);
parcel.writeInt(age);
再看读取
name = in.readString();
age = in.readInt();
来看Person类代码如下。
public class Person implements Parcelable{
private String name;
private int age;
public Person(String name , int age){
this.name = name;
this.age = age;
}
protected Person(Parcel in) {
name = in.readString();
age = in.readInt();
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeInt(age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
在前面我讲到了AIDL不支持基本数据类型short ,为什么呢?
原因是我们在writeToParcel()中会把成员变量根据类型一个个写入到parcel,如下
parcel.writeString(name);
parcel.writeInt(age);
我们发现不支持 parcel.writeShort(),找不到这个方法。这个就是为什么AIDL不支持基本数据类型short。
4.2 创建Person 的AIDL文件
Android Studio 中点击 File ->new->AIDL。如下图所示。
会在main 文件夹下生成一个aidl文件。文件查下有个Person.aidl.
Person.idl代码如下。
// Person.aidl
package com.example.zhangqilu.ipc_aidl_service;
parcelable Person;
这个文件我们就写了一句代码parcelable Person;这句话的作用是引入了一个序列化对象 Person 供其他的AIDL文件使用。
注意事项
1. parcelable 这个要小写。
2. Person.aidl和Person.Java的包名必须是一样的。否则我们后面使用会报错。
4.3 创建AIDL接口文件
IMyAidlInterface.aidl代码如下
// IMyAidlInterface.aidl
package com.example.zhangqilu.ipc_aidl_service;
import com.example.zhangqilu.ipc_aidl_service.person;
interface IMyAidlInterface {
List<Person> addPerson(in Person person);
}
import com.example.zhangqilu.ipc_aidl_service.person;
导入person这个AIDL文件,非自定数据类型不需导入
List<Person> addPerson(in Person person);
//传参时除了Java基本类型(除 short外)以及String,CharSequence之外的类型都需要在前面加上定向tag(in out inout)。
4.4 服务端代码编写
当我们编写好AIDL文件之后,我们编译一下。会在build->generated->source->aidl->debug 下面生成IMYAidlInterface.java 文件。如下图
这个Java文件我们在后面分析源码会具体分析,也是AIDL通信的原理。
public class IpcAidlService extends Service {
private List<Person> personList = new ArrayList<>();
private IBinder iBinder = new IMyAidlInterface.Stub(){
@Override
public List<Person> addPerson(Person person) throws RemoteException {
personList.add(person);
return personList;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
}
IMyAidlInterface.Stub()是IMyAidlInterface.AIDL生成的Java代码。
addPerson(Person person) 接受客户端发来的person数据,将person数据添加到personList,返回给客户端。
4.5 客户端代码编写
客户端逻辑很简单,绑定服务,在连接上服务时,通过onServiceConnected(ComponentName componentName, IBinder iBinder)拿到iBinder。用iBinder生成iMyAidlInterface对象
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
点击按键发送person数据给服务端,
List<Person> personList = iMyAidlInterface.addPerson(person); 等待服务器返回数据
服务端将客户端发送的所有person数据添加到List,返回给客户端。下面我们来看一下代码。
public class MainActivity extends AppCompatActivity {
private static String TAG = MainActivity.class.getSimpleName();
private Button btnAskAnswer;// 点击事件发送数据给服务端
private TextView tvShowAnswer;//显示服务端返回的数据
private IMyAidlInterface iMyAidlInterface = null;
private int i=0;//发送次数递增
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnAskAnswer = (Button) findViewById(R.id.btn_result);
tvShowAnswer = (TextView) findViewById(R.id.tv_result);
initBindService();
btnAskAnswer.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
Person person = new Person("name"+i,i);
List<Person> personList = iMyAidlInterface.addPerson(person);
Log.e(TAG, "onClick: "+personList.toString());
tvShowAnswer.setText(personList.toString());
i++;
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
/**
* ServiceConnection 接口 实现
*/
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
iMyAidlInterface = null ;
}
};
/**
* 绑定服务
*/
private void initBindService(){
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.example.zhangqilu.ipc_aidl_service","com.example.zhangqilu.ipc_aidl_service.IpcAidlService"));
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}
4.6 注意事项和通信
4.6.1 注意事项
- 在客户端和服务端都要有AIDl文件。
- Person.aidl和Person.Java的包名必须是一样的(自定义数据类型的包名需要一样)
4.6.2 通信
我们先运行服务端,再运行客户端。点击Button按键多次。日志如下图
5 AIDL原理解析
当我们编写好AIDL文件之后,我们编译一下。会在build->generated->source->aidl->debug 下面生成IMYAidlInterface.java 文件。如下图
通过上图我们可以清楚看到IMyAidlInterface.java 层次结构
5.1 从客户端分析源码
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
}
在连接上服务时,通过onServiceConnected(ComponentName componentName, IBinder iBinder)拿到iBinder。用iBinder生成iMyAidlInterface对象 。下面我们来看 IMyAidlInterface.Stub.asInterface();源码
public static com.example.zhangqilu.ipc_aidl_service.IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.zhangqilu.ipc_aidl_service.IMyAidlInterface))) {
return ((com.example.zhangqilu.ipc_aidl_service.IMyAidlInterface) iin);
}
return new com.example.zhangqilu.ipc_aidl_service.IMyAidlInterface.Stub.Proxy(obj);
}
解析
1. 判断传入的参数obj 是否为空
2. 获取IInterface接口 iin (第一次执行都为空)
3. new IMyAidlInterface.Stub.Proxy(obj),Proxy 这是一个代理,他是实现了IMyAidlInterface接口,是addPerson实现的地方。
下面我们看一下Proxy源码
private static class Proxy implements com.example.zhangqilu.ipc_aidl_service.IMyAidlInterface {
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;
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
@Override
public java.util.List<com.example.zhangqilu.ipc_aidl_service.Person> addPerson(com.example.zhangqilu
.ipc_aidl_service.Person person) 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.zhangqilu.ipc_aidl_service.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);//写入接口token
if ((person != null)) {
_data.writeInt(1);//有数据写入整数1
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);//数据为空写入整数0
}
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);//发送数据给服务端
_reply.readException();
_result = _reply.createTypedArrayList(com.example.zhangqilu.ipc_aidl_service.Person.CREATOR);//从_reply读取服务端返回的数据
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
我们主要分析addPerson方法。首先我来看一下Parcel,
Parcel就是一个存放读取数据的容器。在Java空间和C++都实现了Parcel,由于它在C/C++中,直接使用了内存来读取数据,因此,它更有效率。
我们来看看addPerson方法具体流程。
1. 生成 2个(_data ,_reply)Parcel存储容器。
2. 向_data存储容器中写入数据。
3. 通过mRemote(IBinder 从服务器返回来的)的transact发送数据给服务端。并挂起当前线程,等待服务端返回数据。
4. 等服务端返回数据后,我们从_reply存储容器中读取数据。
接下来我们来了解一下mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);方法,他有4个参数。
1. int 类型 aidl文件接口方法的ID。
2. Parcel类型 客户端发送数据的容器。
3. Parcel类型 客户端接受服务端返回数据的容器。
4. int 类型 设置进行 IPC 的模式,为 0 表示数据可以双向流通,如果为 1 的话那么数据将只能单向流通,从服务端回来的 _reply 流将不携带任何数据。默认生成为0。
5.2 从服务端分析源码
我们在客户端中有发送数据到服务端,那服务端应该有接收数据的地方。我来看一下接收数据的代码
private IBinder iBinder = new IMyAidlInterface.Stub(){
@Override
public List<Person> addPerson(Person person) throws RemoteException {
personList.add(person);
return personList;
}
};
服务端我们是通过实现addPerson 方法来接收数据的。我们从自动生成的源码中找到了onTransact方法中看到有调用addPerson方法,看一下源码
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws
android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addPerson: {
data.enforceInterface(DESCRIPTOR);
com.example.zhangqilu.ipc_aidl_service.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.zhangqilu.ipc_aidl_service.Person.CREATOR.createFromParcel(data);//读取数据
} else {
_arg0 = null;
}
java.util.List<com.example.zhangqilu.ipc_aidl_service.Person> _result = this.addPerson(_arg0);
reply.writeNoException();
reply.writeTypedList(_result);//reply写入数据
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
switch 语句中我们看到了 TRANSACTION_addPerson,还记得我们在transact()方法中的传入的第一个参数吗?int 类型 方法ID 就是TRANSACTION_addPerson。下面我们来看一下TRANSACTION_addPerson case下具体流程。
1. 判断data中有没有数据,如果有数据读取data 中数据存入_arg0。
2. 调用addPerson(_arg0)方法将返回值存储于_result列表中。
3. 向reply容器中写入数据。
5.3 AIDL服务端和客户端通信的流程图
下面我们来看一下AIDL服务端和客户端通信的流程图
根据上面流程图大致说一下流程
1. 客户端连接上服务端时。执行下面方法, IMyAidlInterface.Stub.asInterface(iBinder);
2. 在asInterface(Binder)方法里面会创建一个Proxy,
new IMyAidlInterface.Stub.Proxy(obj)
3. Proxy实现了IMyAidlInterface接口,是addPerson真正实现的地方。客户端通过Proxy的addPerson方法中的transact发送数据给服务端。
4. 服务端 创建一个 iBinder = new IMyAidlInterface.Stub(),并实现了addPerson方法,用于接收接受客户端发送的数据(服务端真正接收数据的地方是 onTransact方法,在这个方法里回调方法addPerson给客户端)。
最后附上源码:
http://download.csdn.net/detail/zhangqilugrubby/9671675
有什么写的不对的地方请大家多多指教,谢谢。