AIDL:Android Interface Definition Language,即 Android 接口定义语言。
AIDL 是什么
Android 系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。
为了使其他的应用程序也可以访问本应用程序提供的服务,Android 系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于 RPC 的解决方案一样,Android 使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道 Android 四大组件中的 3 种(Activity、BroadcastReceiver和ContentProvider)都可以进行跨进程访问,另外一种 Android 组件 Service 同样可以。因此,可以将这种可以跨进程访问的服务称为 AIDL(Android Interface Definition Language)服务。
在介绍 AIDL 的使用以及其它特性前,我们先来了解下 AIDL 的核心——Binder。
Android 的 Binder 机制浅析
看过一些关于 Binder 的文章,总得来说 Binder 机制的底层实现很复杂,相当复杂,要完全搞清楚,得花大量的时间。从某种角度来说,个人觉得,对于 Binder,我们只需要了解其上层原理以及使用方法即可。
直观来看,从代码的角度来说,Binder 是 Android 系统源码中的一个类,它实现了 IBinder 接口;从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式;从 Android Framework 角度来讲,Binder 是 ServiceManager 连接各种 Manager(ActivityManager、WindowManager 等等)和相应 ManagerService 的桥梁;从 Android 应用层来说,Binder 是客户端和服务端进行通信的媒介,当 bindService 的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象,通过这个 Binder 对象,客户端就可以和服务端进行通信,这里的服务包括普通服务和基于 AIDL 的服务。
接下来,我们通过一个 AIDL 示例,来分析 Binder 的工作机制。在工程目录中新建一个名为 aidl 的 package,然后新建 Book.Java、Book.aidl(创建此文件时,as 会提示已存在,需要先用其它命令,创建成功后再重命名为 Book.aidl )和 IBookManager.aidl,代码如下:
// Book.java package com.cy.ipcsample.aidl; import android.os.Parcel; import android.os.Parcelable; /** * 数据类 * @author cspecialy * @version v1.0.0 * @date 2018/5/14 21:38 */ public class Book implements Parcelable { public int bookId; public String bookName; public Book( int bookId, String bookName) { this .bookId = bookId; this .bookName = bookName; } protected Book(Parcel in) { bookId = in.readInt(); bookName = in.readString(); } public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray( int size) { return new Book[size]; } }; @Override public int describeContents() { return 0 ; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(bookId); parcel.writeString(bookName); } @Override public String toString() { return "Book{" + "bookId=" + bookId + ", bookName='" + bookName + '\ '' + '}' ; } } |
1 // Book.aidl2 package com.cy.ipcsample.aidl;3 4 parcelable Book;/
1 // IBookManager.aidl 2 package com.cy.ipcsample.aidl; 3 4 import com.cy.ipcsample.aidl.Book; 5 import com.cy.ipcsample.aidl.IOnNewBookArrivedListener; 6 7 interface IBookManager { 8 List<Book> getBookList(); 9 void addBook(in Book book);10 void registerListener(IOnNewBookArrivedListener listener);11 void unRegisterListener(IOnNewBookArrivedListener listener);12 }
创建好三个文件之后,编译一下,as 会在app/build/generated/source/aidl/debug 目录下的 com.cy.ipcsample 包中生成一个名为 IBookManager.Java 的类,如下图所示:
这是系统生成的 Binder 类,接下来我们要利用这个类来分析 Binder 的工作原理。其代码如下:(生成的代码格式很乱,可以格式化代码之后看)
1 /* 2 * This file is auto-generated. DO NOT MODIFY. 3 * Original file: G:\\Android\\Github\\Bugly-Android-Demo\\sample\\ipcsample\\src\\main\\aidl\\com\\cy\\ipcsample 4 * \\aidl\\IBookManager.aidl 5 */ 6 package com.cy.ipcsample.aidl; 7 8 public interface IBookManager extends android.os.IInterface { 9 public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException; 10 11 public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException; 12 13 /** Local-side IPC implementation stub class. */ 14 public static abstract class Stub extends android.os.Binder implements com.cy.ipcsample.aidl.IBookManager { 15 static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); 16 static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); 17 private static final java.lang.String DESCRIPTOR = "com.cy.ipcsample.aidl.IBookManager"; 18 19 /** Construct the stub at attach it to the interface. */ 20 public Stub() { 21 this.attachInterface(this, DESCRIPTOR); 22 } 23 24 /** 25 * Cast an IBinder object into an com.cy.ipcsample.aidl.IBookManager interface, 26 * generating a proxy if needed. 27 */ 28 public static com.cy.ipcsample.aidl.IBookManager asInterface(android.os.IBinder obj) { 29 if ((obj == null)) { 30 return null; 31 } 32 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 33 if (((iin != null) && (iin instanceof com.cy.ipcsample.aidl.IBookManager))) { 34 return ((com.cy.ipcsample.aidl.IBookManager) iin); 35 } 36 return new com.cy.ipcsample.aidl.IBookManager.Stub.Proxy(obj); 37 } 38 39 @Override 40 public android.os.IBinder asBinder() { 41 return this; 42 } 43 44 @Override 45 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws 46 android.os.RemoteException { 47 switch (code) { 48 case INTERFACE_TRANSACTION: { 49 reply.writeString(DESCRIPTOR); 50 return true; 51 } 52 case TRANSACTION_getBookList: { 53 data.enforceInterface(DESCRIPTOR); 54 java.util.List<com.cy.ipcsample.aidl.Book> _result = this.getBookList(); 55 reply.writeNoException(); 56 reply.writeTypedList(_result); 57 return true; 58 } 59 case TRANSACTION_addBook: { 60 data.enforceInterface(DESCRIPTOR); 61 com.cy.ipcsample.aidl.Book _arg0; 62 if ((0 != data.readInt())) { 63 _arg0 = com.cy.ipcsample.aidl.Book.CREATOR.createFromParcel(data); 64 } else { 65 _arg0 = null; 66 } 67 this.addBook(_arg0); 68 reply.writeNoException(); 69 return true; 70 } 71 } 72 return super.onTransact(code, data, reply, flags); 73 } 74 75 private static class Proxy implements com.cy.ipcsample.aidl.IBookManager { 76 private android.os.IBinder mRemote; 77 78 Proxy(android.os.IBinder remote) { 79 mRemote = remote; 80 } 81 82 public java.lang.String getInterfaceDescriptor() { 83 return DESCRIPTOR; 84 } @Override 85 public android.os.IBinder asBinder() { 86 return mRemote; 87 } 88 89 @Override 90 public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException { 91 android.os.Parcel _data = android.os.Parcel.obtain(); 92 android.os.Parcel _reply = android.os.Parcel.obtain(); 93 java.util.List<com.cy.ipcsample.aidl.Book> _result; 94 try { 95 _data.writeInterfaceToken(DESCRIPTOR); 96 mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0); 97 _reply.readException(); 98 _result = _reply.createTypedArrayList(com.cy.ipcsample.aidl.Book.CREATOR); 99 } finally {100 _reply.recycle();101 _data.recycle();102 }103 return _result;104 }105 106 @Override107 public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException {108 android.os.Parcel _data = android.os.Parcel.obtain();109 android.os.Parcel _reply = android.os.Parcel.obtain();110 try {111 _data.writeInterfaceToken(DESCRIPTOR);112 if ((book != null)) {113 _data.writeInt(1);114 book.writeToParcel(_data, 0);115 } else {116 _data.writeInt(0);117 }118 mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);119 _reply.readException();120 } finally {121 _reply.recycle();122 _data.recycle();123 }124 }125 126 127 }128 }129 }
可见,系统为我们生成了一个 IBookManager 接口,它继承了 IInterface 这个接口,所以这里要注意下,所有可以在 Binder 中传输的接口,都需要继承 IInterface 接口。
接下来分析下,该类的工作机制。仔细看,可以发现,该类主要分成三个部分:
定义自身方法( getBookList 方法和 addBook 方法);
内部静态类-Stub,该类继承 Binder,同时实现 IBookManager 接口
Stub的内部代理类-Proxy,也实现 IBookManager 接口
第一部分我们不用管它,主要看 Stub 类和 Proxy 类。在 Stub 中,首先声明了两个用于标识 IBookManager 方法的整型变量,这两个变量用于标识在 transact 过程中客户端所请求的是哪个方法。接着,是 asInterface 方法,该方法用于将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象,该方法通过调用 Binder 的 queryLocalInterface 方法,判断客户端和服务端是否处于同一进程,如果客户端、服务端处于同一进程,那么此方法直接返回服务端的 Stub,否则,返回 Stub 的代理对象 Proxy。queryLocalInterface 实现如下:
/** * Use information supplied to attachInterface() to return the * associated IInterface if it matches the requested * descriptor. */public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) { if (mDescriptor.equals(descriptor)) { return mOwner; } return null; }
mOwner 是在 Stub 构造函数中传进去的 this 参数。
接下来是 Stub 的代理类 Stub.Proxy,由上面的分析可知,Stub.Proxy 是运行在客户端的(由 asInterface 方法返回给客户端的对象),Stub.Proxy对象创建后,持有服务端的 Binder 对象,用于客户端请求时调用服务端方法进行远程调用。客户端在向服务端发起请求时,调用 Stub.Proxy 的相应方法,Stub.Proxy 方法的流程如下:
首先创建该方法所需要的输入型 Parcel 对象 _data、输出型对象 _reply 和返回值对象(如果有);
然后把该方法的参数信息写入 _data 中(如果有);
接着调用 transact 方法进行 RPC(远程过程调用)请求,同时当前线程挂起;
然后在 transact 方法通过服务端的 Binder 对象调用服务端的 onTransact 方法,即 Stub 中的 onTransact 方法;
onTransact 方法返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程返回的结果;
最后返回 _reply 中的数据(如果客户端请求方法需要返回值)。
以上,就是系统生成的 IBookManager 的工作过程,需要注意下,服务端的 onTransact 方法是运行在 Binder 线程池中的。由于 IBookManager.Stub 类继承 Binder,所以上述分析即 Binder 的工作机制,简单总结下:
在客户端和服务端连接时(一般通过 bindService 方法),服务端通过 asInterface 方法给客户端返回一个 IInterface 接口类型的对象(如果客户端和服务端在同一个进程,则返回服务端的 Binder 对象,否则返回 服务端 Binder 对象的代理);
客户端通过该对象向服务端进行请求,如果客户端和服务端不在同一个进程,则通过该对象所代理的 Binder 对象进行 RPC 请求,否则,直接通过 Binder 对象调用服务端相应方法。
或者参考下图理解下:
由此可见,Binder 在 AIDL 中承载着重要的职能,是 AIDL 的核心,理解了 Binder 的工作机制,其实在很多方面都很有用。
AIDL 的使用
一套 AIDL 服务搭建的步骤如下:
创建 .aidl 文件,系统生成相应的继承 IInterface 的接口类(暴露给客户端的接口)。
创建一个 Service(服务端),实现 .aidl 文件中的接口。
创建客户端,绑定服务端的 Service。
客户端绑定服务端成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的 IInterface 类型。调用 AIDL 中的方法,实现和服务端的通信。
接下来,我们使用上面的 IBookManager 来实现 AIDL。
创建 .aidl 文件
直接使用上面创建好的 Book.aidl、IBookManager.aidl 文件即可创建服务端
创建一个 Service,命名为 BookManagerService,代码如下:1 package com.cy.ipcsample.aidl 2 3 import android.app.Service 4 import android.content.Intent 5 import android.os.IBinder 6 import android.os.RemoteCallbackList 7 import android.util.Log 8 import java.util.concurrent.CopyOnWriteArrayList 9 10 class BookManagerService : Service() {11 private val TAG = "BookManagerService"12 13 private val mBookList = CopyOnWriteArrayList<Book>()14 private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>()15 16 /**17 * 实现 AIDL 接口的 Binder 对象,客户端绑定服务端时,直接返回此 Binder 对象18 */19 private val mBinder = object : IBookManager.Stub() {20 21 override fun getBookList(): MutableList<Book> {22 return mBookList23 }24 25 override fun addBook(book: Book?) {26 mBookList.add(book)27 }28 29 }30 31 override fun onBind(intent: Intent): IBinder {32 return mBinder33 }34 35 override fun onCreate() {36 super.onCreate()37 38 // 创建两本图书39 mBookList.add(Book(1, "Android"))40 mBookList.add(Book(2, "iOS"))41 }42 }
然后在 AndroidManifest 中注册 Service,注意启动多进程:
<service android:name=".aidl.BookManagerService" android:process="com.cy.ipcsample.bookManagerService"></service>
客户端绑定服务端的 Service
客户端的创建,直接使用 Activity 即可,绑定远程服务的代码如下:private val mConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { } /** * 连接远程服务成功的回调 */ override fun onServiceConnected(name: ComponentName?, service: IBinder?) { } }// 绑定远程服务bindService(Intent(this, BookManagerService::class.java), mConnection, Context.BIND_AUTO_CREATE)
与服务端通信
与服务器绑定成功后,首先在 ServiceConnection 的回调中,将服务端返回的 Binder 对象转换成 AIDL 接口所属的对象,就可以调用相应方法和服务端通信了,代码如下:// 将服务端返回的 Binder 对象转换成 IBookManager 对象val bookManager = IBookManager.Stub.asInterface(service)// 与服务端通信try { // 获取图书列表 val list = bookManager.bookList Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}") Log.i(TAG, "query book list: $list") // 添加一本图书 val book = Book(3, "Android开发艺术探索") bookManager.addBook(book) Log.i(TAG, "add book: $book") // 获取图书列表 val newList = bookManager.bookList Log.i(TAG, "query book list: $newList") } catch (e: RemoteException) { e.printStackTrace() }
代码中,我们先查询了服务端的图书列表,接着向服务端添加一本书
Android艺术开发探索
,然后再次查询看是否添加成功。运行下看 log,如下图所示:可见,运行结果和预期结果一致。完整的客户端代码如下:
1 package com.cy.ipcsample.aidl 2 3 import android.content.ComponentName 4 import android.content.Context 5 import android.content.Intent 6 import android.content.ServiceConnection 7 import android.os.Bundle 8 import android.os.IBinder 9 import android.os.RemoteException10 import android.support.v7.app.AppCompatActivity11 import android.util.Log12 import com.cy.ipcsample.R13 14 class BookManagerActivity : AppCompatActivity() {15 16 private val TAG = "BookManagerActivity"17 18 private val mConnection = object : ServiceConnection {19 20 override fun onServiceDisconnected(name: ComponentName?) {21 Log.d(TAG, "binder died.")22 }23 24 /**25 * 连接远程服务成功的回调26 */27 override fun onServiceConnected(name: ComponentName?, service: IBinder?) {28 // 将服务端返回的 Binder 对象转换成 IBookManager 对象29 val bookManager = IBookManager.Stub.asInterface(service)30 31 // 与服务端通信32 try {33 // 获取图书列表34 val list = bookManager.bookList35 Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}")36 Log.i(TAG, "query book list: $list")37 38 // 添加一本图书39 val book = Book(3, "Android开发艺术探索")40 bookManager.addBook(book)41 Log.i(TAG, "add book: $book")42 43 // 获取图书列表44 val newList = bookManager.bookList45 Log.i(TAG, "query book list: $newList")46 } catch (e: RemoteException) {47 e.printStackTrace()48 }49 }50 51 }52 53 override fun onCreate(savedInstanceState: Bundle?) {54 super.onCreate(savedInstanceState)55 setContentView(R.layout.activity_book_manager)56 57 // 绑定远程服务58 bindService(Intent(this, BookManagerService::class.java),59 mConnection, Context.BIND_AUTO_CREATE)60 }61 62 override fun onDestroy() {63 unbindService(mConnection)64 super.onDestroy()65 }66 }
到此,一个简单的 AIDL 示例就完成了,当然,AIDL 的使用远没有那么简单,还有很多情景需要考虑的,比如:客户端需要随时服务端在状态变化时同时客户端,类似观察这模式,那么订阅与反订阅怎么实现;Binder 意外死亡,怎么重连等等,更多内容,可以参考《Android艺术开发探索》,电子书下载
AIDL 的异步调用
AIDL 的调用过程是同步还是异步的?
这个问题其实看这一节的标题大家都知道了,AIDL 的调用过程是同步的。同时,上面分析 Binder 的机制时,也提到了,客户端进行远程 RPC 请求时,线程会挂起,等待结果,由此也可知,AIDL 的调用过程是同步的,下面来验证下。
首先,在服务端的 BookManagerService 中实现的 Binder 对象的 getBookList 方法添加延时执行,如下图所示:
然后,在客户端的 BookManagerActivity 中添加一个按钮,点击按钮时调用服务端 Binder 的 getBookList 做 RPC 请求。代码如下:
运行后,连续点击按钮,结果如下图所示:
由图所知,连续点击按钮过后一段时间,出现了无响应错误( ANR ),由此可知,客户端向服务端做 RPC 请求时,是同步的,也就是说:AIDL 的调用过程是同步的。
AIDL 的调用过程是同步的,当我们需要服务端做耗时操作时,肯定是不能使用同步调用的,否则轻者影响用户体验,重者直接 ANR 或者应用崩溃。那么如何使 AIDL 的调用过程是异步的呢?
其实也很简单,只需要把调用放到非 UI 线程即可,如果要对调用的返回做 UI 更新的话,再通过 Handler 处理即可。如下图所示:
本文参考
《Android开发艺术探索》 第二章