💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
#### 2.4.4 使用AIDL * [Android 接口定义语言 (AIDL)](https://developer.android.google.cn/guide/components/aidl):Android中每个应用程序都有自己的进程,当需要在不同的进程之间传递对象时,因为Java中是不支持跨进程内存共享,要想传递对象,需要把对象解析成操作系统能够理解的数据格式,Android中采用AIDL实现。 * AIDL:一种接口定义语言,用于约束两个进程之间的通讯规则,供编译器生成代码实现Android设备的IPC。 上一节我们介绍了使用Messenger来进行进程间通信的方法,可以发现,**Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了**。同时,**Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用**。 **AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已**。 在上一节中,我们介绍了Binder的概念,大家对Binder也有了一定的了解,在Binder的基础上我们可以更加容易地理解AIDL。这里先介绍**使用AIDL来进行进程间通信的流程,分为服务端和客户端两个方面**。 **1.服务端** 服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,**将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。 并非直接实现接口,而是通过继承接口的Stub来实现,并且实现接口的方法**。 >[info]注意:这个Stub类,怎么实现的?这是编辑器自动帮助生成的,系统为IBookManager.aidl自动生成的gen目录下的IBookManager.java类。详细情况可以回去查看2.3.3小章节。 **2.客户端** 客户端所要做事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。 上面描述的只是一个感性的过程,AIDL的实现过程远不止这么简单,接下来会对其中的细节和难点进行详细介绍,并完善我们在Binder那一节所提供的的实例。 **3.AIDL接口的创建** 首先看AIDL接口的创建,如下所示,我们创建了一个后缀为AIDL的文件,在里面声明了一个接口和两个接口方法。 **IBookManager.aidl** ~~~ package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener); } ~~~ 在**AIDL文件中,并不是所有的数据类型都是可以使用的**,那么到底AIDL文件支持哪些数据类型呢?如下所示。 * 基本数据类型(int、long、char、boolean、double等)除了short; * String和CharSequence; * **List:只支持ArrayList,里面每个元素都必须能够被AIDL支持**; * 但AIDL的方法的内部可支持所有类型的List,同样适用于Map,因为Binder会对这些其它的List和Map进行转换 * **Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value**; * Parcelable:所有实现了Parcelable接口的对象; * 比如:[Book](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_2/src/com/ryg/chapter_2/aidl/Book.java)这个类 * AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。 * 比如上面代码中的[IOnNewBookArrivedListener](https://github.com/singwhatiwanna/android-art-res/blob/master/Chapter_2/src/com/ryg/chapter_2/aidl/IOnNewBookArrivedListener.aidl)接口 以上6种数据类型就是AIDL所支持的所有类型,其中**自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个包内。比如IBookManager.aidl这个文件,里面用到了Book这个类,这个类实现了Parcelable接口并且和IBookManager.aidl位于同一个包中,但是遵守AIDL的规范,我们仍然需要显式地import进来**:`import com.ryg.chapter_2.aidl.Book`。 AIDL中会大量使用到Parcelable,至于如何使用Parcelable接口来序列化对象,在本章的前面已经介绍过,这里就不再赘述。 另外一个需要注意的地方是,**如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。在上面的IBookManager.aidl中,我们用到了Book这个类,所以,我们必须要创建Book.aidl,然后在里面添加如下内容**: ~~~ package com.ryg.chapter_2.aidl; parcelable Book; ~~~ 我们需要注意,**AIDL中每个实现了Parcelable接口的类都需要按照上面那种方式去创建相应的AIDL文件并声明那个类为parcelable**。除此之外,**AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout, in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,比如前面代码中的 IBookManager.aidl中接口 IBookManager中的`addBook(in Book book);`**,至于它们具体的区别,这个就不说了。我们**要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的**。最后,**AIDL接口中只支持方法,不支持声明静态常量,这一点区别于传统的接口**。 **为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放入同一个包中,这样做的好处是,当客户端是另外一个应用时,我们可以直接把整个包复制到客户端工程中**,对于本例来说,就是要把com.ryg.chapter_2.aidl这个包和包中的文件原封不动地复制到客户端中。如果AIDL相关的文件位于不同的包中时,那么就需要把这些包一一复制到客户端工程中,这样操作起来比较麻烦而且也容易出错。 **需要注意的是,AIDL的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法正常运行**。 为了方便演示,本章的所有示例都是在同一个工程中进行的,但是读者要理解,一个工程和两个工程的多进程本质是一样的,两个工程的情况,除了需要复制AIDL接口所相关的包到客户端,其他完全一样,读者可以自行试验。 **4.远程服务端Service的实现** 上面讲述了如何定义AIDL接口,接下来我们就需要实现这个接口了。我们先创建一个Service,称为BookManagerService,代码如下: ~~~ public class BookManagerService extends Service { private static final String TAG = "BMS"; private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false); private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); // private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = // new CopyOnWriteArrayList<IOnNewBookArrivedListener>(); private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>(); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { SystemClock.sleep(5000); return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid( getCallingUid()); if (packages != null && packages.length > 0) { packageName = packages[0]; } Log.d(TAG, "onTransact: " + packageName); if (!packageName.startsWith("com.ryg")) { return false; } return super.onTransact(code, data, reply, flags); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success."); } else { Log.d(TAG, "not found, can not unregister."); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); }; }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "Ios")); new Thread(new ServiceWorker()).start(); } @Override public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "onbind check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; } @Override public void onDestroy() { mIsServiceDestoryed.set(true); super.onDestroy(); } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l != null) { try { l.onNewBookArrived(book); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } private class ServiceWorker implements Runnable { @Override public void run() { // do background processing here..... while (!mIsServiceDestoryed.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "new book#" + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } } ~~~ 上面是一个服务端Service的典型实现,首先在onCreate中初始化添加了两本图书的信息,然后创建了一个Binder对象并在onBind中返回它,这个对象继承自IBookManager.Stub并实现了它内部的AIDL方法,这个过程在Binder那一节已经介绍过了,这里就不多说了。 这里主要看getBookList和addBook这两个AIDL方法的实现,实现过程也比较简单,注意这里采用了CopyOnWriteArrayList,这个**CopyOnWriteArrayList支持并发读/写**。在前面我们提到,**AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,而我们这里直接使用CopyOnWriteArrayList来进行自动的线程同步**。 前面我们提到,**AIDL中能够使用的List只有ArrayList,但是我们这里却使用了CopyOnWriteArrayList(注意它不是继承自ArrayList),为什么能够正常工作呢**?这是因为**AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端**。所以,我们在服务端采用CopyOnWriteArrayList是完全可以的。 和此类似的还有ConcurrentHashMap,读者可以体会一下这种转换情形。 然后我们**需要在XML中注册这个Service**,如下所示。 注意:**BookManagerService是运行在独立进程中的,它和客户端的Activity不在同一个进程中,这样就构成了进程间通信的场景**。 ``` <service android:name=".aidl.BookManagerService" android:process=":remote" > </service> ``` **5.客户端的实现** 客户端的实现就比较简单了,**首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了**,代码如下所示。 ~~~ package com.ryg.chapter_2.aidl; public class BookManagerActivity extends Activity { private static final String TAG = "BookManagerActivity"; private static final int MESSAGE_NEW_BOOK_ARRIVED = 1; private IBookManager mRemoteBookManager; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_NEW_BOOK_ARRIVED: Log.d(TAG, "receive new book :" + msg.obj); break; default: super.handleMessage(msg); } } }; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName()); if (mRemoteBookManager == null) { return; } mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mRemoteBookManager = null; // TODO:这里重新绑定远程Service } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); List<Book> list = bookManager.getBookList(); Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log.i(TAG, "query book list:" + list.toString()); Book newBook = new Book(3, "Android进阶"); bookManager.addBook(newBook); Log.i(TAG, "add book:" + newBook); List<Book> newList = bookManager.getBookList(); Log.i(TAG, "query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName className) { mRemoteBookManager = null; Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName()); } }; private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook) .sendToTarget(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } public void onButton1Click(View view) { Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { if (mRemoteBookManager != null) { try { List<Book> newList = mRemoteBookManager.getBookList(); } catch (RemoteException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onDestroy() { if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) { try { Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener); mRemoteBookManager .unregisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); super.onDestroy(); } } ~~~ 绑定成功以后,会通过bookManager去调用getBookList方法,然后打印出所获取的图书信息。 **需要注意的是,服务端的方法有可能需要很久才能执行完毕,这个时候下面的代码就会导致ANR,这一点是需要注意的**,后面会再介绍这种情况,之所以先这么写是为了让读者更好地了解AIDL的实现步骤。 接着**在XML中注册此Activity,运行程序**,log如下所示。 ``` I/BookManagerActivity(3047): query book list, list type:java.util.ArrayList I/BookManagerActivity(3047): query book list:[[bookId:1, bookName:Android], [bookId:2, bokName:Ios]] ``` 可以发现,**虽然我们在服务端返回的是CopyOnWriteArrayList类型,但是客户端收到的仍然是ArrayList类型,这也证实了我们在前面所做的分析**。第二行log表明客户端成功地得到了服务端的图书列表信息。 这就是一次完完整整的使用AIDL进行IPC的过程,到这里相信读者对AIDL应该有了一个整体的认识了,但是还没完,AIDL的复杂性远不止这些,下面继续介绍AIDL中常见的一些难点。 我们**接着再调用一下另外一个接口addBook,我们在客户端给服务端添加一本书,然后再获取一次,看程序是否能够正常工作**。还是上面的代码,客户端在服务连接后,在onServiceConnected中做如下改动(下面的代码还是上面客户端中的代码,只不过是挪下来了,因为上面的客户端代码是整个章节的代码): ~~~ public void onServiceConnected(ComponentName className, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); List<Book> list = bookManager.getBookList(); Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log.i(TAG, "query book list:" + list.toString()); Book newBook = new Book(3, "Android进阶"); bookManager.addBook(newBook); Log.i(TAG, "add book:" + newBook); List<Book> newList = bookManager.getBookList(); Log.i(TAG, "query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } ~~~ 运行后我们再看一下log,很显然,我们成功地向服务端添加了一本书“Android进阶”。 I/BookManagerActivity( 3148): query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios]] I/BookManagerActivity( 3148): add book:[bookId:3, bookName:Android进阶] I/BookManagerActivity( 3148): query book list:[[bookId:1, bookName:Android], [bookId:2, bookName:Ios], [bookId:3, bookName:Android进阶]] 现在我们考虑一种情况,假设有一种需求:用户不想时不时地去查询图书列表了,太累了,于是,他去问图书馆,“当有新书时能不能把书的信息告诉我呢?”。大家应该明白了,这就是一种**典型的观察者模式**,**每个感兴趣的用户都观察新书,当新书到的时候,图书馆就通知每一个对这本书感兴趣的用户**,这种模式在实际开发中用得很多,下面我们就来模拟这种情形。 首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,当然用户也可以随时取消这种提醒。之所以选择AIDL接口而不是普通接口,是因为AIDL中无法使用普通接口。这里我们**创建一个IOnNewBookArrivedListener.aidl文件,我们所期望的情况是:当服务端有新书到来时,就会通知每一个已经申请提醒功能的用户。从程序上来说就是调用所有IOnNew BookArrivedListener对象中的onNewBookArrived方法,并把新书的对象通过参数传递给客户端**,内容如下所示。 package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; interface IOnNewBookArrivedListener { void onNewBookArrived(in Book newBook); } 除了要新加一个AIDL接口,还需要在原有的接口中添加两个新方法,代码如下所示,和前面的代码一样。 ~~~ package com.ryg.chapter_2.aidl; import com.ryg.chapter_2.aidl.Book; import com.ryg.chapter_2.aidl.IOnNewBookArrivedListener; interface IBookManager { List<Book> getBookList(); void addBook(in Book book); void registerListener(IOnNewBookArrivedListener listener); void unregisterListener(IOnNewBookArrivedListener listener); } ~~~ 接着,服务端中Service的实现也要稍微修改一下,主要是Service中IBookManager.Stub的实现,因为我们在IBookManager新加了两个方法,所以在IBookManager.Stub中也要实现这两个方法。同时,在BookManagerService中还开启了一个线程,每隔5s就向书库中增加一本新书并通知所有感兴趣的用户,整个代码如下所示。 ~~~ package com.ryg.chapter_2.aidl; public class BookManagerService extends Service { private static final String TAG = "BMS"; private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false); private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>(); //private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = //new CopyOnWriteArrayList<IOnNewBookArrivedListener>(); private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>(); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList() throws RemoteException { SystemClock.sleep(5000); return mBookList; } @Override public void addBook(Book book) throws RemoteException { mBookList.add(book); } public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid( getCallingUid()); if (packages != null && packages.length > 0) { packageName = packages[0]; } Log.d(TAG, "onTransact: " + packageName); if (!packageName.startsWith("com.ryg")) { return false; } return super.onTransact(code, data, reply, flags); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success."); } else { Log.d(TAG, "not found, can not unregister."); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); }; }; @Override public void onCreate() { super.onCreate(); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "Ios")); new Thread(new ServiceWorker()).start(); } @Override public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"); Log.d(TAG, "onbind check=" + check); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; } @Override public void onDestroy() { mIsServiceDestoryed.set(true); super.onDestroy(); } private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l != null) { try { l.onNewBookArrived(book); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } private class ServiceWorker implements Runnable { @Override public void run() { // do background processing here..... while (!mIsServiceDestoryed.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "new book#" + bookId); try { onNewBookArrived(newBook); } catch (RemoteException e) { e.printStackTrace(); } } } } } ~~~ 最后,我们还需要修改一下客户端的代码,主要有两方面: 1. 首先**客户端要注册IOnNewBookArrivedListener到远程服务端,这样当有新书时服务端才能通知当前客户端,同时我们要在Activity退出时解除这个注册**; 2. 另一方面,**当有新书时,服务端会回调客户端的IOnNewBookArrivedListener对象中的onNewBookArrived方法,但是这个方法是在客户端的Binder线程池中执行的,因此,为了便于进行UI操作,我们需要有一个Handler可以将其切换到客户端的主线程中去执行**,这个原理在Binder中已经做了分析, 这里就不多说了。客户端的代码修改如下: ~~~ package com.ryg.chapter_2.aidl; public class BookManagerActivity extends Activity { private static final String TAG = "BookManagerActivity"; private static final int MESSAGE_NEW_BOOK_ARRIVED = 1; private IBookManager mRemoteBookManager; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_NEW_BOOK_ARRIVED: Log.d(TAG, "receive new book :" + msg.obj); break; default: super.handleMessage(msg); } } }; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { Log.d(TAG, "binder died. tname:" + Thread.currentThread().getName()); if (mRemoteBookManager == null) { return; } mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0); mRemoteBookManager = null; // TODO:这里重新绑定远程Service } }; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0); List<Book> list = bookManager.getBookList(); Log.i(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log.i(TAG, "query book list:" + list.toString()); Book newBook = new Book(3, "Android进阶"); bookManager.addBook(newBook); Log.i(TAG, "add book:" + newBook); List<Book> newList = bookManager.getBookList(); Log.i(TAG, "query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName className) { mRemoteBookManager = null; Log.d(TAG, "onServiceDisconnected. tname:" + Thread.currentThread().getName()); } }; private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(Book newBook) throws RemoteException { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook) .sendToTarget(); } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } public void onButton1Click(View view) { Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { if (mRemoteBookManager != null) { try { List<Book> newList = mRemoteBookManager.getBookList(); } catch (RemoteException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onDestroy() { if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) { try { Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener); mRemoteBookManager .unregisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); super.onDestroy(); } } ~~~ 运行程序,看一下log,从log中可以看出,客户端的确收到了服务端每5s一次的新书推送,我们的功能也就实现了。 D/BMS(3414):onNewBookArrived, notify listener:com.ryg.chapter_2.aidl. IOnNewBookArrivedListener$Stub$Proxy@4052a648 D/BookManagerActivity( 3385): receive new book :[bookId:4, bookName:new book#4] D/BMS(3414):onNewBookArrived, notify listener:com.ryg.chapter_2.aidl. IOnNewBookArrivedListener$Stub$Proxy@4052a648 D/BookManagerActivity( 3385): receive new book :[bookId:5, bookName:new book#5] 如果你以为到这里AIDL的介绍就结束了,那你就错了,之前就说过,**AIDL远不止这么简单,目前还有一些难点是我们还没有涉及的**,接下来将继续为读者介绍。 从上面的代码可以看出,当BookManagerActivity关闭时,我们会在onDestroy中去解除已经注册到服务端的listener,这就相当于我们不想再接收图书馆的新书提醒了,所以我们可以随时取消这个提醒服务。按back键退出BookManagerActivity,下面是打印出的log。 ``` I/BookManagerActivity(5642): unregister listener:com.ryg.chapter_2.aidl. BookManagerActivity$3@405284c8 D/BMS(5650): not found, can not unregister. D/BMS(5650): unregisterListener, current size:1 ``` 从上面的log可以看出,程序没有像我们所预期的那样执行。**在解注册的过程中,服务端竟然无法找到我们之前注册的那个listener,在客户端我们注册和解注册时明明传递的是同一个listener啊!最终,服务端由于无法找到要解除的listener而宣告解注册失败**!这当然不是我们想要的结果,但是仔细想想,好像这种方式的确无法完成解注册。其实,这是必然的,**这种解注册的处理方式在日常开发过程中时常使用到,但是放到多进程中却无法奏效,因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然我们在注册和解注册过程中使用的是同一个客户端对象,但是通过Binder传递到服务端后,却会产生两个全新的对象。别忘了对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须要实现Parcelable接口的原因**。 那么到底我们**该怎么做才能实现解注册功能呢**?答案是**使用[RemoteCallbackList](https://www.androidos.net.cn/android/7.1.1_r28/xref/frameworks/base/core/java/android/os/RemoteCallbackList.java)**,这看起来很抽象,不过没关系,请看接下来的详细分析。 * **RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口**。 * Remote-CallbackList支持泛型,支持管理任意的AIDL接口,这点从它的声明就可以看出,因为**所有的AIDL接口都继承自IInterface接口,从AIDL对应的编辑器生成的对应类名的java文件也可以看出来**,读者还有印象吗? ``` public class RemoteCallbackList<E extends IInterface> ``` 它的工作原理很简单,在**它的内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型**,如下所示。 ``` ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); ``` 其中**Callback中封装了真正的远程listener**。 **当客户端注册listener的时候,它会把这个listener的信息存入mCallbacks中,其中key和value分别通过下面的方式获得**: ``` IBinder key= listener.asBinder() Callback value = new Callback(listener, cookie) ``` 到这里,读者应该都明白了,**虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们底层的Binder对象是同一个**,利用这个特性,就可以实现上面我们无法实现的功能。**当客户端解注册的时候,我们只要遍历服务端所有的listener,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删掉即可,这就是RemoteCallbackList为我们做的事情**。 同时**RemoteCallbackList还有一个很有用的功能,那就是当客户端进程终止后,它能够自动移除客户端所注册的listener**。 另外,**RemoteCallbackList内部自动实现了线程同步的功能,所以我们使用它来注册和解注册时,不需要做额外的线程同步工作。由此可见,RemoteCallbackList的确是个很有价值的类**, 下面就演示如何使用它来完成解注册。 RemoteCallbackList使用起来很简单,我们要对BookManagerService做一些修改,首先**要创建一个RemoteCallbackList对象来替代之前的CopyOnWriteArrayList**,如下所示。 ``` private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>(); ``` 然后修改registerListener和unregisterListener这两个接口的实现,如下所示。 ~~~ @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "registerListener, current size:" + N); } @Override public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException { boolean success = mListenerList.unregister(listener); if (success) { Log.d(TAG, "unregister success."); } else { Log.d(TAG, "not found, can not unregister."); } final int N = mListenerList.beginBroadcast(); mListenerList.finishBroadcast(); Log.d(TAG, "unregisterListener, current size:" + N); } ~~~ 怎么样?是不是用起来很简单,接着要**修改onNewBookArrived方法,当有新书时,我们就要通知所有已注册的listener**,如下所示。 ~~~ private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l != null) { try { l.onNewBookArrived(book); } catch (RemoteException e) { e.printStackTrace(); } } } mListenerList.finishBroadcast(); } ~~~ BookManagerService的修改已经完毕了,为了方便我们验证程序的功能,我们还需要添加一些log,在注册和解注册后我们分别打印出所有listener的数量。如果程序正常工作的话,那么注册之后listener总数量是1,解注册之后总数量应该是0,我们再次运行一下程序,看是否如此。 从下面的log来看,很显然,使用RemoteCallbackList的确可以完成跨进程的解注册功能。 I/BookManagerActivity(8419): register listener:com.ryg.chapter_2.aidl. BookManagerActivity$3@40537610 D/BMS(8427): registerListener, current size:1 I/BookManagerActivity(8419): unregister listener:com.ryg.chapter_2.aidl. BookManagerActivity$3@40537610 D/BMS(8427): unregister success. D/BMS(8427): unregisterListener, current size:0 使用RemoteCallbackList,有几点需要注意, 1. 我们无法像操作List一样去操作它,**尽管它的名字中也带个List,但是它并不是一个List**。 2. 遍历RemoteCallbackList,必须要按照下面的方式进行,**其中beginBroadcast和finishBroadcast必须要配对使用**,哪怕我们仅仅是想要获取RemoteCallbackList中的元素个数,这是必须要注意的地方。 final int N = mListenerList.beginBroadcast(); for (int i = 0; i < N; i++) { IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i); if (l ! = null) { //TODO handle l } } mListenerList.finishBroadcast(); 到这里,AIDL的基本使用方法已经介绍完了,但是有几点还需要再次说明一下。 我们知道,**客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,这当然不是我们想要看到的。因此,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法。由于客户端的`onServiceConnected`和`onService Disconnected`方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法**,这点要尤其注意。 另外 ,**由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做**。 下面我们稍微改造一下服务端的getBookList方法,我们**假定这个方法是耗时的,那么服务端可以这么实现**: ``` @Override public List<Book> getBookList() throws RemoteException { SystemClock.sleep(5000);//这里假设服务端耗时操作 return mBookList; } ``` 然后**在客户端中放一个按钮,单击它的时候就会调用服务端的getBookList方法,可以预知,连续单击几次,客户端就ANR了**,如图2-7所示,感兴趣读者可以自行试一下。 :-: ![](https://img.kancloud.cn/d3/5f/d35fae5d97bf22b2e24d6ee261123928_538x795.png) 图2-7 UI线程中调用远程耗时方法导致的ANR 避免出现上述这种ANR其实很简单,我们**只需要把调用放在非UI线程即可**,如下所示。 public void onButton1Click(View view) { Toast.makeText(this, "click button1", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { if (mRemoteBookManager ! = null) { try { List<Book> newList = mRemoteBookManager.getBookList(); } catch (RemoteException e) { e.printStackTrace(); } } } }).start(); } 同理,**当远程服务端需要调用客户端的listener中的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池**。所以,我们**同样不可以在服务端中调用客户端的耗时方法**。 比如**针对BookManagerService的onNewBookArrived方法,如下所示。在它内部调用了客户端的IOnNewBookArrivedListener中的onNewBookArrived方法,如果客户端的这个onNewBookArrived方法比较耗时的话,那么请确保BookManagerService中的onNewBookArrived运行在非UI线程中,否则将导致服务端无法响应**。 private void onNewBookArrived(Book book) throws RemoteException { mBookList.add(book); Log.d(TAG, "onNewBookArrived, notify listeners:" + mListenerList. size()); for (int i = 0; i < mListenerList.size(); i++) { IOnNewBookArrivedListener listener = mListenerList.get(i); Log.d(TAG, "onNewBookArrived, notify listener:" + listener); listener.onNewBookArrived(book); } } 另外,**由于客户端的IOnNewBookArrivedListener中的onNewBookArrived方法运行在客户端的Binder线程池中,所以不能在它里面去访问UI相关的内容,如果要访问UI,请使用Handler切换到UI线程**,这一点在前面的代码实例中已经有所体现,这里就不再详细描述了。 为了程序的健壮性,我们还需要做一件事。**Binder是可能意外死亡的,这往往是由于服务端进程意外停止了,这时我们需要重新连接服务**。有两种方法, 1. 第一种方法是**给Binder设置DeathRecipient监听,当Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务**,具体方法在Binder那一节已经介绍过了,这里就不再详细描述了。 2. 另一种方法是**在onServiceDisconnected中重连远程服务**。 这两种方法我们可以随便选择一种来使用,它们的区别在于:**onServiceDisconnected在客户端的UI线程中被回调,而binderDied在客户端的Binder线程池中被回调**。也就是说,**在binderDied方法中我们不能访问UI,这就是它们的区别**。 下面验证一下二者之间的区别,首先我们**通过DDMS杀死服务端进程,接着在这两个方法中打印出当前线程的名称**,如下所示。 ``` D/BookManagerActivity(13652): onServiceDisconnected. tname:main D/BookManagerActivity(13652): binder died. tname:Binder Thread #2 ``` 从上面的log和图2-8我们可以看到,**onServiceDisconnected运行在main线程中,即UI线程,而binderDied运行在“Binder Thread #2”这个线程中,很显然,它是Binder线程池中的一个线程**。 :-: ![](https://img.kancloud.cn/16/09/1609f7bae930981648ff2152c8cecddf_1439x700.png) 图2-8 DDMS中的线程信息 * [ ] **如何在AIDL中使用权限验证功能** 到此为止,我们已经对AIDL有了一个系统性的认识,但是还差最后一步:**如何在AIDL中使用权限验证功能**。 **默认情况下,我们的远程服务任何人都可以连接,但这应该不是我们愿意看到的,所以我们必须给服务加入权限验证功能,权限验证失败则无法调用服务中的方法**。 在**AIDL中进行权限验证,这里介绍两种常用的方法**。 * 第一种方法,我们**可以在onBind中进行验证,验证不通过就直接返回null,这样验证失败的客户端直接无法绑定服务**, 至于验证方式可以有多种,比如使用permission验证。使用这种验证方式,我们要先在AndroidMenifest中声明所需的权限,比如: <permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" android:protectionLevel="normal" /> 关于permission的定义方式请读者查看相关资料,这里就不详细展开了,毕竟本节的主要内容是介绍AIDL。定义了权限以后,就可以在BookManagerService的onBind方法中做权限验证了,如下所示。 public IBinder onBind(Intent intent) { int check = checkCallingOrSelfPermission("com.ryg.chapter_2. permission.ACCESS_BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return null; } return mBinder; } **一个应用来绑定我们的服务时,会验证这个应用的权限,如果它没有使用这个权限,onBind方法就会直接返回null,最终结果是这个应用无法绑定到我们的服务,这样就达到了权限验证的效果,这种方法同样适用于Messenger中**,读者可以自行扩展。 如果我们自己内部的应用想绑定到我们的服务中,只需要在它的AndroidMenifest文件中采用如下方式使用permission即可。 ``` <uses-permission android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE" /> ``` * 第二种方法,我们可以**在服务端的onTransact方法中进行权限验证,如果验证失败就直接返回false,这样服务端就不会终止执行AIDL中的方法从而达到保护服务端的效果**。 至于具体的**验证方式有很多,可以采用permission验证**,具体实现方式和第一种方法一样。 **还可以采用Uid和Pid来做验证,通过getCallingUid和getCallingPid可以拿到客户端所属应用的Uid和Pid,通过这两个参数我们可以做一些验证工作,比如验证包名**。 在下面的代码中,**既验证了permission,又验证了包名**。 **一个应用如果想远程调用服务中的方法,首先要使用我们的自定义权限“`com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE`”,其次包名必须以“`com.ryg`”开始,否则调用服务端的方法会失败**。 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.ryg.chapter_2.permission. ACCESS_BOOK_SERVICE"); if (check == PackageManager.PERMISSION_DENIED) { return false; } String packageName = null; String[] packages = getPackageManager().getPackagesForUid(getCalling- Uid()); if (packages ! = null && packages.length > 0) { packageName = packages[0]; } if (! packageName.startsWith("com.ryg")) { return false; } return super.onTransact(code, data, reply, flags); } 上面介绍了两种AIDL中常用的权限验证方法,但是**肯定还有其他方法可以做权限验证,比如为Service指定android:permission属性等**,这里就不一一进行介绍了。到这里为止,本节的内容就全部结束了,读者应该对AIDL的使用过程有很深入的理解了,接下来会介绍另一个IPC方式,那就是使用ContentProvider。