Android · 2015年1月20日 0

Android学习小Demo(23)Aidl实现进程间通信

我们知道,Android是靠Binder机制来实现进程间的通信,而上一篇文章中,我们利用AIDL,简单地从代码方面的角度讲解了在服务端中的Binder的存在形式,是以服务的实现存在的,而在客户端,则是以代理的形式,实现存在的只是一个关于服务端的Binder实现的引用。

理论上的东西我们要去学习掌握,但是也不能忽略了实际的动手能力,对吧。

今天,我们就一步一步地利用我们所了解地关于AIDL的知识来实现一个跨进程通信的例子。

在Android的上层应用中,每一个App都是一个单独的进程,所以,要实现跨进程通信,我们需要至少有2个进程,一个代表服务端,一个代表客户端,所以我们会创建2个项目,来分别代表服务端和客户端。

服务端是提供服务的,得先提供服务,才能让客户端来享受服务,对吧。

服务端

我们创建一个服务端的项目,然后创建我们的aidl文件,毕竟只是一个Demo,所以我先从简单入手,方法不要太多,就叫IDemoService.aidl, 如下:

  1. package com.lms.service;
  2. interface IDemoService {
  3.     void invoke();
  4. }

我们将其放在com.lms.service的包名目录下,表明其是提供服务的,当我们编译一下项目之后,就会发现在gen目录下面同样的包名下,生成了一个IDemoService.java的文件,如下:

而其生成的IDemoService.java文件内容如下,跟上一篇文章中所展现的其实一样,就是一个Stub类和一个Proxy类,如下:

  1. /*
  2.  * This file is auto-generated.  DO NOT MODIFY.
  3.  * Original file: F:\\workspace_android\\AidlDemoClient\\src\\com\\lms\\service\\IDemoService.aidl
  4.  */
  5. package com.lms.service;
  6. public interface IDemoService extends android.os.IInterface {
  7.     /** Local-side IPC implementation stub class. */
  8.     public static abstract class Stub extends android.os.Binder implements
  9.             com.lms.service.IDemoService {
  10.         private static final java.lang.String DESCRIPTOR = “com.lms.service.IDemoService”;
  11.         /** Construct the stub at attach it to the interface. */
  12.         public Stub() {
  13.             this.attachInterface(this, DESCRIPTOR);
  14.         }
  15.         /**
  16.          * Cast an IBinder object into an com.lms.service.IDemoService
  17.          * interface, generating a proxy if needed.
  18.          */
  19.         public static com.lms.service.IDemoService asInterface(
  20.                 android.os.IBinder obj) {
  21.             if ((obj == null)) {
  22.                 return null;
  23.             }
  24.             android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  25.             if (((iin != null) && (iin instanceof com.lms.service.IDemoService))) {
  26.                 return ((com.lms.service.IDemoService) iin);
  27.             }
  28.             return new com.lms.service.IDemoService.Stub.Proxy(obj);
  29.         }
  30.         @Override
  31.         public android.os.IBinder asBinder() {
  32.             return this;
  33.         }
  34.         @Override
  35.         public boolean onTransact(int code, android.os.Parcel data,
  36.                 android.os.Parcel reply, int flags)
  37.                 throws android.os.RemoteException {
  38.             switch (code) {
  39.             case INTERFACE_TRANSACTION: {
  40.                 reply.writeString(DESCRIPTOR);
  41.                 return true;
  42.             }
  43.             case TRANSACTION_invoke: {
  44.                 data.enforceInterface(DESCRIPTOR);
  45.                 this.invoke();
  46.                 reply.writeNoException();
  47.                 return true;
  48.             }
  49.             }
  50.             return super.onTransact(code, data, reply, flags);
  51.         }
  52.         private static class Proxy implements com.lms.service.IDemoService {
  53.             private android.os.IBinder mRemote;
  54.             Proxy(android.os.IBinder remote) {
  55.                 mRemote = remote;
  56.             }
  57.             @Override
  58.             public android.os.IBinder asBinder() {
  59.                 return mRemote;
  60.             }
  61.             public java.lang.String getInterfaceDescriptor() {
  62.                 return DESCRIPTOR;
  63.             }
  64.             @Override
  65.             public void invoke() throws android.os.RemoteException {
  66.                 android.os.Parcel _data = android.os.Parcel.obtain();
  67.                 android.os.Parcel _reply = android.os.Parcel.obtain();
  68.                 try {
  69.                     _data.writeInterfaceToken(DESCRIPTOR);
  70.                     mRemote.transact(Stub.TRANSACTION_invoke, _data, _reply, 0);
  71.                     _reply.readException();
  72.                 } finally {
  73.                     _reply.recycle();
  74.                     _data.recycle();
  75.                 }
  76.             }
  77.         }
  78.         static final int TRANSACTION_invoke = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
  79.     }
  80.     public void invoke() throws android.os.RemoteException;
  81. }

