熟悉AIDL的都知道,AIDL使用有比较固定的流程,可能大概也许是这样子的:1、书写一个aidl文件,比如叫作IMyAidlInterface.aidl,写上需要的接口方法,如果有自定义类型参数,必须实现Parcelable接口,并且增加对应的aidl文件;
2、新建一个Service,写一个类继承IMyAidlInterface.Stub类,对接口方法进行实现,并且在onBind()方法返回一个它的实例,本质上它是一个Binder对象;
3、在Activity中采用bindService()绑定服务,实现参数ServiceConnection的onServiceConnected()和onServiceDisconnected()方法;
4、在onServiceConnected()方法将IBinder实例采用IMyAidlInterface.Stub.asInterface()方法包装成IMyAidlInterface接口对象,得到对象之后我们就可以实现服务的方法调用了。
来个实例:
第一步、写个aidl文件,写上需要的接口方法。
// IStudentService.aidl
package com.example.administrator.study;
import com.example.administrator.study2.StudentInfo;
interface IStudentService {
StudentInfo getStudentInfo();
void setStudentInfo(in StudentInfo info);
String getName();
void setName(String name);
}
其中有自定义参数StudentInfo,它需要实现Parcelable接口,并且还需要增加StudentInfo.aidl文件。
// StudentInfo.java
package com.example.administrator.study2;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by Administrator on 2018/7/12 0012.
*/
public class StudentInfo implements Parcelable{
private int age;
private String grade;
private int _class;
public StudentInfo(int age, String grade, int _class) {
this.age = age;
this.grade = grade;
this._class = _class;
}
@Override
public String toString() {
return "StudentInfo{" +
"age=" + age +
", grade='" + grade + '\'' +
", _class=" + _class +
'}';
}
protected StudentInfo(Parcel in) {
age = in.readInt();
grade = in.readString();
_class = in.readInt();
}
public static final Creator<StudentInfo> CREATOR = new Creator<StudentInfo>() {
@Override
public StudentInfo createFromParcel(Parcel in) {
return new StudentInfo(in);
}
@Override
public StudentInfo[] newArray(int size) {
return new StudentInfo[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(age);
dest.writeString(grade);
dest.writeInt(_class);
}
}
// IStudent.aidl
package com.example.administrator.study2;
parcelable StudentInfo;
StudentInfo需要对Parcelable接口进行实现,不过我们写好需要的属性之后AS是可以自动生成的。需要注意的是StudentInfo.java需要与StudentInfo.aidl在同一个包下,之所以需要在同一个包,是因为IStudentService.aidl在生成IStudentService.java的时候,会引入同名包下对应的类,比如上面的getStudentInfo()方法和setStudentInfo()方法在IStudentService.java中是这样的。
public com.example.administrator.study2.StudentInfo getStudentInfo() throws android.os.RemoteException;
public void setStudentInfo(com.example.administrator.study2.StudentInfo info) throws android.os.RemoteException;
如果com.example.administrator.study2包下没有对应的StudentInfo类,那么就会报编译错误。
第二步、写一个类继承IStudentService.Stub类,并且在Serviced的onBind()方法中返回它的实例。
public class StudentService extends Service{
private StudentInfo mInfo;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new IStudentService.Stub() {
@Override
public com.example.administrator.study2.StudentInfo getStudentInfo() throws RemoteException {
return mInfo;
}
@Override
public void setStudentInfo(com.example.administrator.study2.StudentInfo info) throws RemoteException {
mInfo = info;
}
public String getName() throws RemoteException {
return mName;
}
@Override
public void setName(String name) throws RemoteException {
mName = name;
}
};
}
}
第三步、在Activity中bindService绑定服务,实现ServiceConnection的ServiceConnected()方法,得到Binder对象,并且转成IStudentService接口对象,之后就可以进行方法调用了。
bindService(new Intent(this, StudentService.class), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iStudentService = IStudentService.Stub.asInterface(service);
try {
iStudentService.setStudentInfo(new StudentInfo(12,"大一",5));
Toast.makeText(Main3Activity.this, iStudentService.getStudentInfo().toString(), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iStudentService = null
}
}, BIND_AUTO_CREATE);
运行之后正常弹出Toast显示StudentInfo{age=12, grade='大一', _class=5},流程顺利。
以上代码很多人都非常熟悉了,是我们使用aidl实现跨进程通讯的方式。但其实aidl只是一种接口定义语言,它是用来生成Android进程间通信代码的,真正实现进程间通信的是Binder机制。那Binder到底在哪里,它做了什么,我们可以直接用Binder来进行进程间通讯吗,我也很想知道,接下来我们就来分析一下。
我们来看看自动生成的IStudentService.java类。
package com.example.administrator.study;
public interface IStudentService extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.administrator.study.IStudentService {
private static final java.lang.String DESCRIPTOR = "com.example.administrator.study.IStudentService";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.administrator.study.IStudentService interface,
* generating a proxy if needed.
*/
public static com.example.administrator.study.IStudentService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.administrator.study.IStudentService))) {
return ((com.example.administrator.study.IStudentService) iin);
}
return new com.example.administrator.study.IStudentService.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 {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getStudentInfo: {
data.enforceInterface(DESCRIPTOR);
com.example.administrator.study2.StudentInfo _result = this.getStudentInfo();
reply.writeNoException();
if ((_result != null)) {
reply.writeInt(1);
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
case TRANSACTION_setStudentInfo: {
data.enforceInterface(DESCRIPTOR);
com.example.administrator.study2.StudentInfo _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.administrator.study2.StudentInfo.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.setStudentInfo(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getName: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_setName: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.setName(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.administrator.study.IStudentService {
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 com.example.administrator.study2.StudentInfo getStudentInfo() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
com.example.administrator.study2.StudentInfo _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getStudentInfo, _data, _reply, 0);
_reply.readException();
if ((0 != _reply.readInt())) {
_result = com.example.administrator.study2.StudentInfo.CREATOR.createFromParcel(_reply);
} else {
_result = null;
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void setStudentInfo(com.example.administrator.study2.StudentInfo info) 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 ((info != null)) {
_data.writeInt(1);
info.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_setStudentInfo, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.lang.String getName() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void setName(java.lang.String name) 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.writeString(name);
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getStudentInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setStudentInfo = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
}
public com.example.administrator.study2.StudentInfo getStudentInfo() throws android.os.RemoteException;
public void setStudentInfo(com.example.administrator.study2.StudentInfo info) throws android.os.RemoteException;
public java.lang.String getName() throws android.os.RemoteException;
public void setName(java.lang.String name) throws android.os.RemoteException;
}
我们来分析一下这个类的结构。最外层是一个接口IStudentService,它继承了接口android.os.IInterface。IStudentService有四个方法,在文件的最后,跟我们在aidl文件中写的是一样的。
接口内有个静态内部类Stub,它继承了Binder类同时实现了IStudentService接口。
Stub类里面也有一个静态内部类Proxy,它仅仅实现了IStudentService接口。
它们三个虽然定义在一个文件内,但本质上是相互独立的。Proxy是运行在客户端的代码。Stub是运行在服务端的代码。aidl通信的实质就是客户端通过Proxy去发送请求调用,服务端通过Stub做出自己的响应处理。Proxy和Stub都实现了IStudentService接口,因此他们的调用可以“无缝连接”,即Proxy所发出的调用都可以在Stub中找到对应的处理方法。
一张图表示一下这个关系。
让我们来跟踪一下流程。
客户端中,bindService()后我们在onServiceConnected()方法中我们采用IStudentService.Stub.asInterface()将IBinder对象转化成了IStudentService对象
bindService(new Intent(this, StudentService.class), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
iStudentService = IStudentService.Stub.asInterface(service);
try {
iStudentService.setStudentInfo(new StudentInfo(12,"大一",5));
Toast.makeText(Main3Activity.this, iStudentService.getStudentInfo().toString(), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
iStudentService = null
}
}, BIND_AUTO_CREATE);
asInterface()的实现是这样子的。
/**
* Cast an IBinder object into an com.example.administrator.study.IStudentService interface,
* generating a proxy if needed.
*/
public static com.example.administrator.study.IStudentService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.administrator.study.IStudentService))) {
return ((com.example.administrator.study.IStudentService) iin);
}
return new com.example.administrator.study.IStudentService.Stub.Proxy(obj);
}
前面一部分是查询本进程有没有该接口对象,如果是同一个进程就会走 iin不为空的判断,我们先不管,来看最后一句。最后一句创建了Proxy对象,并且把IBinder对象传入,然后返回。
来到Proxy的构造方法,对参数IBinder的处理只是保存了它的引用而已,其实Proxy使用了代理模式,Proxy的方法将会通过IBinder的方法来实现。
private static class Proxy implements com.example.administrator.study.IStudentService {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
……
}
asInterface()方法结束了,我们得到一个Proxy对象,接着我们调用setStudentInfo方法,给Service设置了一个StudentInfo对象。
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IStudentService iStudentService = IStudentService.Stub.asInterface(service);
try {
iStudentService.setStudentInfo(new StudentInfo(12,"大一",5));
Toast.makeText(Main3Activity.this, iStudentService.getStudentInfo().toString(), Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
setStudentInfo在Proxy的实现是这样的。
@Override
public void setStudentInfo(com.example.administrator.study2.StudentInfo info) 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 ((info != null)) {
_data.writeInt(1);
info.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_setStudentInfo, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
首先创建了两个Parcel对象,可以理解_data作为参数值,_reply作为返回值。首先用_data写了一些东西,我们先不看,写完之后调用了
mRemote.transact(Stub.TRANSACTION_setStudentInfo, _data, _reply, 0);
transact方法的声明如下。
/**
* Perform a generic operation with the object.
*
* @param code The action to perform. This should
* be a number between {@link #FIRST_CALL_TRANSACTION} and
* {@link #LAST_CALL_TRANSACTION}.
* @param data Marshalled data to send to the target. Must not be null.
* If you are not sending any data, you must create an empty Parcel
* that is given here.
* @param reply Marshalled data to be received from the target. May be
* null if you are not interested in the return value.
* @param flags Additional operation flags. Either 0 for a normal
* RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
*/
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException;
可以理解为code表示标识,data表示参数,reply表示返回值,flags是标志(不是0就是FLAG_ONEWAY,FLAG_ONEWAY的值是1)。
查看其它方法,流程也差不多,Proxy本身并没有提供具体逻辑,而是委托给Binder的transact()方法加上Parcel的方法实现。
transact方法的实现就不再细究,它会调用到本地方法,它的效果是引起服务端onTransact的回调。即我们在客户端调用transact方法之后,服务端Binder的onTransact就会得到调用,这个跨进程通信是Binder机制实现的。至于Binder是如何实现跨进程通信的,大概的原理是Android系统的内存空间是分为用户空间和内核空间,用户空间不同进程之间是不能共享的,而内核空间却是共享的。客户端进程向服务端进程发起通信,就是利用进程间可共享的内核空间来完成底层通信工作。对内核空间中驱动的交互往往采用ioctl等方法,客户端将Parcel发送给内核中的Binder Driver,Server会读取Binder Driver中的请求数据,如果是发送给自己的,解包Parcel对象,处理并将数据返回。大概是这样子,具体不是很懂,熟悉内核驱动的就会对这个非常熟了,想知道具体的可以详细查查这部分的知识。
接下来我们就来看看服务端是如何setStudentInfo()做出响应。前面说了transact()方法会引起服务端onTransact()的回调。
@Overridepublic boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
……
case TRANSACTION_setStudentInfo: {
data.enforceInterface(DESCRIPTOR);
com.example.administrator.study2.StudentInfo _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.administrator.study2.StudentInfo.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.setStudentInfo(_arg0);
reply.writeNoException();
return true;
}
……
}
return super.onTransact(code, data, reply, flags);
}
onTransact()也有跟transact()同样的四个参数,并且是一一对应的。首先对code进行switch处理,执行setStudentInfo()相关的逻辑。大致过程是提取参数data这个Parcel的数据,即setStudentInfo()时传入的StudentInfo对象,然后调用setStudentInfo()方法,这个方法在Stub中是个空实现,这就是为什么Stub是抽象的,它的方法要交给具体的子类实现,也就是我们在onBind方法中返回Binder对象。
@Override
public IBinder onBind(Intent intent) {
return new IStudentService.Stub() {
@Override
public com.example.administrator.study2.StudentInfo getStudentInfo() throws RemoteException {
return mInfo;
}
@Override
public void setStudentInfo(com.example.administrator.study2.StudentInfo info) throws RemoteException {
mInfo = info;
}
@Override
public String getName() throws RemoteException {
return mName;
}
@Override
public void setName(String name) throws RemoteException {
mName = name;
}
};
}
这样就实现了对应方法的调用,数据在其中的传递过程是采用Parcel的writeXXX和readXXX方法,内部实现也是native方法,并且它们在transact和onTransact的读取和写入的顺序是一一对应的。
所以总结一下Binder通信的过程就是,客户端采用Parcel写入各种数据,然后调用transact,此时会阻塞,等待服务端onTransact执行完成,接着从另一个Parcel中取得返回的数据。
服务端在onTransact中采用Parcel读取数据,然后执行具体逻辑,执行完后采用另一个Parcel写回返回值。
既然是这样,那是否我们实现Binder通信也可以不用借助aidl,直接把Binder拿来使用,调用它的transact方法实现进程间通信。答案当然是可以的。
首先直接在Service的onBind方法创建一个Binder对象,并且实现它的onTransact方法。
@Override
public IBinder onBind(Intent intent) {
return new Binder(){
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
//get
case 2333: {
reply.writeString(mName);
reply.writeInt(mAge);
return true;
}
//set
case 2335: {
String name = data.readString();
int age = data.readInt();
mName = name;
mAge = age;
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
};
}
简简单单,set流程中我们从data读数据然后保存,get流程中我们直接往replay中写入数据。
客户端中,我们直接拿onServiceConnected的Binder来调用transact()方法。
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//set过程
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeString("zjb");
_data.writeInt(88);
service.transact(2335, _data, _reply, 0);
} catch (RemoteException e) {
e.printStackTrace();
} finally {
_reply.recycle();
_data.recycle();
}
//get过程
Parcel _data_2 = Parcel.obtain();
Parcel _reply_2 = Parcel.obtain();
try {
service.transact(2333, _data_2, _reply_2, 0);
String _name = _reply_2.readString();
int _age = _reply_2.readInt();
Toast.makeText(MainActivity.this, _name + " : " + _age, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
} finally {
_reply_2.recycle();
_data_2.recycle();
}
}
set过程,创建两个Parcel对象,一个是用来传递数据的,一个是用来得到返回值的。把需要传递的参数采用Parcel的write方法一一写入,然后调用transact()方法,这就完成了set方法的调用。get过程,创建两个Parcel对象,因为没有数据传递,所以直接transact()获取到返回的Parcel _reply_2,采用readXXX()方法读取它的数据。
运行程序,正常弹出 zjb : 88 的Toast,证明调用成功了,我们也看到,采用这种方法,我们也可以在不自定义类型的情况下实现一次方法调用返回两个值。运行成功也证明aidl就是这样工作的,只不过它封装了流程而已。
补充一下,客户端得到的Binder和服务端返回的Binder是不是同一个对象,答案是:服务与Activity在同一个进程时是的,如果不在同个进程则不是。
虽然标题叫了解Binder,但只是说了一下它的用法,要具体了解,要深入的还有很多。不过知道这部分内容,当我们再看ActivityManagerService和ActivityThread通信的部分源码的时候就没有那么难理解了。这也是本文的一个小小目的吧。