Android学习小Demo(23)Aidl实现进程间通信
我们知道,Android是靠Binder机制来实现进程间的通信,而上一篇文章中,我们利用AIDL,简单地从代码方面的角度讲解了在服务端中的Binder的存在形式,是以服务的实现存在的,而在客户端,则是以代理的形式,实现存在的只是一个关于服务端的Binder实现的引用。
理论上的东西我们要去学习掌握,但是也不能忽略了实际的动手能力,对吧。
今天,我们就一步一步地利用我们所了解地关于AIDL的知识来实现一个跨进程通信的例子。
在Android的上层应用中,每一个App都是一个单独的进程,所以,要实现跨进程通信,我们需要至少有2个进程,一个代表服务端,一个代表客户端,所以我们会创建2个项目,来分别代表服务端和客户端。
服务端是提供服务的,得先提供服务,才能让客户端来享受服务,对吧。
服务端
我们创建一个服务端的项目,然后创建我们的aidl文件,毕竟只是一个Demo,所以我先从简单入手,方法不要太多,就叫IDemoService.aidl, 如下:
package com.lms.service; interface IDemoService { void invoke(); }
我们将其放在com.lms.service的包名目录下,表明其是提供服务的,当我们编译一下项目之后,就会发现在gen目录下面同样的包名下,生成了一个IDemoService.java的文件,如下:
而其生成的IDemoService.java文件内容如下,跟上一篇文章中所展现的其实一样,就是一个Stub类和一个Proxy类,如下:
/* * This file is auto-generated. DO NOT MODIFY. * Original file: F:\\workspace_android\\AidlDemoClient\\src\\com\\lms\\service\\IDemoService.aidl */ package com.lms.service; public interface IDemoService extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.lms.service.IDemoService { private static final java.lang.String DESCRIPTOR = "com.lms.service.IDemoService"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.lms.service.IDemoService * interface, generating a proxy if needed. */ public static com.lms.service.IDemoService asInterface( android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.lms.service.IDemoService))) { return ((com.lms.service.IDemoService) iin); } return new com.lms.service.IDemoService.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_invoke: { data.enforceInterface(DESCRIPTOR); this.invoke(); reply.writeNoException(); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.lms.service.IDemoService { 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; } @Override public void invoke() throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_invoke, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } } static final int TRANSACTION_invoke = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public void invoke() throws android.os.RemoteException; }
既然已经通过aidl生成了这份接口文件,那么根据上一篇文章所讲的,我们接下来就要来实现服务端这边提供的服务了。
我们可以想到,当我们在使用Android中的服务接口的时候,当其需要与其他模块进行相互通信的时候,我们一般是会通过绑定的形式实现的,而通过此种形式的实现,我们都会实现一个Binder,比如我们之前音乐播放器的实现,如下:
class NatureBinder extends Binder{ /** * 唱吧,有人想听 */ public void startPlay(int currentMusic, int currentPosition){} /** * 别唱了 */ public void stopPlay(){} /** * 后一首 */ public void toNext(){} /** * 前一首 */ public void toPrevious(){} /** * 有人改变模式了,我得把它记下来 */ public void changeMode(){} /** * 告诉别人,你现在到底是顺序播放,还是随机乱弹 * MODE_ONE_LOOP = 1; * MODE_ALL_LOOP = 2; * MODE_RANDOM = 3; * MODE_SEQUENCE = 4; * @return */ public int getCurrentMode(){} /** * 告诉调用者,到底有没有在做事。。。 * @return */ public boolean isPlaying(){} /** * 要告诉调用者,当前播哪首歌了,歌多长啊 */ public void notifyActivity(){} /** * 有人拖动Seekbar了,要告诉service去改变播放的位置 * @param progress */ public void changeProgress(int progress){} }
在上面这种情况下,Service是与同一个进程中的其他线程或者模块进行通信的,而在我们这种跨进程的服务中是不是也可以类似自己去实现我们AIDL中的定义的Stub类呢?
因为Stub类也是继承Binder的,而Binder又是实现了IBinder接口的,所以可以在调用Service的onBinde方法的时候,就返回我们实现的服务呀,对吧。
所以我们自定义一个服务,就叫做DemoService,如下:
package com.lms.service; import android.app.Service; import android.content.Intent; import android.os.IBinder; public class DemoService extends Service{ @Override public IBinder onBind(Intent intent) { // TODO Auto-generated method stub return null; } }
Service是一个抽象类,继承它必须实现其抽象方法 onBinde,其会返回一个IBinder对象,那么很显然,我们就需要在这个方法里返回我们的Binder了。
下面,我们先简单实现我们的Binder,如下:
public class DemoService extends Service { private static final String TAG = "DemoService"; public final IDemoService.Stub mBinder = new IDemoService.Stub() { @Override public void invoke() throws RemoteException { Log.v(TAG, "Invoke -> Current Process From Server: " + android.os.Process.myPid()); } }; @Override public IBinder onBind(Intent intent) { Log.v(TAG, "Bind -> Current Process From Server: " + android.os.Process.myPid()); return mBinder; } public boolean onUnbind(Intent intent){ Log.v(TAG, "Unbind -> Current Process From Server: " + android.os.Process.myPid()); return super.onUnbind(intent); } }
最后,在AndroidManifest.xml中注册Service,如下:
<service android:name="com.lms.service.DemoService" android:exported="true" android:process=":remote" > <intent-filter> <category android:name="android.intent.category.DEFAULT" /> <action android:name="com.lms.service.DemoService" /> </intent-filter> </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的原因了。
public class MainActivity extends ActionBarActivity { private static final String TAG = "DemoService"; private static final String ACTION_BIND_SERVICE = "com.lms.service.DemoService"; private IDemoService mDemoService; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { mDemoService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { mDemoService = IDemoService.Stub.asInterface(service); try { mDemoService.invoke(); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btnHelloWorld = (Button) findViewById(R.id.btnBind); btnHelloWorld.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intentService = new Intent(ACTION_BIND_SERVICE); intentService.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); bindService(intentService, mServiceConnection, BIND_AUTO_CREATE); Log.v(TAG, "Current Process From Client: " + android.os.Process.myPid()); } }); Button btnUnbind = (Button) findViewById(R.id.btnUnbind); btnUnbind.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(mServiceConnection != null){ unbindService(mServiceConnection); mServiceConnection = null; } } }); }
我们定义了两个按钮,当点击的时候,分别通过隐式的intent去启动服务并触发方法和解除绑定,我们可以看到在onServiceConnected中,我们利用了IDemoService.Stub.asInterface() 方法来实例化这个Binder,至于asterface是如何跨进程找到这个binder的,我们上一篇文章中已经简单地讲了一下。
接下来,我们可以启动这两个应用,来试一下啦。
从上面的画面中,我们可以看到,点击事件发生的线程ID是685,而真正接受服务和触发Invoke方法的是在进程676上面,于是我们就实现了跨进程通信了。
当然在这里,我们只是做了一个很简单的调用而已,而实际上我们还能够传递参数和接受返回值,而这些我们都可以在aidl文件中定义,不过要注意的是:
1)aidl中直接提供的数据类型只支持基本的数据类型和String
2)如果要支持我们自己的自定义的对象,我们的对象必须也通过aidl来定义,而且实现parceable接口,如下:
package com.lms.aidl; import java.util.List; import com.lms.aidl.Bean; interface ITestService { List<Bean> getBean(); void addBean(in Bean bean); }
而Bean的aidl文件如下:
package com.lms.aidl; parcelable Bean;
其具体实现如下:
package com.lms.aidl; import android.os.Parcel; import android.os.Parcelable; public class Bean implements Parcelable { private int i; private String str; public Bean(){ } /** * @return the i */ public int getI() { return i; } /** * @param i the i to set */ public void setI(int i) { this.i = i; } /** * @return the str */ public String getStr() { return str; } /** * @param str the str to set */ public void setStr(String str) { this.str = str; } public Bean(Parcel in) { readFromParcel(in); } public static final Parcelable.Creator<Bean> CREATOR = new Parcelable.Creator<Bean>() { public Bean createFromParcel(Parcel in) { return new Bean(in); } public Bean[] newArray(int size) { return new Bean[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(i); dest.writeString(str); } public void readFromParcel(Parcel in) { i = in.readInt(); str = in.readString(); } }
而我们可以从其通信的过程中拿到服务端的数据,如下:
好了,关于aidl的使用,我们就简单地介绍到这里吧,我会把两份Demo的代码都放上来,大家如果有兴趣,可以自己拿去参考一下。
简单版(只是调用方法):AidlDemo简单版
进阶版(进程间通信并且传递自定义对象):AidlDemo进阶版
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。