既然已经通过aidl生成了这份接口文件,那么根据上一篇文章所讲的,我们接下来就要来实现服务端这边提供的服务了。

我们可以想到,当我们在使用Android中的服务接口的时候,当其需要与其他模块进行相互通信的时候,我们一般是会通过绑定的形式实现的,而通过此种形式的实现,我们都会实现一个Binder,比如我们之前音乐播放器的实现,如下:

  1. class NatureBinder extends Binder{
  2.         /**
  3.          * 唱吧,有人想听
  4.          */
  5.         public void startPlay(int currentMusic, int currentPosition){}
  6.         /**
  7.          * 别唱了
  8.          */
  9.         public void stopPlay(){}
  10.         /**
  11.          * 后一首
  12.          */
  13.         public void toNext(){}
  14.         /**
  15.          * 前一首
  16.          */
  17.         public void toPrevious(){}
  18.         /**
  19.          * 有人改变模式了,我得把它记下来 
  20.          */
  21.         public void changeMode(){}
  22.         /**
  23.          * 告诉别人,你现在到底是顺序播放,还是随机乱弹
  24.          * MODE_ONE_LOOP = 1;
  25.          * MODE_ALL_LOOP = 2;
  26.          * MODE_RANDOM = 3;
  27.          * MODE_SEQUENCE = 4; 
  28.          * @return
  29.          */
  30.         public int getCurrentMode(){}
  31.         /**
  32.          * 告诉调用者,到底有没有在做事。。。
  33.          * @return
  34.          */
  35.         public boolean isPlaying(){}
  36.         /**
  37.          * 要告诉调用者,当前播哪首歌了,歌多长啊
  38.          */
  39.         public void notifyActivity(){}
  40.         /**
  41.          * 有人拖动Seekbar了,要告诉service去改变播放的位置
  42.          * @param progress
  43.          */
  44.         public void changeProgress(int progress){}
  45.     }

在上面这种情况下,Service是与同一个进程中的其他线程或者模块进行通信的,而在我们这种跨进程的服务中是不是也可以类似自己去实现我们AIDL中的定义的Stub类呢?

因为Stub类也是继承Binder的,而Binder又是实现了IBinder接口的,所以可以在调用Service的onBinde方法的时候,就返回我们实现的服务呀,对吧。

所以我们自定义一个服务,就叫做DemoService,如下:

  1. package com.lms.service;
  2. import android.app.Service;
  3. import android.content.Intent;
  4. import android.os.IBinder;
  5. public class DemoService extends Service{
  6.     @Override
  7.     public IBinder onBind(Intent intent) {
  8.         // TODO Auto-generated method stub
  9.         return null;
  10.     }
  11. }

Service是一个抽象类,继承它必须实现其抽象方法 onBinde,其会返回一个IBinder对象,那么很显然,我们就需要在这个方法里返回我们的Binder了。

