IPC之AIDL简析
一.什么是AIDL
Android Interface Definition Language意为Android接口描述语言,常用于进程间通讯。首先要说明的是,如果你很牛逼,你完全不用AIDL文件即可实现Binder,Android之所以给开发者提供AIDL是为了方便系统为我们自动生成实现Binder的代码。
二.实现简单的跨进程服务
使用前先理解这是一个典型的C/S结构,提供服务的是服务端,需要跨进程访问的是客户端。
1.创建一个Android项目作为service端(这里使用AS作为示例)命名为:Demo_aidl_service,在新建一个AIDL文件,方便的是AS已经为我们考虑到了,右键工程->new->AIDL->AIDL FILE,创建一个名为ILoadAidlInterface.aidl的文件。结构如下(注意所在包):
2.将暴露给客户端的接口在这个AIDL文件中声明(通俗说就是把将来客户端要用的方法写出来)
// ILoadAidlInterface.aidl
package com.example.aidl_service;
interface ILoadAidlInterface {
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
void load(String username,String pwd);
}
第一个basicTypes是系统默认的不用管,我这里只写了个load方法用于模拟登入功能,记住写完后一定要make一下工程让系统去生成相应的ILoadAidlInterface.java类
3.服务端service的实现,还是在原工程下创建一个LoadService继承Service(注意包名)
package com.example.aidl_service.service;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import com.example.aidl_service.ILoadAidlInterface;
/**
* Created by Xio on 2016/7/11.
*/
public class LoadService extends Service {
@Override
public IBinder onBind(Intent intent) {
Log.d("service", "service onBind");
return mBinder;
}
@Override
public void onCreate() {
Log.d("service", "service onCreate");
super.onCreate();
}
Binder mBinder = new ILoadAidlInterface.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public void load(String username, String pwd) throws RemoteException {
Log.d("service", "load: 用户名:" + username + ",密码:" + pwd);
}
};
}
在服务中我们创建了一个Binder对象并在onBind中返回,这个对象继承自ILoadAidlInterface.Stub并实现了它内部的俩个方法,在load方法中打印了个log。最后别忘记注册服务activity并且在activity下启动这个服务,如果不启动何来进程间通信?
<service android:name=".service.LoadService">
<intent-filter >
<action android:name="com.example.aidl_service.service.LoadService"/>
</intent-filter>
</service>
注意要给service设定一个action以便客户端找到该服务。
4.接下来是新建客户端工程:名为Demo_aidl_client,之后把服务端工程下的aidl连文件夹一起拷过来,这样就能确保俩个aidl文件的包名一样。别忘了再make下工程。最后在mainactivity下绑定远程服务就行了。
public class MainActivity extends Activity {
private Button mBtn;
private ServiceConnection connection;
private ILoadAidlInterface mLoadAIDL = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mLoadAIDL = ILoadAidlInterface.Stub.asInterface(service);
Log.d("client", "onServiceConnected: ok!");
doLoad();
}
@Override
public void onServiceDisconnected(ComponentName name) {
mLoadAIDL = null;
Log.d("client", "unbind");
}
};
mBtn = (Button) findViewById(R.id.btn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i("TvT", "onClick: ");
if(mLoadAIDL==null){
bindService();
Log.i("TvT", "onClick: 1111111111111111111");
}else{
doLoad();
}
}
});
}
private void doLoad() {
try {
mLoadAIDL.load("xiongzihao","123456");
} catch (RemoteException e) {
e.printStackTrace();
}
}
private void bindService() {
Intent intent = new Intent();
intent.setAction("com.example.aidl_service.service.LoadService");
intent.setPackage("com.example.aidl_service");
bindService(intent,connection,BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
ILoadAidlInterface mLoadAIDL = ILoadAidlInterface.Stub.asInterface(service);
这句式关键,绑定成功后我们将服务端返回的Binder对象转化为了AIDL接口,通过这个接口就可以调用服务端的远程方法了。mLoadAIDL.load();
这里需要注意:Android5.0官方规定service必须使用显示调用,如果隐式调用的话会产生异常导致程序崩溃,所以我们在setAction的同时还要setPackge使其显式调用
方法是执行在服务端,最后结果打印的log是在sevice端下的,而不是在客户端,这说明跨进程成功了!
三.分析AIDL实现跨进程原理
我这里参考了很多资料和博文,基本上都是从服务端开始讲起接口回调,这本来就是种逆向思维,不太好理解,这里我们从客户端讲起,一步步顺着流程走下去。
Demo_aidl_client要跨进程访问Demo_aidl_service,首先通过bindservice方法绑定一个服务,这个服务就是服务端所提供的服务LoadService,建立连接之后客户端通过asInterface方法拿到一个IBinder对象,这个方法等会分析,这时客户端发起远程请求,同时当前线程会挂起直至服务端返回数据,所以一个远程方法比较耗时的话,不能在主线程去发起此请求。
先看下系统根据ILoadAidlInterface.aidl生成的ILoadAidlInterface.java文件,其格式是固定的。
package com.example.aidl_service;
// Declare any non-default types here with import statements
public interface ILoadAidlInterface extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.example.aidl_service.ILoadAidlInterface {
private static final java.lang.String DESCRIPTOR = "com.example.aidl_service.ILoadAidlInterface";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.aidl_service.ILoadAidlInterface interface,
* generating a proxy if needed.
*/
public static com.example.aidl_service.ILoadAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.aidl_service.ILoadAidlInterface))) {
return ((com.example.aidl_service.ILoadAidlInterface) iin);
}
return new com.example.aidl_service.ILoadAidlInterface.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_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;
}
case TRANSACTION_load: {
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
java.lang.String _arg1;
_arg1 = data.readString();
this.load(_arg0, _arg1);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.aidl_service.ILoadAidlInterface {
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 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);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void load(java.lang.String username, java.lang.String pwd) 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(username);
_data.writeString(pwd);
mRemote.transact(Stub.TRANSACTION_load, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_load = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
public void load(java.lang.String username, java.lang.String pwd) throws android.os.RemoteException;
}
- 可以看到这个类继承了IInterface接口,它自己也是个接口,改接口类声明了俩个方法,可以看出就是我们在aidl文件中写的两个方法(一个是系统默认的)basicTypes()和load()。
- 同时还声明了两个静态id来标识这两个方法,
- 接着声明了个内部类stub继承Binder(实现ILoadAidlInterface接口),这个stub不用想的太复杂,他就是个Binder。
- 在这个stub内部有个静态的标识DESCRIPTOR,作为Binder的唯一标识,这里是:”com.example.aidl_service.ILoadAidlInterface”;
- stub下有个方法asInterface(IBinder obj)这个方法是不是很熟悉?没错,这个就是客户端绑定服务之后调用的方法ILoadAidlInterface.Stub.asInterface(service);
- 那么他是干什么的呢?我们观察这个方法里面做了个判断,它判断客户端和服务器是否位于同一进程,如果是就返回服务端的stub对象本身。但是这里我们不是在同一进程,所以它返回的是封装后的stub.proxy对象。代码:return new com.example.aidl_service.ILoadAidlInterface.Stub.Proxy(obj);
- 进入到proxy:我们在client里请求的是load方法,所以这里调用proxy里的load方法,这个方法运行在客户端,首先他把username和pwd这两个参数传递到了_data对象里,再通过transact方法来发起RPC(远程过程调用),同时线程挂起,然后服务端的onTransact方法执行。
- onTransact执行在服务端的Binder线程池中,方法别调用后通过刚刚定义的标识来判断执行那个方法,这里执行load方法,把username和pwd从_data中取出调用load方法,如果有返回值的话就向_reply中写入返回值。如果此方法返回false,那么客户端请求失败,可以在这里做权限验证
- 那么proxy的transact的RPC过程返回后,线程继续,从_reply中取出数据(如果有的话)。自此,整个流程就完成了。
最后在简单的阐述下过程(因为很多人肯定晕了):首先在服务端的LoadService下我创建了一个Binder对象mBinder,继承自ILoadAidlInterface实现了里面load方法(其实就是打印了一个log),那么客户端bindservice的时候会拿到这个mBinder,通过Stub.asInterface(mBinder)把binder传了进去,由于这里是跨进程,那么执行asinterface方法就是把mBinder在传给本地的proxy(代理)下的mRemote,就是通过mRemote执行远程函数load的调用,执行在服务端的Binder线程池中的onTransact就会调用load方法并返回结果,跨进程就成功了。