android aidl通讯详解
一,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 {@Overridepublic 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中,这就形成了一次交互。
mRemote是远程对象,transact方法会执行onTransact方法@Overridepublic 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();}}
@Overridepublic 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;
RemoteMusic.aidl
package com.marttinli.aidl;import com.marttinli.aidl.Music;interface RemoteMusic{Music getMusic();void uploadMusic(in Music music);}
Music.java
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;}@Overridepublic int describeContents() {// TODO Auto-generated method stubreturn 0;}@Overridepublic void writeToParcel(Parcel dest, int flags) {// TODO Auto-generated method stubdest.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; } }; }
MusiceService.java
public class MusicService extends Service {MyBinder mBinder = new MyBinder();@Overridepublic IBinder onBind(Intent intent) {// TODO Auto-generated method stubreturn mBinder;}private class MyBinder extends RemoteMusic.Stub{ public Music getMusic() throws RemoteException { Music music = new Music(); music.setId(1) ; music.setName("沉默是金") ; return music; }@Overridepublic void uploadMusic(Music music) throws RemoteException {// TODO Auto-generated method stubLog.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() {@Overridepublic void onChanged(Music music) throws RemoteException {// TODO Auto-generated method stubSystem.out.println("music:"+music.getName());}};
@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// TODO Auto-generated method stubLog.d("marttinli","connected successful");mService = RemoteMusic.Stub.asInterface(service); try {mService.registerMusic(mRemoteMusicCallback);} catch (RemoteException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
在取消ServiceConnection绑定的时候先取消注册:
private void unBindService(){if (mService!=null) {if (mRemoteMusicCallback!=null&& mRemoteMusicCallback.asBinder().isBinderAlive()) {try {mService.unRegisterMusic(mRemoteMusicCallback);} catch (RemoteException e) {// TODO Auto-generated catch blocke.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; }@Overridepublic void uploadMusic(Music music) throws RemoteException {// TODO Auto-generated method stubLog.d("marttinli", "uploadMusic music:"+music.getName());}@Overridepublic void registerMusic(RemoteMusicCallback callback)throws RemoteException {// TODO Auto-generated method stubmCallbacks.add(callback); notifyListeners();//这是一个段伪代码}@Overridepublic void unRegisterMusic(RemoteMusicCallback callback)throws RemoteException {// TODO Auto-generated method stubmCallbacks.remove(callback);}}
notifyListeners模拟数据发送:
private void notifyListeners() throws RemoteException{new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubint 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 blocke1.printStackTrace();}try {Thread.sleep(3000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.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 方式通信,更为简单易用(具体请参考上文)。
更多相关文章
- 浅谈Android(安卓)ANR在线监控原理
- Android(安卓)Camera数据流分析全程记录(非overlay方式)
- [置顶] 对Android(安卓)MVVM的理解
- 理解 Java 的 GC 与 幽灵引用
- 新浪微博客户端源码 android
- 【android】android短信数据库表
- Android快速集成框架:MVP+Dagger+主流框架
- android Fragment与Activity交互,互相发数据(附图详解)
- Android操作SQLite数据库