一,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());}}  }


客户端。客户端的Music.java,Music.aidl,RemoteMusic.aidl,这三个文件请从服务器端copy过来,连外面的包也一起copy过来,确保一模一样。

客户端绑定服务和发送请求的代码就不贴了,第三点就已经说过了。

这里有几个点要注意的:

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 方式通信,更为简单易用(具体请参考上文)。

更多相关文章

  1. 浅谈Android(安卓)ANR在线监控原理
  2. Android(安卓)Camera数据流分析全程记录(非overlay方式)
  3. [置顶] 对Android(安卓)MVVM的理解
  4. 理解 Java 的 GC 与 幽灵引用
  5. 新浪微博客户端源码 android
  6. 【android】android短信数据库表
  7. Android快速集成框架:MVP+Dagger+主流框架
  8. android Fragment与Activity交互,互相发数据(附图详解)
  9. Android操作SQLite数据库

随机推荐

  1. Google Map Android(安卓)v2开发: 安装运
  2. Intent机制详解
  3. ASM (Android(安卓)Screen Monitor) Andr
  4. 开箱即用!Android四款系统架构工具
  5. android 进程和线程
  6. Win 10 下 android studio显示 Intel hax
  7. Android(安卓)实用工具Hierarchy Viewer
  8. Android客户端通过socket与服务器通信
  9. android – 多屏幕适配相关
  10. C#、Golang、Python、Java(Android)之间Des