Android(安卓)AIDL 双向调用的使用及相关原理
AIDL全称Android Interface Definition Language,一种android的接口定义语言,用于进程间通讯,我们知道android是不允许不同进程间直接共享数据的,但是有几种解决办法,比如ContentProvider,AIDL等等,那么什么情况下我们会用到AIDL呢,这里直接举一个实际应用的例子,比如应用市场,下载应用的逻辑一般放到一个service中,由于应用市场属于系统级应用,OS希望对这个应用进行保护(不被kill),但是如果全都保护起来,又太耗内存,于是就把下载的service单独写成一个进程,这样的话应用市场的service就得以保护,又不占用很多的系统资源,这种方式就直接在manifest里写android:process即可,接下来我会讲普遍的AIDL调用情况,这里我写了一个例子,模拟了应用实现客户端请求服务service下载应用,并且下载完成后回调到客户端的过程,我们来看看怎么实现的:
ipcclient工程
ipcserver工程
我们看到aidl和相关实体类文件,无论是客户端还是服务端,都需要有相同的包名,如果不相同就会报错
AppItem类:
就是一个实现了Parcelable的实体类,不多说了,下面来看看这几个aidl文件:
IDownLoadApp.aidl:
定义了IDownLoadApp接口,两个方法,startDownLoad模拟下载应用,registCallBack模拟下载完成后的回调,注意,即使AppItem跟这个接口在一个包名下,也必须import,AIDL这种语言就是这么规定的,还有一个规定,由于用到了AppItem,需要再新建一个aidl文件进行声明,AppItem.aidl:
注意:parcelable是小写,这个aidl文件是必须的,还用到了另外一个接口IDownLoadCallBack接口,所以也必读再定义一个aidl文件,IDownLoadCallBack.aidl:
这里定义了一个callBack方法用于回调,写完aidl文件之后,clean下工程,就会在build下生成相应的java文件:
其实整个aidl的调用全都是依赖于这里生成的对应的java文件,待会我们再来看这些java类,先看服务端实现:
public class AIDLService extends Service{private RemoteCallbackList<IDownLoadCallBack> mListenerList = new RemoteCallbackList<IDownLoadCallBack>(); public final String TAG = "AIDLDEMO_By_FUQIANG"; private final IDownLoadApp.Stub mAppManager = new IDownLoadApp.Stub() { @Override public void startDownLoad(AppItem app) throws RemoteException { new Thread(new DownLoadThread()).start(); } @Override public void registCallBack(IDownLoadCallBack callback) throws RemoteException { mListenerList.register(callback); } }; private class DownLoadThread implements Runnable{ @Override public void run() { try { Thread.sleep(5000); callback(); }catch (Exception e){ e.printStackTrace(); } } } private void callback() throws RemoteException{ final int N = mListenerList.beginBroadcast(); for(int i = 0 ;i < N; i++){ IDownLoadCallBack l = mListenerList.getBroadcastItem(i); if(l != null){ try{ l.callback(); }catch (Exception e){ e.printStackTrace(); } } mListenerList.finishBroadcast(); } } @Override public IBinder onBind(Intent intent) { return mAppManager; } @Override public void onCreate() { super.onCreate(); }}
客户端的实现:
public class MainActivity extends AppCompatActivity { public final String TAG = "AIDLDEMO_By_FUQIANG"; private IDownLoadApp mAppManager; public AppItem mAppItem; private boolean mBound = false; //false为未连接 true为已连接 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mAppItem = new AppItem(); } public void downLoad(View view){ if(!mBound){ attempToBindService(); Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show(); } if(mAppManager == null){ return; } try{ //获得服务端执行方法的返回值,并打印输出 mAppManager.startDownLoad(mAppItem); }catch (Exception e){ e.printStackTrace(); } } @Override protected void onStart() { super.onStart(); if (!mBound) { attempToBindService(); } } @Override protected void onStop() { super.onStop(); if (mBound) { unbindService(mServiceConnection); mBound = false; } } private void attempToBindService(){ Intent intent = new Intent(); intent.setAction("com.lypeer.aidl"); intent.setPackage("com.fq.ipc.ipcserver"); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); } private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { // TODO Auto-generated method stub if (mAppManager == null) return; mAppManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mAppManager = null; // TODO:重新绑定远程服务 attempToBindService(); } }; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e(TAG, "service connected"); try { service.linkToDeath(mDeathRecipient, 0); mAppManager = IDownLoadApp.Stub.asInterface(service); mAppManager.registCallBack(mCallBack); }catch (Exception e){ e.printStackTrace(); } mBound = true; } @Override public void onServiceDisconnected(ComponentName name) { Log.e(TAG, "service disconnected"); mBound = false; } }; private IDownLoadCallBack mCallBack = new IDownLoadCallBack.Stub() { @Override public void callback() throws RemoteException { Log.e(TAG, "下载完成的回调显示"); } };}
大致的流程理一遍,客户端通过bindService绑定服务端,服务端的onBind方法返回一个IBinder对象,来跟客户端进行绑定,这个IBinder对象mAppManager 重写了服务端提供给客户端的两个方法,开始下载和注册回调,客户端通过ServiceConnection的onServiceConnected和onServiceDisconnected判断是否与服务端连接上了,如果连接上了就通过IDownLoadApp.Stub.asInterface(service)获取到服务端的IBinder对象mAppManager,注册下载完后的回调方法(mAppManager.registCallBack(mCallBack);),而这个CallBack方法实现是在客户端,这样的话就可以远程调用服务端的下载和注册回调的方法了,客户端通过点击按钮(此处我省略布局了,直接调用download方法),调用开始服务端的startDownLoad方法,startDownLoad开启一个线程模拟下载应用过程,下载完后回调CallBack方法。。。有点绕,这里先讲下
RemoteCallbackList是干什么的,这个列表主要是存放回调方法的,那为什么用这个类呢,这个类是系统专门提供的用于删除跨进程listener的接口,在客户端终止后,它会帮你自动移除客户端所注册的接口,用法也很简单,当要注册一个listener的时候,就register即可,还有一点要注意,需要编译这个RemoteCallbackList的时候,beginBroadcast和finishBroadcast必须要配对使用,哪怕我们只是获取listener的个数也必须注意这一点:
final int N = mListenerList.beginBroadcast(); for(int i = 0 ;i < N; i++){ IDownLoadCallBack l = mListenerList.getBroadcastItem(i); if(l != null){ try{ l.callback(); }catch (Exception e){ e.printStackTrace(); } } mListenerList.finishBroadcast(); }
还有一个死亡代理需要提一下,如下:
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { // TODO Auto-generated method stub if (mAppManager == null) return; mAppManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mAppManager = null; // TODO:重新绑定远程服务 attempToBindService(); } }; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.e(TAG, "service connected"); try { service.linkToDeath(mDeathRecipient, 0); mAppManager = IDownLoadApp.Stub.asInterface(service); mAppManager.registCallBack(mCallBack); }catch (Exception e){ e.printStackTrace(); } mBound = true; }
在绑定服务端后注册一个死亡代理,当断开连接的时候,就可以重新绑定了。上面就是aidl的整体实现用法,下面我们看一下aidl文件生成的java类:
/* * This file is auto-generated. DO NOT MODIFY. * Original file: D:\\IPC\\ipcclient\\src\\main\\aidl\\com\\fq\\ipc\\ipcclient\\IDownLoadApp.aidl */package com.fq.ipc.ipcclient;// Declare any non-default types here with import statementspublic interface IDownLoadApp extends android.os.IInterface {/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.fq.ipc.ipcclient.IDownLoadApp {private static final java.lang.String DESCRIPTOR = "com.fq.ipc.ipcclient.IDownLoadApp";/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/** * Cast an IBinder object into an com.fq.ipc.ipcclient.IDownLoadApp interface, * generating a proxy if needed. */public static com.fq.ipc.ipcclient.IDownLoadApp asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.fq.ipc.ipcclient.IDownLoadApp))) {return ((com.fq.ipc.ipcclient.IDownLoadApp)iin);}return new com.fq.ipc.ipcclient.IDownLoadApp.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_startDownLoad:{data.enforceInterface(DESCRIPTOR);com.fq.ipc.ipcclient.AppItem _arg0;if ((0!=data.readInt())) {_arg0 = com.fq.ipc.ipcclient.AppItem.CREATOR.createFromParcel(data);}else {_arg0 = null;}this.startDownLoad(_arg0);reply.writeNoException();return true;}case TRANSACTION_registCallBack:{data.enforceInterface(DESCRIPTOR);com.fq.ipc.ipcclient.IDownLoadCallBack _arg0;_arg0 = com.fq.ipc.ipcclient.IDownLoadCallBack.Stub.asInterface(data.readStrongBinder());this.registCallBack(_arg0);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}private static class Proxy implements com.fq.ipc.ipcclient.IDownLoadApp {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;}/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */@Override public void startDownLoad(com.fq.ipc.ipcclient.AppItem app) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);if ((app!=null)) {_data.writeInt(1);app.writeToParcel(_data, 0);}else {_data.writeInt(0);}mRemote.transact(Stub.TRANSACTION_startDownLoad, _data, _reply, 0);_reply.readException();}finally {_reply.recycle();_data.recycle();}}@Override public void registCallBack(com.fq.ipc.ipcclient.IDownLoadCallBack callback) 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((((callback!=null))?(callback.asBinder()):(null)));mRemote.transact(Stub.TRANSACTION_registCallBack, _data, _reply, 0);_reply.readException();}finally {_reply.recycle();_data.recycle();}}}static final int TRANSACTION_startDownLoad = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_registCallBack = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);}/** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */public void startDownLoad(com.fq.ipc.ipcclient.AppItem app) throws android.os.RemoteException;public void registCallBack(com.fq.ipc.ipcclient.IDownLoadCallBack callback) throws android.os.RemoteException;}
当客户端调用mAppManager = IDownLoadApp.Stub.asInterface(service);的时候,看源码中的asInterface,如果是不同的进程,其实是得到一个二级代理Proxy,然后调用里面的startDownLoad方法:
@Override public void startDownLoad(com.fq.ipc.ipcclient.AppItem app) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);if ((app!=null)) {_data.writeInt(1);app.writeToParcel(_data, 0);}else {_data.writeInt(0);}mRemote.transact(Stub.TRANSACTION_startDownLoad, _data, _reply, 0);_reply.readException();}finally {_reply.recycle();_data.recycle();}}
注意这句mRemote.transact(Stub.TRANSACTION_startDownLoad, _data, _reply, 0);这句会触发远程的onTransact方法:
@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_startDownLoad:{data.enforceInterface(DESCRIPTOR);com.fq.ipc.ipcclient.AppItem _arg0;if ((0!=data.readInt())) {_arg0 = com.fq.ipc.ipcclient.AppItem.CREATOR.createFromParcel(data);}else {_arg0 = null;}this.startDownLoad(_arg0);reply.writeNoException();return true;}case TRANSACTION_registCallBack:{data.enforceInterface(DESCRIPTOR);com.fq.ipc.ipcclient.IDownLoadCallBack _arg0;_arg0 = com.fq.ipc.ipcclient.IDownLoadCallBack.Stub.asInterface(data.readStrongBinder());this.registCallBack(_arg0);reply.writeNoException();return true;}}return super.onTransact(code, data, reply, flags);}
看到这句this.startDownLoad(_arg0);最终会调用到服务端的startDownLoad方法,来实现进程间通讯
总结:AIDL的东西比较多,本文可能还有很多没涉及到的地方,不过基本上用起来就是这样用,大家只要用过一次,以后再写AIDL就会很熟练了,写的比较仓促,有问题我会随时改正。
更多相关文章
- Android[高级教程] 设计模式之七 单例模式
- Android面试知识点总结-Android篇
- Android(安卓)Service
- 通用(任何android机型)Root教程(完整版!附砖机自救方法)
- 关闭 / 隐藏 Android(安卓)软键盘
- Android中View的滑动
- Service和Activity通讯的3种常用方式示例
- 【Android】高效ListView
- Android(安卓)架构组件(一)——Lifecycle