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 + '\'' +
- '}';
- }
- }
- // Book.aidl
- package com.cy.ipcsample.aidl;
- parcelable Book;/
- // IBookManager.aidl
- package com.cy.ipcsample.aidl;
- import com.cy.ipcsample.aidl.Book;
- import com.cy.ipcsample.aidl.IOnNewBookArrivedListener;
- interface IBookManager {
- List<Book> getBookList();
- void addBook(in Book book);
- void registerListener(IOnNewBookArrivedListener listener);
- void unRegisterListener(IOnNewBookArrivedListener listener);
- }
- 创建好三个文件之后,编译一下,as 会在
app/build/generated/source/aidl/debug
目录下的 com.cy.ipcsample 包中生成一个名为 IBookManager.Java 的类,如下图所示:
这是系统生成的 Binder 类,接下来我们要利用这个类来分析 Binder 的工作原理。其代码如下:(生成的代码格式很乱,可以格式化代码之后看)
- /*
- * This file is auto-generated. DO NOT MODIFY.
- * Original file: G:\\Android\\Github\\Bugly-Android-Demo\\sample\\ipcsample\\src\\main\\aidl\\com\\cy\\ipcsample
- * \\aidl\\IBookManager.aidl
- */
- package com.cy.ipcsample.aidl;
- public interface IBookManager extends android.os.IInterface {
- public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException;
- public void addBook(com.cy.ipcsample.aidl.Book book) throws android.os.RemoteException;
- /** Local-side IPC implementation stub class. */
- public static abstract class Stub extends android.os.Binder implements com.cy.ipcsample.aidl.IBookManager {
- static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
- static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
- private static final java.lang.String DESCRIPTOR = "com.cy.ipcsample.aidl.IBookManager";
- /** Construct the stub at attach it to the interface. */
- public Stub() {
- this.attachInterface(this, DESCRIPTOR);
- }
- /**
- * Cast an IBinder object into an com.cy.ipcsample.aidl.IBookManager interface,
- * generating a proxy if needed.
- */
- public static com.cy.ipcsample.aidl.IBookManager asInterface(android.os.IBinder obj) {
- if ((obj == null)) {
- return null;
- }
- android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin != null) && (iin instanceof com.cy.ipcsample.aidl.IBookManager))) {
- return ((com.cy.ipcsample.aidl.IBookManager) iin);
- }
- return new com.cy.ipcsample.aidl.IBookManager.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_getBookList: {
- data.enforceInterface(DESCRIPTOR);
- java.util.List<com.cy.ipcsample.aidl.Book> _result = this.getBookList();
- reply.writeNoException();
- reply.writeTypedList(_result);
- return true;
- }
- case TRANSACTION_addBook: {
- data.enforceInterface(DESCRIPTOR);
- com.cy.ipcsample.aidl.Book _arg0;
- if ((0 != data.readInt())) {
- _arg0 = com.cy.ipcsample.aidl.Book.CREATOR.createFromParcel(data);
- } else {
- _arg0 = null;
- }
- this.addBook(_arg0);
- reply.writeNoException();
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
- private static class Proxy implements com.cy.ipcsample.aidl.IBookManager {
- private android.os.IBinder mRemote;
- Proxy(android.os.IBinder remote) {
- mRemote = remote;
- }
- public java.lang.String getInterfaceDescriptor() {
- return DESCRIPTOR;
- } @Override
- public android.os.IBinder asBinder() {
- return mRemote;
- }
- @Override
- public java.util.List<com.cy.ipcsample.aidl.Book> getBookList() throws android.os.RemoteException {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- java.util.List<com.cy.ipcsample.aidl.Book> _result;
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
- _reply.readException();
- _result = _reply.createTypedArrayList(com.cy.ipcsample.aidl.Book.CREATOR);
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- return _result;
- }
- @Override
- public void addBook(com.cy.ipcsample.aidl.Book book) 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 ((book != null)) {
- _data.writeInt(1);
- book.writeToParcel(_data, 0);
- } else {
- _data.writeInt(0);
- }
- mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
- _reply.readException();
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }
- }
- }
- }
可见,系统为我们生成了一个 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,代码如下:- package com.cy.ipcsample.aidl
- import android.app.Service
- import android.content.Intent
- import android.os.IBinder
- import android.os.RemoteCallbackList
- import android.util.Log
- import java.util.concurrent.CopyOnWriteArrayList
- class BookManagerService : Service() {
- private val TAG = "BookManagerService"
- private val mBookList = CopyOnWriteArrayList<Book>()
- private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>()
- /**
- * 实现 AIDL 接口的 Binder 对象,客户端绑定服务端时,直接返回此 Binder 对象
- */
- private val mBinder = object : IBookManager.Stub() {
- override fun getBookList(): MutableList<Book> {
- return mBookList
- }
- override fun addBook(book: Book?) {
- mBookList.add(book)
- }
- }
- override fun onBind(intent: Intent): IBinder {
- return mBinder
- }
- override fun onCreate() {
- super.onCreate()
- // 创建两本图书
- mBookList.add(Book(1, "Android"))
- mBookList.add(Book(2, "iOS"))
- }
- }
然后在 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,如下图所示:可见,运行结果和预期结果一致。完整的客户端代码如下:
- package com.cy.ipcsample.aidl
- import android.content.ComponentName
- import android.content.Context
- import android.content.Intent
- import android.content.ServiceConnection
- import android.os.Bundle
- import android.os.IBinder
- import android.os.RemoteException
- import android.support.v7.app.AppCompatActivity
- import android.util.Log
- import com.cy.ipcsample.R
- class BookManagerActivity : AppCompatActivity() {
- private val TAG = "BookManagerActivity"
- private val mConnection = object : ServiceConnection {
- override fun onServiceDisconnected(name: ComponentName?) {
- Log.d(TAG, "binder died.")
- }
- /**
- * 连接远程服务成功的回调
- */
- override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
- // 将服务端返回的 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()
- }
- }
- }
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_book_manager)
- // 绑定远程服务
- bindService(Intent(this, BookManagerService::class.java),
- mConnection, Context.BIND_AUTO_CREATE)
- }
- override fun onDestroy() {
- unbindService(mConnection)
- super.onDestroy()
- }
- }
- // 将服务端返回的 Binder 对象转换成 IBookManager 对象
到此,一个简单的 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开发艺术探索》 第二章
Android AIDL浅析及异步使用的更多相关文章
- Android AIDL自动生成Java文件测试
/******************************************************************************** * Android AIDL自动生成 ...
- Using self-defined Parcelable objects during an Android AIDL RPC / IPC call
Using self-defined Parcelable objects during an Android AIDL RPC / IPC call In my previous post “Usi ...
- Android引入高速缓存的异步加载全分辨率
Android引进高速缓存的异步加载全分辨率 为什么要缓存 通过图像缩放,我们这样做是对的异步加载优化的大图,但现在的App这不仅是一款高清大图.图.动不动就是图文混排.以图代文,假设这些图片都载入到 ...
- AIDL/IPC Android AIDL/IPC 进程通信机制——超具体解说及使用方法案例剖析(播放器)
首先引申下AIDL.什么是AIDL呢?IPC? ------ Designing a Remote Interface Using AIDL 通常情况下,我们在同一进程内会使用Binder.Broad ...
- (转载)你真的理解Android AIDL中的in,out,inout么?
前言 这其实是一个很小的知识点,大部分人在使用AIDL的过程中也基本没有因为这个出现过错误,正因为它小,所以在大部分的网上关于AIDL的文章中,它都被忽视了——或者并没有,但所占篇幅甚小,且基本上都是 ...
- Android中的AsyncTask异步任务的简单实例
在 Android中的AsyncTask异步任务的简介 一文中.已经对 安卓 异步任务操作做了简单的介绍.这里,直接将上文中的异步任务做了一个实例.实现异步操作更新UI线程,相比开启子线程更新来说逻辑 ...
- Android AIDL使用详解_Android IPC 机制详解
一.概述 AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来 ...
- Android aidl Binder框架浅析
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/38461079 ,本文出自[张鸿洋的博客] 1.概述 Binder能干什么?B ...
- android aidl
参考: http://blog.csdn.net/u014614038/article/details/48399935 本文提供了一个关于AIDL使用的简单易懂的例子,分为客户端和服务端两部分,分别 ...
随机推荐
- Maven学习总结(五):maven命令的含义及用法
Maven有许多命令,不管是在命令行(1),还是在Myecplise10的Maven项目--右键Run As(2),还是IDEA的左下角--Maven Projects--Maven项目名--Life ...
- 关于 PHPMailer 邮件发送类的使用心得(含多文件上传)
This is important for send mail PHPMailer 核心文件 class.phpmailer.php class.phpmaileroauth.php class.ph ...
- 【MFC】CHtmlView::GetSource中文乱码的问题
在MFC的SDI中,使用CHtmlView::GetSource来获取网页源码,保存到本地,发现中文中的一部分乱码,有些中文正常.自己先试着转码等各种尝试,发现一无所获.网上也没有正确的解决方案. 自 ...
- LintCode2016年8月8日算法比赛----中序遍历和后序遍历构造二叉树
中序遍历和后序遍历构造二叉树 题目描述 根据中序遍历和后序遍历构造二叉树 注意事项 你可以假设树中不存在相同数值的节点 样例 给出树的中序遍历: [1,2,3] 和后序遍历: [1,3,2] 返回如下 ...
- 上海ctf2017 pwn100 && pwn200
前言 分析的 idb 文件在这: https://gitee.com/hac425/blog_data/tree/master/shanghaictf pwn100 程序是一个经典的 堆管理程序,基本 ...
- 结对编程项目总结(core2组)
结对编程项目总结(core2组) 作业---四则运算(Core 第二组) ----by 吴雪晴 PB16061514 齐天杨 PB16060706 一.项目简介 项目的任务为制作一个给(貌似是?) ...
- 初识WCF1
参照: http://blog.csdn.net/songyefei/article/details/7363296 第一篇 Hello WCF WCF(Windows Communication F ...
- Linux下top命令监控性能状态
1.性能分析因素:CPU.内存.网络.磁盘读写 2.系统对应的应用类型主要分为以下两种: IO Bound:一般都是高负荷的内存使用以及存储系统,IO范畴的应用就是一个大数据处理的过程:通常数据库软件 ...
- Python学习---django重点之视图函数
django重点之视图函数 http请求中产生两个核心对象: http请求:HttpRequest对象,由Django自己创建 http响应:HttpResponse对象,由开发自己创建,必须返回一个 ...
- ZT Linux系统环境下的Socket编程详细解析
Linux系统环境下的Socket编程详细解析 来自: http://blog.163.com/jiangh_1982/blog/static/121950520082881457775/ 什么是So ...