首页 > 学院 > 开发设计 > 正文

IPC机制<二>AIDL

2019-11-07 23:55:26
字体:
来源:转载
供稿:网友

上次讲到ipC机制的几种通信方式,但是没有讲完,还剩下几种方式没有讲,今天继续。

在上次讲到Service的时候,说过AIDL,但是那种只是非常简单的方式,今天将会详细的讲述AIDL。

一、AIDL 中的数据类型

aidl支持的数据类型:

基本数据类型(int,long,char,boolean,double等)String和CharSequenceList:只支持ArrayList,里面的每个元素都必须能够被AIDL支持。Map:只支持HashMap,里面的每个元素都必须能够被AIDL支持Parcelable:所有实现了Parcelable接口的对象。AIDL:所有的AIDL接口本身也可以在AIDL中使用。

注意:

自定义的Parcelable对象和AIDL对象必须要显示import进来,不管他们是否在同一个包中。

package cn.demo.zx_aidl_learn;import cn.demo.zx_aidl_learn.domain.Book;interface IService { List<Book>getBookList(); void addBook(in Book b);}

如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明为parcelable类型。

在AIDL中处理基本数据类型,其他类型的参数必须标上方向:in,out或者inout。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。AIDL接口中只支持方法不支持声明静态常量。AIDL的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化。

案例:

服务端:

public class BookManagerService extends Service { /** * 使用CopyOnWriteArrayList的原因: * 因为CopyOnWriteArrayList支持并发读/写,AIDL方法是在服务端的Binder的线程池中执行的, * 因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中 * 处理线程同步,而我们这里直接使用CopyOnWriteArrayList来进行自动的线程同步。 * * AIDL中能够使用的List只有ArrayList,但是这里为什么还使用它呢? * 因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList, * 但是在Binder中会按照List的规范去访问数据并最终返回一个新的ArrayList传递给客户端。还有就是类似的是 * ConcurrentHashMap. */ CopyOnWriteArrayList<Book>mBookList = new CopyOnWriteArrayList<>(); public BookManagerService() { } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1,"android")); mBookList.add(new Book(2,"ios学习")); } PRivate Binder mBinder = new IService.Stub(){ @Override public List<Book> getBookList() throws RemoteException { return mBookList; } @Override public void addBook(Book b) throws RemoteException { mBookList.add(b); } };}

客户端:

public class MainActivity extends AppCompatActivity { private IService mService = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent = new Intent(this,BookManagerService.class); startService(intent); bindService(intent,conn,BIND_AUTO_CREATE); } public void findBook(View v){ /** * 注意:服务端的方法有可能是耗时的方法,那么下面的调用就有可能早能ANR异常,需要注意。 */ try { List<Book> bookList = mService.getBookList(); if(bookList!=null){ for(Book b : bookList){ System.out.println("书籍::"+b.toString()); } } } catch (RemoteException e) { e.printStackTrace(); } } private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IService.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { } };}

清单文件AndroidMenifest:

<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/APPTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <service android:name=".BookManagerService" android:enabled="true" android:exported="true" android:process=":romote"> </service></application>

上述案例只是讲述客户端从服务端获取图书信息,如果是在服务端每添加一本书籍就向客户端发送消息,那么怎么实现呢?

这里就会用到观察者模式。

二、AIDL中的观察者模式

创建一个aidl接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能。

新创建的aidl文件:

import cn.demo.zx_aidl_learn.domain.Book;interface IOnNewBookArrivedListener { void onNewBookArrived(in Book newBook);}import cn.demo.zx_aidl_learn.domain.Book;import cn.demo.zx_aidl_learn.IOnNewBookArrivedListener;interface IService { List<Book>getBookList(); void addBook(in Book b); void registerListener(in IOnNewBookArrivedListener listener); void unregisterListener(in IOnNewBookArrivedListener listener);}

在MainActivity中创建一个IOnNewBookArrivedListener对象,然后通过注册的方式将IOnNewBookArrivedListener对象传递给服务端:

private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IService.Stub.asInterface(service); try { /** * 注册对新书的监控 */ mService.registerListener(listener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { }};private IOnNewBookArrivedListener listener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { System.out.println("新到的图书::"+newBook.toString()); mHandler.obtainMessage(0,newBook).sendToTarget(); }};

服务端通过获取传入的IOnNewBookArrivedListener对象来调用客户端中的方法来通知客户端:

/** * 发送新书已到的消息到客户端 * 怎样通知呢? * 通过客户端在主持监听的时候传入的IOnNewBookArrivedListener对象来通知客户端 * @param newBook */private void sendNewBookArrivedToClient(Book newBook){ mBookList.add(newBook); for(int i=0;i<mListenerList.size();i++){ IOnNewBookArrivedListener listener = mListenerList.get(i); try { listener.onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } }}

详细功能实现请查看源码

三、取消注册功能

当我们实现了注册功能之后,怎样取消注册功能呢?

@Overrideprotected void onDestroy() { super.onDestroy(); if(mService!=null || mService.asBinder().isBinderAlive()){ try { mService.unregisterListener(listener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(conn);}

我们通过上述方法无法取消注册。这是为什么呢?

这是因为在客户端传入的IOnNewBookArrivedListener对象是同一个对象,但是在服务端却又变成了另外的一个对象了。因为对象无法跨进程通信,服务端接收的IOnNewBookArrivedListener对象是通过序列化之后得到对象,所以无法取消注册,这种情况怎么处理呢?

这里我们就会用到RemoteCallbackList类,它是系统专门提供的用于删除跨进程listener的接口,它是一个泛型,支持管理任意的AIDL接口,因为它继承了IInterface接口,aidl接口都继承IInterface。

它的工作原理非常简单,就是在它的内部有一个专门用于保存所有的AIDL回调的Map集合:

ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();

其中Callback中封装了真正的远程listener,当客户端注册listener的时候,它会把这个listener的信息存入mCallback中,其中key和value分别通过下面的方式获得:

private final class Callback implements IBinder.DeathRecipient。。。。public boolean register(E callback, Object cookie) { synchronized (mCallbacks) { if (mKilled) { return false; } IBinder binder = callback.asBinder(); try { Callback cb = new Callback(callback, cookie); binder.linkToDeath(cb, 0); mCallbacks.put(binder, cb); return true; } catch (RemoteException e) { return false; } }}

到这里就应该知道,虽然在客户端传入的同一对象在服务端会生成了不同的对象,但是它的底层的Binder对象却没有变化,利用这个特性就可以实现上面的功能了。当客户端取消注册的时候,我们会便利所有的listener接口,找到与客户端传入的listener具有相同Binder对象的服务端listener,然后把它删除即可。

服务端的代码:

RemoteCallbackList<IOnNewBookArrivedListener>mListenerList = new RemoteCallbackList<>();@Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {// if(!mListenerList.contains(listener)){// mListenerList.add(listener);// } mListenerList.register(listener); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {// if(mListenerList.contains(listener)){// mListenerList.remove(listener);// } mListenerList.unregister(listener); }/** * 发送新书已到的消息到客户端 * 怎样通知呢? * 通过客户端在主持监听的时候传入的IOnNewBookArrivedListener对象来通知客户端 * @param newBook */private void sendNewBookArrivedToClient(Book newBook){ mBookList.add(newBook);// for(int i=0;i<mListenerList.size();i++){ // IOnNewBookArrivedListener listener = mListenerList.get(i); // try { // listener.onNewBookArrived(newBook); // } catch (RemoteException e) { // e.printStackTrace(); // } // } int N = mListenerList.beginBroadcast(); for(int i=0;i<N;i++){ IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i); try { listener.onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } mListenerList.finishBroadcast();}

四、设置死亡代理

关于死亡代理,以前讲过,这里在说明一下,当客户端在与服务端进行跨进程通信的时候,服务端因为异常而被销毁,但是服务端却不知道,那么怎样解决这个问题呢,这里就讲到了重新连接服务的两种方法,第一种就是死亡代理:

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { mService.asBinder().unlinkToDeath(mDeathRecipient,0); mService = null; //重新连接 bindService(intent,conn,BIND_AUTO_CREATE); }};private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = IService.Stub.asInterface(service); try { service.linkToDeath(mDeathRecipient,0); } catch (RemoteException e) { e.printStackTrace(); } try { /** * 注册对新书的监控 */ mService.registerListener(listener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { }};

还有一种方法就是在onServiceDisconnected中重新连接绑定服务。

五、权限验证

我们不想让所有的客户端访问我们的服务端,我们希望具有某些权限的客户端才能访问,怎样实现?

第一种方法,在onBind中进行验证。验证不通过就返回null,造成无法连接服务端。这种验证很多,这里介绍其中一种permission权限验证,在服务端定义一个权限。

AndroidMenifest:

<permission android:name="cn.demo.zx_aidl_learn.YANZHENG"/>

服务端:

@Overridepublic IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("cn.demo.zx_aidl_learn.YANZHENG"); if(check== PackageManager.PERMISSION_DENIED){ return null; } return mBinder;}

如果想要访问这个服务端的话,那么客户端就需要设置权限:

<uses-permission android:name="cn.demo.zx_aidl_learn.YANZHENG"/>

另一种方法,这种方法就是在服务端的onTransact方法中进行权限验证,如果验证失败返回false。验证的方法也是很多,可以通过permission验证,也可以通过包名验证。permission验证的方法与上述onBind方法中差不多,唯一的区别就是返回false。包名验证就通过getCallinngUidgetCallingPid获取客户端的uid和pid从而获取包名,进行验证。

String[] packageNames = getPackageManager().getPacagesForUid(getCallingUid());if(!packageNames[0].startsWith("cn.demo")){ return false;}

注意

当客户端访问服务端的方法时候,客户端会等待跨进程通信,如果服务端中的方法是一个耗时的方法的话,那么客户端就需要在子线程中去访问,如果在UI线程中,容易造成ANR异常。服务端访问客户端的方法也是一样的。

源码下载:

https://github.com/zhangxun1900/my_ipc_learn/


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表