下面,我们先简单实现我们的Binder,如下:

  1. public class DemoService extends Service {
  2.     private static final String TAG = “DemoService”;
  3.     public final IDemoService.Stub mBinder = new IDemoService.Stub() {
  4.         @Override
  5.         public void invoke() throws RemoteException {
  6.             Log.v(TAG, “Invoke -> Current Process From Server: “ + android.os.Process.myPid());
  7.         }
  8.     };
  9.     @Override
  10.     public IBinder onBind(Intent intent) {
  11.         Log.v(TAG, “Bind -> Current Process From Server: “ + android.os.Process.myPid());
  12.         return mBinder;
  13.     }
  14.     public boolean onUnbind(Intent intent){
  15.         Log.v(TAG, “Unbind -> Current Process From Server: “ + android.os.Process.myPid());
  16.         return super.onUnbind(intent);
  17.     }
  18. }

 

最后,在AndroidManifest.xml中注册Service,如下:

  1. <service
  2.     android:name=“com.lms.service.DemoService”
  3.     android:exported=“true”
  4.     android:process=“:remote” >
  5.     <intent-filter>
  6.         <category android:name=“android.intent.category.DEFAULT” />
  7.         <action android:name=“com.lms.service.DemoService” />
  8.     </intent-filter>
  9. </service>

在这里,我们还需要定义一个intent-filter,以便Android系统在第三方调用的时候,可以通过隐式的intent来匹配到这个服务,同时还要注意两点:

1)android:process 必须设置值,至于什么值,好像关系并不大,表明可接受远程调用

2)android:exported设置为true,这表明此组件可供第三方调用,不过对于Service来说,这默认就是true的,所以我们不设也可以。但是对于Activity等来说,其值是默认为false的,这时候,如果我们想让我们的Activity为第三方调用,就要显式设置为true了。通常,在接入第三方插件的时候,我们会用到这个属性。

OK,到这里为止,我们已经简单地实现了我们的服务端服务了,接下来我们再继续实现我们客户端。

再创建一个项目,AidlDemoClient,然后将我们在服务端的这份aidl文件,包括包名等,复制到客户端代码中,编译一下,其会生成一份一样的java文件,不过此时,我们不再需要去实现Stub类了,我们只是需要去调用这个类就可以了,如下:

接着,我们只需要像平常一样调用我们的Service就可以了,所不同的是,由于这服务并不在我们进程内,所以我们必须用隐式的Intent去调用,由系统去匹配服务,从而调起,这也是为什么我们在服务端注册Service的时候,需要设置IntentFilter的原因了。

  1. public class MainActivity extends ActionBarActivity {
  2.     private static final String TAG = “DemoService”;
  3.     private static final String ACTION_BIND_SERVICE = “com.lms.service.DemoService”;
  4.     private IDemoService mDemoService;
  5.     private ServiceConnection mServiceConnection = new ServiceConnection() {
  6.         @Override
  7.         public void onServiceDisconnected(ComponentName name) {
  8.             mDemoService = null;
  9.         }
  10.         @Override
  11.         public void onServiceConnected(ComponentName name, IBinder service) {
  12.             mDemoService = IDemoService.Stub.asInterface(service);
  13.             try {
  14.                 mDemoService.invoke();
  15.             } catch (RemoteException e) {
  16.                 // TODO Auto-generated catch block
  17.                 e.printStackTrace();
  18.             }
  19.         }
  20.     };
  21.     @Override
  22.     protected void onCreate(Bundle savedInstanceState) {
  23.         super.onCreate(savedInstanceState);
  24.         setContentView(R.layout.activity_main);
  25.         Button btnHelloWorld = (Button) findViewById(R.id.btnBind);
  26.         btnHelloWorld.setOnClickListener(new OnClickListener() {
  27.             @Override
  28.             public void onClick(View v) {
  29.                 Intent intentService = new Intent(ACTION_BIND_SERVICE);
  30.                 intentService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  31.                 bindService(intentService, mServiceConnection, BIND_AUTO_CREATE);
  32.                 Log.v(TAG, “Current Process From Client: “ + android.os.Process.myPid());
  33.             }
  34.         });
  35.         Button btnUnbind = (Button) findViewById(R.id.btnUnbind);
  36.         btnUnbind.setOnClickListener(new OnClickListener() {
  37.             @Override
  38.             public void onClick(View v) {
  39.                 if(mServiceConnection != null){
  40.                     unbindService(mServiceConnection);
  41.                     mServiceConnection = null;
  42.                 }
  43.             }
  44.         });
  45.     }

 

我们定义了两个按钮,当点击的时候,分别通过隐式的intent去启动服务并触发方法和解除绑定,我们可以看到在onServiceConnected中,我们利用了IDemoService.Stub.asInterface() 方法来实例化这个Binder,至于asterface是如何跨进程找到这个binder的,我们上一篇文章中已经简单地讲了一下。

接下来,我们可以启动这两个应用,来试一下啦。

从上面的画面中,我们可以看到,点击事件发生的线程ID是685,而真正接受服务和触发Invoke方法的是在进程676上面,于是我们就实现了跨进程通信了。

当然在这里,我们只是做了一个很简单的调用而已,而实际上我们还能够传递参数和接受返回值,而这些我们都可以在aidl文件中定义,不过要注意的是:

1)aidl中直接提供的数据类型只支持基本的数据类型和String

