时人不识凌云木,直待凌云始道高。这篇文章主要讲述Android AIDL浅析及异步使用相关的知识,希望能为你提供帮助。
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.aidl 2 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 { 8List< Book> getBookList(); 9void addBook(in Book book); 10void registerListener(IOnNewBookArrivedListener listener); 11void 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 { 9public java.util.List< com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException; 10 11public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException; 12 13/** Local-side IPC implementation stub class. */ 14public static abstract class Stub extends android.os.Binder implements com.cy.ipcsample.aidl.IBookManager { 15static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); 16static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); 17private static final java.lang.String DESCRIPTOR = "com.cy.ipcsample.aidl.IBookManager"; 18 19/** Construct the stub at attach it to the interface. */ 20public Stub() { 21this.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*/ 28public static com.cy.ipcsample.aidl.IBookManager asInterface(android.os.IBinder obj) { 29if ((obj == null)) { 30return null; 31} 32android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 33if (((iin != null) & & (iin instanceof com.cy.ipcsample.aidl.IBookManager))) { 34return ((com.cy.ipcsample.aidl.IBookManager) iin); 35} 36return new com.cy.ipcsample.aidl.IBookManager.Stub.Proxy(obj); 37} 38 39@Override 40public android.os.IBinder asBinder() { 41return this; 42} 43 44@Override 45public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws 46android.os.RemoteException { 47switch (code) { 48case INTERFACE_TRANSACTION: { 49reply.writeString(DESCRIPTOR); 50return true; 51} 52case TRANSACTION_getBookList: { 53data.enforceInterface(DESCRIPTOR); 54java.util.List< com.cy.ipcsample.aidl.Book> _result = this.getBookList(); 55reply.writeNoException(); 56reply.writeTypedList(_result); 57return true; 58} 59case TRANSACTION_addBook: { 60data.enforceInterface(DESCRIPTOR); 61com.cy.ipcsample.aidl.Book _arg0; 62if ((0 != data.readInt())) { 63_arg0 = com.cy.ipcsample.aidl.Book.CREATOR.createFromParcel(data); 64} else { 65_arg0 = null; 66} 67this.addBook(_arg0); 68reply.writeNoException(); 69return true; 70} 71} 72return super.onTransact(code, data, reply, flags); 73} 74 75private static class Proxy implements com.cy.ipcsample.aidl.IBookManager { 76private android.os.IBinder mRemote; 77 78Proxy(android.os.IBinder remote) { 79mRemote = remote; 80} 81 82public java.lang.String getInterfaceDescriptor() { 83return DESCRIPTOR; 84}@Override 85public android.os.IBinder asBinder() { 86return mRemote; 87} 88 89@Override 90public java.util.List< com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException { 91android.os.Parcel _data = https://www.songbingjia.com/android/android.os.Parcel.obtain(); 92android.os.Parcel _reply = android.os.Parcel.obtain(); 93java.util.List< com.cy.ipcsample.aidl.Book> _result; 94try { 95_data.writeInterfaceToken(DESCRIPTOR); 96mRemote.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} 103return _result; 104} 105 106@Override 107public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException { 108android.os.Parcel _data = android.os.Parcel.obtain(); 109android.os.Parcel _reply = android.os.Parcel.obtain(); 110try { 111_data.writeInterfaceToken(DESCRIPTOR); 112if ((book != null)) { 113_data.writeInt(1); 114book.writeToParcel(_data, 0); 115} else { 116_data.writeInt(0); 117} 118mRemote.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 接口
/** * 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 中的数据(如果客户端请求方法需要返回值)。
- 在客户端和服务端连接时(一般通过 bindService 方法),服务端通过 asInterface 方法给客户端返回一个 IInterface 接口类型的对象(如果客户端和服务端在同一个进程,则返回服务端的 Binder 对象,否则返回 服务端 Binder 对象的代理);
- 客户端通过该对象向服务端进行请求,如果客户端和服务端不在同一个进程,则通过该对象所代理的 Binder 对象进行 RPC 请求,否则,直接通过 Binder 对象调用服务端相应方法。
文章图片
由此可见,Binder 在 AIDL 中承载着重要的职能,是 AIDL 的核心,理解了 Binder 的工作机制,其实在很多方面都很有用。
AIDL 的使用一套 AIDL 服务搭建的步骤如下:
- 创建 .aidl 文件,系统生成相应的继承 IInterface 的接口类(暴露给客户端的接口)。
- 创建一个 Service(服务端),实现 .aidl 文件中的接口。
- 创建客户端,绑定服务端的 Service。
- 客户端绑定服务端成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的 IInterface 类型。调用 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() { 11private val TAG = "BookManagerService" 12 13private val mBookList = CopyOnWriteArrayList< Book> () 14private val mListenerList = RemoteCallbackList< IOnNewBookArrivedListener> () 15 16/** 17* 实现 AIDL 接口的 Binder 对象,客户端绑定服务端时,直接返回此 Binder 对象 18*/ 19private val mBinder = object : IBookManager.Stub() { 20 21override fun getBookList(): MutableList< Book> { 22return mBookList 23} 24 25override fun addBook(book: Book?) { 26mBookList.add(book) 27} 28 29} 30 31override fun onBind(intent: Intent): IBinder { 32return mBinder 33} 34 35override fun onCreate() { 36super.onCreate() 37 38// 创建两本图书 39mBookList.add(Book(1, "Android")) 40mBookList.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.RemoteException 10 import android.support.v7.app.AppCompatActivity 11 import android.util.Log 12 import com.cy.ipcsample.R 13 14 class BookManagerActivity : AppCompatActivity() { 15 16private val TAG = "BookManagerActivity" 17 18private val mConnection = object : ServiceConnection { 19 20override fun onServiceDisconnected(name: ComponentName?) { 21Log.d(TAG, "binder died.") 22} 23 24/** 25* 连接远程服务成功的回调 26*/ 27override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 28// 将服务端返回的 Binder 对象转换成 IBookManager 对象 29val bookManager = IBookManager.Stub.asInterface(service) 30 31// 与服务端通信 32try { 33// 获取图书列表 34val list = bookManager.bookList 35Log.i(TAG, "query book list, list type: ${list.javaClass.canonicalName}") 36Log.i(TAG, "query book list: $list") 37 38// 添加一本图书 39val book = Book(3, "Android开发艺术探索") 40bookManager.addBook(book) 41Log.i(TAG, "add book: $book") 42 43// 获取图书列表 44val newList = bookManager.bookList 45Log.i(TAG, "query book list: $newList") 46} catch (e: RemoteException) { 47e.printStackTrace() 48} 49} 50 51} 52 53override fun onCreate(savedInstanceState: Bundle?) { 54super.onCreate(savedInstanceState) 55setContentView(R.layout.activity_book_manager) 56 57// 绑定远程服务 58bindService(Intent(this, BookManagerService::class.java), 59mConnection, Context.BIND_AUTO_CREATE) 60} 61 62override fun onDestroy() { 63unbindService(mConnection) 64super.onDestroy() 65} 66 }
AIDL 的异步调用AIDL 的调用过程是同步还是异步的?
这个问题其实看这一节的标题大家都知道了,AIDL 的调用过程是同步的。同时,上面分析 Binder 的机制时,也提到了,客户端进行远程 RPC 请求时,线程会挂起,等待结果,由此也可知,AIDL 的调用过程是同步的,下面来验证下。
首先,在服务端的 BookManagerService 中实现的 Binder 对象的 getBookList 方法添加延时执行,如下图所示:
文章图片
然后,在客户端的 BookManagerActivity 中添加一个按钮,点击按钮时调用服务端 Binder 的 getBookList 做 RPC 请求。代码如下:
文章图片
运行后,连续点击按钮,结果如下图所示:
文章图片
由图所知,连续点击按钮过后一段时间,出现了无响应错误( ANR ),由此可知,客户端向服务端做 RPC 请求时,是同步的,也就是说:AIDL 的调用过程是同步的。
AIDL 的调用过程是同步的,当我们需要服务端做耗时操作时,肯定是不能使用同步调用的,否则轻者影响用户体验,重者直接 ANR 或者应用崩溃。那么如何使 AIDL 的调用过程是异步的呢?
其实也很简单,只需要把调用放到非 UI 线程即可,如果要对调用的返回做 UI 更新的话,再通过 Handler 处理即可。如下图所示:
【Android AIDL浅析及异步使用】
文章图片
本文参考
- 《Android开发艺术探索》 第二章
推荐阅读
- 2017-2018-2 20165312 实验四《Android程序设计》实验报告
- 4-拷贝我的eclipse写安卓的配置说明
- 20165309 实验四 Android程序设计
- [Android] 代码中动态设置shape
- Android项目Gradle内网配置
- 20165322 实验四 《Android程序设计》
- 2017-2018-2 20165330 实验四《Android程序设计》实验报告
- 20165313Java实验四 Android程序设计
- 在Mac上用bootcamp安装windows,使用Android studio启动模拟器时蓝屏问题的解决方法