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 {
 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             @Override
107             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 接口。

接下来分析下,该类的工作机制。仔细看,可以发现,该类主要分成三个部分:

第一部分我们不用管它,主要看 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 方法的流程如下:

以上,就是系统生成的 IBookManager 的工作过程,需要注意下,服务端的 onTransact 方法是运行在 Binder 线程池中的。由于 IBookManager.Stub 类继承 Binder,所以上述分析即 Binder 的工作机制,简单总结下:

或者参考下图理解下:

 

由此可见,Binder 在 AIDL 中承载着重要的职能,是 AIDL 的核心,理解了 Binder 的工作机制,其实在很多方面都很有用。

AIDL 的使用

一套 AIDL 服务搭建的步骤如下:

接下来,我们使用上面的 IBookManager 来实现 AIDL。

  1. 创建 .aidl 文件
    直接使用上面创建好的 Book.aidl、IBookManager.aidl 文件即可
  2. 创建服务端
    创建一个 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 mBookList
    23         }
    24 
    25         override fun addBook(book: Book?) {
    26             mBookList.add(book)
    27         }
    28 
    29     }
    30 
    31     override fun onBind(intent: Intent): IBinder {
    32         return mBinder
    33     }
    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>
  3. 客户端绑定服务端的 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)
  4. 与服务端通信
    与服务器绑定成功后,首先在 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 
    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.bookList
    35                 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.bookList
    45                 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 AIDL浅析及异步使用

全文结束