2)如果要支持我们自己的自定义的对象,我们的对象必须也通过aidl来定义,而且实现parceable接口,如下:

  1. package com.lms.aidl;
  2. import java.util.List;
  3. import com.lms.aidl.Bean;
  4. interface ITestService {
  5.     List<Bean> getBean();
  6.     void addBean(in Bean bean);
  7. }

而Bean的aidl文件如下:

  1. package com.lms.aidl;
  2. parcelable Bean;

其具体实现如下:

  1. package com.lms.aidl;
  2. import android.os.Parcel;
  3. import android.os.Parcelable;
  4. public class Bean implements Parcelable {
  5.     private int i;
  6.     private String str;
  7.     public Bean(){
  8.     }
  9.     /**
  10.      * @return the i
  11.      */
  12.     public int getI() {
  13.         return i;
  14.     }
  15.     /**
  16.      * @param i the i to set
  17.      */
  18.     public void setI(int i) {
  19.         this.i = i;
  20.     }
  21.     /**
  22.      * @return the str
  23.      */
  24.     public String getStr() {
  25.         return str;
  26.     }
  27.     /**
  28.      * @param str the str to set
  29.      */
  30.     public void setStr(String str) {
  31.         this.str = str;
  32.     }
  33.     public Bean(Parcel in) {
  34.         readFromParcel(in);
  35.     }
  36.     public static final Parcelable.Creator<Bean> CREATOR = new Parcelable.Creator<Bean>() {
  37.         public Bean createFromParcel(Parcel in) {
  38.             return new Bean(in);
  39.         }
  40.         public Bean[] newArray(int size) {
  41.             return new Bean[size];
  42.         }
  43.     };
  44.     @Override
  45.     public int describeContents() {
  46.         return 0;
  47.     }
  48.     @Override
  49.     public void writeToParcel(Parcel dest, int flags) {
  50.         dest.writeInt(i);
  51.         dest.writeString(str);
  52.     }
  53.     public void readFromParcel(Parcel in) {
  54.         i = in.readInt();
  55.         str = in.readString();
  56.     }
  57. }

而我们可以从其通信的过程中拿到服务端的数据,如下:

好了,关于aidl的使用,我们就简单地介绍到这里吧,我会把两份Demo的代码都放上来,大家如果有兴趣,可以自己拿去参考一下。

简单版(只是调用方法):AidlDemo简单版

进阶版(进程间通信并且传递自定义对象):AidlDemo进阶版

转自:http://blog.csdn.net/linmiansheng/article/details/42835229

Share this: