一,aidl进程通讯介绍
Android 是进程间内存是分离的,因此需要将对象分解成操作系统可理解的元数据,并将此打包让操作系统帮忙传递对象到另一个进程。这个过程是十分复杂繁重的,因此 Google 定义了 AIDL(Android Interface Definition Language)帮助开发者简化工作。
二,aidl通讯的实现
实现步骤:
1,创建.aidl文件-该文件(YourInterface.aidl)定义了客户端可用的方法和数据的接口。
2,在服务端和客户端makefile文件中都加入.aidl文件——Eclipse中的ADT插件自动将aidl文件编译成java代码生成在gen文件夹下
3,Implement your interface methods - The AIDL compiler creates an interface in the Java programming language from your AIDL interface. This interface has an inner abstract class named Stub that inherits the interface (and implements a few additional methods necessary for the IPC call). You must create a class that extends YourInterface.Stub and implements the methods you declared in your .aidl file.
实现你定义aidl接口中的内部抽象类Stub。(这一步骤建议联系下面的代码理解会容易一些)
class Stub extends Binder implements binder.aidl.AIDLService (这里的代码在gen文件夹下)
4,服务端继承Service并且重载Service.onBind(Intent)以返回实现了接口的对象实例
具体实现
IMyService.aidl:
package com.myapp.aidl;
interface IMyService {
public void playMusic(int id);
int getName(int id);
}
这里的设置意味着:客户端可以向服务器发送指令和获取数据。
其中aidl中支持的参数类型为:基本类型(int、long、char、boolean等),String,CharSequence,List,Map。若想要自定义对象传输,后面会详细讲到。
定义好aidl文件后,请在服务端和客户端粘贴一份一模一样的aidl文件。
Eclipse在编译的时候会自动在gen下面为我们生成了一个代理类Stub,这个Stub类就是一个普通的Binder,只不过它实现了我们定义的aidl接口。通过它的静态方法asInterface我们就可以在客户端中得到IMyService的实例,进而通过实例来调用其他方法。
客户端:
Intent startIntent = new Intent();
ComponentName componentName = new ComponentName(
PACKAGE_REMOTE_SERVICE,
NAME_REMOTE_SERVICE);
startIntent.setComponent(componentName);
bindService(startIntent, mConnection, Context.BIND_AUTO_CREATE);
这里的PACKAGE_REMOTE_SERVICE,NAME_REMOTE_SERVICE就是指定的service。
IMyService mService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log("connect service");
mService = IMyService.Stub.asInterface(service);
try {
int name = mService.getName(1);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
Log("disconnect service");
mService = null;
}
};
服务端:
public class TestService extends Service {
@Override
public IBinder onBind(Intent arg0) {
System.out.println("Service onBind");
return mBinder;
}
private final IMyService.Stub mBinder = new IMyService.Stub() {
@Override
String getName(int id){
String name = findBy(id);//findBy()这是伪代码
return name;
}
@Override
public void playMusic(int id){
playMusic(int id);//playMusic()也是伪代码;
}
};
}
Manifest.xml中记得配置service,注意:exported="true"要配置为true,不然客户端是没有权限访问服务的。
如此一个基本的aidl通讯就完成了。
原理分析
那么接下来我们开始分析在gen目录下自动生成的文件到底是什么?
这里有几个重要的类和方法:asInterface(IBinder obj) , Proxy ,transact(int code, Parcel data, Parcel reply, int flags) ,onTransact(int code, Parcel data,Parcel reply, int flags)
这里涉及到Parcel序列化的一些技术,晚些会专门开一章来讲,这里你先明白Parcel对象是用来在进程间传递的数据对象,其他的基本类型和对象最终都要序列化程Parcel数据才能在进程间传递。
还记得客户端调用的IMyService.Stub.asInterface(service)吧,就在这里
public static com.marttinli.aidl.IMysService asInterface(
android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.marttinli.aidl.IMysService))) {
return ((com.marttinli.aidl.IMysService) iin);
}
return new com.marttinli.aidl.IMysService.Stub.Proxy(
obj);
}
这里返回了一个Proxy,并把service作为输入。而Proxy正是完成了对我们在aidl接口中定义接口的实现。这里Client把参数转换成Parcel(_data)传递到了Service,而在Server端又会把返回数据保存到_reply中,这就形成了一次交互。
@Override
public void playMusic(int id)
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.writeStrongBinder((((id != null)) ? (id
.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_playMusic, _data,
_reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@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_playMusic: {
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
this.playMusic(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
而到了onTransact()中,已经是开始和service进行交互了,_data最终转化成了他原来的数据格式(这里是int),然后调用playMusic(),service端实现了playMusic()。
三,aidl通讯传递对象数据
aidl的数据传递本来也就是把基本数据类型转化成Parelable作为跨进称数据传输的格式。
所以一个对象想要传输,也只需要把对象转成Parelable 就可以跨进称传输。
我还专门为此写了个demo,确保可行。
服务端3个文件,我假定我需要传输的对象为Music:Music.java,Music.aidl,RemoteMusic.aidl
Music.aidl
parcelable Music;
package com.marttinli.aidl;
import com.marttinli.aidl.Music;
interface RemoteMusic{
Music getMusic();
void uploadMusic(in Music music);
}
public class Music implements Parcelable {
int id;
String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int describeContents() {
// TODO Auto-generated method stub
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
// TODO Auto-generated method stub
dest.writeInt(id);
dest.writeString(name);
}
/**
* 在想要进行序列号传递的实体类内部一定要声明该常量。常量名只能是CREATOR,类型也必须是
* Parcelable.Creator
*/
public static final Parcelable.Creator CREATOR = new Creator() {
/**
* 创建一个要序列号的实体类的数组,数组中存储的都设置为null
*/
@Override
public Music[] newArray(int size) {
return new Music[size];
}
/***
* 根据序列号的Parcel对象,反序列号为原本的实体对象
* 读出顺序要和writeToParcel的写入顺序相同
*/
@Override
public Music createFromParcel(Parcel source) {
int id = source.readInt();
String name = source.readString();
Music music = new Music();
music.setId(id);
music.setName(name);
return music;
}
};
}
public class MusicService extends Service {
MyBinder mBinder = new MyBinder();
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return mBinder;
}
private class MyBinder extends RemoteMusic.Stub{
public Music getMusic() throws RemoteException {
Music music = new Music();
music.setId(1) ;
music.setName("沉默是金") ;
return music;
}
@Override
public void uploadMusic(Music music) throws RemoteException {
// TODO Auto-generated method stub
Log.d("marttinli", "uploadMusic music:"+music.getName());
}}
}
客户端绑定服务和发送请求的代码就不贴了,第三点就已经说过了。
这里有几个点要注意的:
1,Music.java中注释也写了,在序列化时,想要进行序列号传递的实体类内部一定要声明CREATOR 常量。常量名只能是CREATOR,类型也必须是 Parcelable.Creator
2,Music.java在序列化时,根据序列号的Parcel对象,反序列号为原本的实体对象 读出顺序要和writeToParcel的写入顺序相同
3,标识符in,out,inout,类似c语言里的用法,in表示输入参数,这个很好理解。out表示该参数做为输入,也是作为输出;比如change(int value),输入value=1,传到service后,service重新复制value=2,那么在客户端这个value==2.
4,Music.java,Music.aidl也要名字一样,后缀不一样而已。
四,aidl实现数据监听
有个问题就是,目前我们已经实现了客户端对服务端发送指令和获取数据,但有一种情况:当你发送指令后,服务在处理数据要执行耗时操作才能返回数据。对,这个时候你应该很容易可以想到再定义一个接收数据的接口,只要服务端处理完数据调用该接口方法,在客户端实现的方法就可以获取到对应数据。
实现步骤(这里接着上面的代码写):
1,再定义一个RemoteMusicCallback.aidl,顾名思义,这个是服务端执行完耗时操作后对客户端的callback。这里的标识符一定是in,至于原因,稍后会讲到。
interface RemoteMusicCallback{
void onChanged(in Music music);
}
2,既然有了Callback,那么必须要声明注册监听和反注册的方法,这里放在原来的RemoteMusic.aidl中是最合适的。那么RemoteMusic.adil变为:
interface RemoteMusic{
Music getMusic();
void uploadMusic(in Music music);
void registerMusic(in RemoteMusicCallback callback);
void unRegisterMusic(in RemoteMusicCallback callback);
}
3,然后把Music.java,Music.aidl,RemoteMusic.aidl,RemoteMusicCallback.aidl,连同包一起复制到客户端和服务端。
客户端:
加入注册music变化的callback:
RemoteMusicCallback mRemoteMusicCallback = new RemoteMusicCallback.Stub() {
@Override
public void onChanged(Music music) throws RemoteException {
// TODO Auto-generated method stub
System.out.println("music:"+music.getName());
}
};
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
Log.d("marttinli","connected successful");
mService = RemoteMusic.Stub.asInterface(service);
try {
mService.registerMusic(mRemoteMusicCallback);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void unBindService(){
if (mService!=null) {
if (mRemoteMusicCallback!=null&& mRemoteMusicCallback.asBinder().isBinderAlive()) {
try {
mService.unRegisterMusic(mRemoteMusicCallback);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (mConnection!=null) {
unbindService(mConnection);
}
}
}
这里有一点要注意的,我们是提供service的,谁注册监听我的数据变化,我就要给谁提供服务。那么必然存在一种可能,很多应用同时监听我的服务,那么这时我就需要把他们都按顺序存放在一个队列里,等到他们监听的服务变化时,我就给他们一一推送过去。这里我用ArrayList来存放队列,在MyBinder下实现队列的添加和删除:
private class MyBinder extends RemoteMusic.Stub{
public Music getMusic() throws RemoteException {
Music music = new Music();
music.setId(1) ;
music.setName("沉默是金") ;
return music;
}
@Override
public void uploadMusic(Music music) throws RemoteException {
// TODO Auto-generated method stub
Log.d("marttinli", "uploadMusic music:"+music.getName());
}
@Override
public void registerMusic(RemoteMusicCallback callback)
throws RemoteException {
// TODO Auto-generated method stub
mCallbacks.add(callback);
notifyListeners();//这是一个段伪代码
}
@Override
public void unRegisterMusic(RemoteMusicCallback callback)
throws RemoteException {
// TODO Auto-generated method stub
mCallbacks.remove(callback);
}}
notifyListeners模拟数据发送:
private void notifyListeners() throws RemoteException{
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
for (int j = 0; j < 10; j++) {
for (RemoteMusicCallback callback : mCallbacks) {
Music music = new Music();
music.setId(i);
music.setName("沉默是金 "+i);
try {
callback.onChanged(music);
} catch (RemoteException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
i++;
}
}
}).start();
}
在实际开发中实现对service的数据监听经常有这个需求,service通常用来处理一类数据,你通过service获取你想要的数据,也可以监听service数据,一旦某条你监听的数据发生变化,service就会根据注册监听的队列对象一一推送该数据过去。而你只要在监听实现中获取数据。
不知道你有没有发现,aidl里面定义的都是interface,顾名思义,aidl就是定义的接口,一个供不同进程连接的接口。他们会在gen目录下生成对应的.java文件,也是接口,是继承IInterface的接口;因为需要通过Binder跨进程传输的接口都是需要继承IInterface的。
五,aidl与Messager在实现进程通讯上的区别
一般,你只有在客户端需要访问另一进程的 Service ,且需要 Service 多纯种处理客户端请求时才有必要使用 AIDL;如果只需要在进程内和 Service 通信,只需要实现 Binder 通过 onBind() 返回对象;如果需要进程间通信但不需要并发处理请求,可考虑使用 Messenger,Messenger 底层实现和 AIDL 类似,上层采用 handler-message 方式通信,更为简单易用(具体请参考上文)。