携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情
前言
前面文章我们重点分析了Linux的进程划分,以及为了减少一次拷贝的内存映射技术,本篇文章我们就来介绍Android中使用最多也最具有代表性的IPC方式:AIDL。
本篇文章主要侧重AIDL的使用细节,然后重点分析AIDL生成的文件,通过手写AIDL生成文件来大致理解Binder机制,话不多说,直接开整。
正文
我们先来看看AIDL的使用。
AIDL使用
AIDL的原理是通过Binder来实现,而且是C/S架构,所以AIDL分为服务端和客户端2个方面来使用:
- 服务端:服务端首先需要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口。
从这个简单描述我们知道,作为服务端必须建立在Service上,这也非常好理解,Service作为四大组件之一,由Android系统统一管理,同时自己就可以被其他进程所启动;
- 客户端:客户端首先就需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL接口中的方法了。
从客户端实现我们可知这里的关键就是Binder了,本篇文章后面我们在使用AIDL时会简单说明一些Binder相关类的作用。
为了更好地说明AIDL使用的细节,这里我使用俩个APP交互的方式,而不是一个APP开启多进程。
AIDL接口的创建
AIDL接口的的创建非常重要,首先是需要定义在不同进程之间传递的自定义类型,比如我这里定义为Book:
//包名
package com.zyh.ipc;
import android.os.Parcel;
import android.os.Parcelable;
//必须实现Parcelable接口
public class Book implements Parcelable {
public int BookId;
public String BookName;
...省略
}
这是一个Book.java类,必须实现Parcelable接口,然后就是存放该类的目录,这里推荐把涉及到AIDL通信的自定义类都放到一个相同的目录中,这是因为俩个进行跨进程通信的APP会对自定义的类型对象进行序列化和反序列化,而这种序列化方式会记录类的信息,即使俩个APP中都定义了Book类,而且成员变量都一样,但是因为包名不一样,同样是无法反序列化的。
定义完自定义类型后,凡是在AIDL文件中用到的自定义Parcelable类型,都必须新建一个和它同名的AIDL文件,并且在其中声明它为Parcelable类型,而AIDL文件的目录,可以使用AS直接创建出来,这里在AIDL目录下创建一个Book.aidl:
//Book.aidl
package com.zyh.ipc;
parcelable Book;
再然后我们就可以定义AIDL接口了,该接口就是定义IPC通信的内容,注意这个接口和Java接口有一点不一样,它是只支持方法的,不支持声明静态常量,在本例中创建管理Book的接口IBookManager.aidl:
//包名
package com.zyh.ipc;
//必须手动导入自定义的Parcelable类型
import com.zyh.ipc.Book;
//接口
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
这里需要注意俩点:
- 就是前面定义的自定义Parcelable类型,在AIDL文件中必须手动导入。
- 在AIDL文件中,并不是所有的数据类型都可以使用的,AIDL文件支持的数据类型如下:
- 基本数据类型,包括int、long、char、boolean等;
- String和CharSequence;
- List只支持ArrayList,里面元素必须能被AIDL支持;
- Map只支持HashMap,里面的key和value的类型也必须被AIDL支持;
- Parcelable,即实现了Parcelable接口的对象。
创建完AIDL文件以及AIDL用到的自定义Parcelable类型,这时就需要把这些文件从服务端APP拷贝一份到客户端APP中,注意类的包名千万不要改变,当一端的类结构发生改变,在另一端必须替换为一样的。
服务端的实现
在定义完AIDL接口后,需要Build一下项目,这时在app/build/generated/aidl_source_out_dir/debug目录下会生成一个和AIDL接口同名的java文件,即IBookManager.java,关于这个文件我们后面会仔细分析。
这里先来看一下服务端如何实现,前面说了需要通过Service,直接定义一个服务BookManagerService:
class BookManagerService : Service() {
//服务端的书籍列表,这里需要使用线程安全的类来保存
private val mBookList = CopyOnWriteArrayList<Book>()
//实现接口的Binder
private val mBinder = object : IBookManager.Stub(){
override fun getBookList(): MutableList<Book> {
return mBookList
}
override fun addBook(book: Book?) {
mBookList.add(book!!)
}
}
//onBind回调中返回我们定义的Binder
override fun onBind(intent: Intent): IBinder {
return mBinder
}
//在服务创建时,往Book列表中添加俩本书
override fun onCreate() {
super.onCreate()
mBookList.add(Book(11,"Android"))
mBookList.add(Book(22,"Ios"))
}
}
这个类涉及的知识点比较多,我们来挨个分析:
- 保存Book的列表为什么要使用CopyOnWriteArrayList这个线程安全的类型,这是因为getBookList()和addBook()这俩个方法会在线程池中运行,当有多个客户端绑定该服务,同时调用这俩个方法,使用线程安全的集和可以保证数据正确。
类似的集和还有ConcurrentHashMap线程安全的集和。
- 这个onBind方法返回的类型是IBinder,这里的IBinder是一个接口,可以这么简单理解:所有通过Binder机制进行IPC的对象,都必须实现该类。
这里mBinder是IBookManager.Stud类的实例,这个类是AIDL文件生成的文件,该类定义如下:
public static abstract class Stub extends android.os.Binder implements com.zyh.ipc.IBookManager
可以发现它继承至Binder,这个Binder就是IBinder的实现类,同时实现了IBookManager接口,根据IBinder接口的特性,这里我们就可以认为这个mBinder就具有了跨进程的能力。
客户端的实现
前面既然说了对象实现了IBinder就有了跨进程的能力,那可以想象在客户端应该就是拿到刚刚在服务端定义的Binder对象,然后对这个Binder进行操作,所以客户端代码如下:
//通过bindService连接客户端APP的服务
findViewById<Button>(R.id.bindService).setOnClickListener {
bindService(
//这里的Intent除了需要action,还必须设置服务端APP的包名
Intent("com.zyh.ipc.BookManagerService").setPackage("com.zyh.ipc"),
conn,
BIND_AUTO_CREATE
)
}
//服务连接成功和断开的回调
private val conn = object : ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i("ipc", "onServiceConnected: ")
//通过asInterface方法把IBinder对象转成IBookManager类型
bookManager = IBookManager.Stub.asInterface(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.i("ipc", "onServiceDisconnected: ")
}
}
//查看书籍列表
findViewById<Button>(R.id.getBook).setOnClickListener {
val list = bookManager?.bookList
//看一下这个list的类型
Log.i("ipc", "onCreate: ${list?.javaClass?.name}")
for (book in list!!) {
Log.i("ipc", "onServiceConnected: ${book.BookId} ${book.BookName}")
}
}
这里的核心代码就是IBinder对象的asInterface了,这里可以看成就获取到了服务端的Binder了(真实原理我们后面再说)。
我们看一下打印:
2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onCreate: java.util.ArrayList
2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onServiceConnected: 11 Android
2022-07-28 15:11:54.804 21595-21595/com.zyh.client I/ipc: onServiceConnected: 22 Ios
这里会发现list的类型是ArrayList,为什么说这个呢 原因是刚开始我们说AIDL支持List,但是只支持ArrayList,但是我们在服务端代码中却用了CopyOnWriteArrayList,这个类定义:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
会发现它虽然是名字和ArrayList相关,但是它却不是继承至ArrayList,这就说明了数据在传输过程中,Binder对数据类型做了处理。
这里还有注意一点,就是客户端调用完getBookList方法后,会挂起当前线程,直到等待结果返回,是一个同步方法,所以当在服务端该方法执行时间过长,在主线程调用该方法可能会导致ANR。
这里我们可以做个小小总结:
- 服务端和客户端使用同样的AIDL文件,会生成同样的类,而生成类IBookManager.Stub是继承至Binder,且实现了IBookManager接口。
- 服务端的作用就是实现IBookManager.Stub接口,实现业务需求。
- 客户端通过拿到Binder对象,然后转成IBookManager类型,和服务端进行通信。
使用回调
在前面我们已经说了AIDL的简单使用了,现在我们加个功能,就是在AIDL中使用回调。
这个功能在开发车机、电视等系统中经常用,比如导航APP在运行时,一般需要在Launcher显示出导航的简短信息,这个导航实时信息就是导航APP通过回调给的。
在本章例子中,我们添加一个回调监听,希望服务端每隔一段时间能主动回调当前Book的信息。首先我们定义接口,这个接口也必须定义为aidl类型:
// IOnBookListener.aidl
package com.zyh.ipc;
import com.zyh.ipc.Book;
//返回所有Book
interface IOnBookListener {
void onAllBook(in List<Book> books);
}
这里为什么要定义为AIDL接口,因为接口中的方法是需要跨进程调用的,方法回调的数据是需要序列化和反序列化的,所以一般的Java接口肯定满足不了;
定义完新接口后,需要在原来接口中添加注册和反注册的方法:
package com.zyh.ipc;
//必须手动导入
import com.zyh.ipc.Book;
import com.zyh.ipc.IOnBookListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
//添加监听
void registerListener(IOnBookListener listener);
//移除监听
void unregisterListener(IOnBookListener listener);
}
修改完AIDL接口,需要Build一下项目,然后在服务端进行简单修改:
class BookManagerService : Service() {
private val mBookList = CopyOnWriteArrayList<Book>()
//listener列表
private val mListenerList = CopyOnWriteArrayList<IOnBookListener>()
private val mBinder = object : IBookManager.Stub(){
override fun getBookList(): MutableList<Book> {
return mBookList
}
override fun addBook(book: Book?) {
mBookList.add(book!!)
}
override fun registerListener(listener: IOnBookListener?) {
if (mListenerList.contains(listener)){
Log.i("BMS", "registerListener: already exist")
}else{
mListenerList.add(listener)
}
}
override fun unregisterListener(listener: IOnBookListener?) {
if (mListenerList.contains(listener)){
mListenerList.remove(listener)
}else{
Log.i("BMS", "unregisterListener: not exist")
}
}
}
override fun onBind(intent: Intent): IBinder {
return mBinder
}
override fun onCreate() {
super.onCreate()
mBookList.add(Book(11,"Android"))
mBookList.add(Book(22,"Ios"))
//开启线程循环回调book信息
Thread{
while (true){
Thread.sleep(5000)
for (listener in mListenerList){
Log.i("BMS", "onCreate: 回调信息")
listener.onAllBook(mBookList)
}
}
}.start()
}
}
上面代码非常简单,和保存Book一样也新建一个listener列表,然后在onCreate方法中,每隔5s回调一次Book信息,同时对Binder实现类添加了注册和反注册的功能。
写完服务端代码,必须把修改涉及的文件拷贝到客户端,这一步千万不能出错,在客户端中一样先Build,然后在服务连接成功后,创建监听的实例,给注册到服务端:
private val conn = object : ServiceConnection{
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i("ipc", "onServiceConnected: ")
bookManager = IBookManager.Stub.asInterface(service)
//这里注意必须是创建IOnBookListener.Stub的实例
bookManager?.registerListener(object : IOnBookListener.Stub(){
override fun onAllBook(books: MutableList<Book>?) {
for (book in books!!) {
Log.i("ipc", "onAllBook: ${book.BookId} ${book.BookName}")
}
}
})
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.i("ipc", "onServiceDisconnected: ")
}
}
这里要注意的上面已经注释了,就是在跨进程中,我们定义监听的接口必须是aidl类型的,而非普通的Java类型,所以客户端在注册时必须是传递生成类Stub的对象,至于原因后面会细说;这里创建了IOnBookListener.Stub的实例,但是通过跨进程后,客户端实例和服务端实例将不是同一个,上面代码的运行如下:
可以发现可以正常实现功能,每5S回调一次服务的Book列表信息。
既然我们可以注册listener,当然也需要测试一下反注册listener,但是在反注册listener中,我们会发现如下打印:
服务端居然提醒找不到该listener,并且客户端还是一直在回调,这不是大坑吗 这就是AIDL使用回调时需要注意的点。
我们在客户端新建listener对象,通过Binder传递到服务端后,会生成一个全新的对象,这是因为IPC的本质是序列化和反序列化,对象是需要序列化后才可以传输,所以这个listener在客户端和服务端俩个进程就不是一个对象。
既然不是同一个对象,那回调功能咋是正常的呢,这个后面分析AIDL生成文件时细说,这里先解决如何正常反注册。
解决的办法非常简单,使用RemoteCallbackList来代替前面的CopyOnWriteArrayList集和,从名字就可以看出这个是专门用来存放远程回调的,我们可以简单看一下该类的定义:
public class RemoteCallbackList<E extends IInterface>
可以发现它是一个泛型类,而泛型类型是IInterface,这个接口很重要,定义如下:
//Binder接口的基类,当我们定义一个跨进程的接口时,必须继承这个接口
public interface IInterface
{
//由于跨进程的真实原理是通过Binder,所以通过这个把接口和Binder对象联系起来。
//我们把接口转成Binder要用这个,而不是直接强转。
public IBinder asBinder();
}
RemoteCallbackList原理非常简单,在它的内部有一个Map结构专门用来保存所有AIDL回调,这个Map的key是IBinder类型,value是Callback类型:
ArrayMap<IBinder, Callback> mCallbacks
= new ArrayMap<IBinder, Callback>();
而当我们注册一个回调时,其中的key和value是如下方法获取:
IBinder binder = callback.asBinder();
Callback cb = new Callback(callback, cookie);
unregister(callback);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
到这里我们就明白了,虽然说跨进程传输中,在客户端新建的对象和服务端不是同一个,但是这俩个对象对应的Binder对象是同一个,所以当注册和反注册时可以针对对应的Binder来进行增删。
同时RemoteCallbackList还有一个很有用的功能,就是当客户端进程终止后,它能自动移除客户端注册的listener,而且RemoteCallbackList内部自动实现了线程同步功能,所以我们使用它来注册和反注册时,不需要做额外的线程同步工作。
重连机制
为了程序的健壮性,当服务端进程意外停止时,我们需要重新连接服务。在这里重新连接服务,有俩种方法:
- 可以给在服务端运行的Binder设置DeathRecipient监听回调,当Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务。
代码如下:
private val deathRecipient = object : IBinder.DeathRecipient {
override fun binderDied() {
Log.i("ipc", "binderDied: 调用 需用重连Service")
}
}
定义完deathRecipient后,在bindService成功的回调中给binder添加该死亡监听即可:
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.i("ipc", "onServiceConnected: ")
bookManager = IBookManager.Stub.asInterface(service)
//注册Binder死亡回调
service?.linkToDeath(deathRecipient, 0)
bookManager?.registerListener(listener)
}
- 通过bindService的回调,即ServiceConnection,可以在其中的onServiceDisconnected方法中进行重连机制,代码如下:
override fun onServiceDisconnected(name: ComponentName?) {
Log.i("ipc", "onServiceDisconnected: ")
//需要进行重连
}
到这里,AIDL的使用我们基本就说完了。其实AIDL文件是一套java代码生成规则,可以看成是给编译器来使用的一套规则,通过AIDL文件,编译器生成符合跨进程通信的java代码。 这样的好处就是不用让开发者编写复杂的跨进程代码。
AIDL生成文件分析
会使用AIDL还是不够的,必须要能看得懂AIDL文件所生成的java文件,因为在很多源码中,都是直接使用java代码来编码的。
前面说了当IPC时我们需要定义.aidl接口,这个接口就是IPC的内容,当定义了一个IBookManager.aidl接口,这个接口肯定不能直接起作用,它会生成一个IBookManager.java,代码如下:
package com.zyh.ipc;
//IBookManager是一个接口,继承至IInterface
public interface IBookManager extends android.os.IInterface
{
//对IBookManager接口的默认实现
public static class Default implements com.zyh.ipc.IBookManager
{
@Override public java.util.List<com.zyh.ipc.Book> getBookList() throws android.os.RemoteException
{
return null;
}
@Override public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
//IPC通信实现的存根类,这里实现了IBookManager接口,同时继承了Binder
public static abstract class Stub extends android.os.Binder implements com.zyh.ipc.IBookManager
{
//唯一描述符
private static final java.lang.String DESCRIPTOR = "com.zyh.ipc.IBookManager";
//构造函数,关联接口
public Stub()
{
//Binder的方法,将特定的接口与Binder关联。当调用完该方法后,queryLocalInterface方法将会根据唯一描述符返回特定的接口
this.attachInterface(this, DESCRIPTOR);
}
//把一个IBinder对象转换成IBookManager对象,如果是跨进程通信则需要生成一个代理类
public static com.zyh.ipc.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
//通过唯一描述符,找到跨进程的接口对象
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//如果跨进程的类型是IBookManager,说明是没有跨进程的
if (((iin!=null)&&(iin instanceof com.zyh.ipc.IBookManager))) {
return ((com.zyh.ipc.IBookManager)iin);
}
//否则返回Stub.Proxy类型对象
return new com.zyh.ipc.IBookManager.Stub.Proxy(obj);
}
//这个是IInterface接口的方法,即需要把该接口强转为Binder对象
@Override public android.os.IBinder asBinder()
{
return this;
}
//交换的核心代码,返回值返回true则表示调用成功。
//code表示唯一标识符,即该调用哪个方法
//data表示客户端发送给服务端的数据
//reply表示服务端返回的数据
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
//开始交换,返回值写入唯一表示符
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
//获取book列表
case TRANSACTION_getBookList:
{
//验证当前接口是否匹配
data.enforceInterface(descriptor);
//这个this表示IBookManager接口的方法
java.util.List<com.zyh.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
//验证当前接口是否匹配
data.enforceInterface(descriptor);
com.zyh.ipc.Book _arg0;
if ((0!=data.readInt())) {
//data是Parcel数据,这里根据CREATOR创建出Book对象
_arg0 = com.zyh.ipc.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
//同样是调用IBookManager接口的方法
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
//IBookManager接口的代理类,当真进行IPC通信时,调用该类
private static class Proxy implements com.zyh.ipc.IBookManager
{
private android.os.IBinder mRemote;
//远程的IBinder对象
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
//IInterface接口方法
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List<com.zyh.ipc.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.zyh.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//调用刚刚定义的transact方法,会回调Stub类中的onTransact方法
boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
//当调用不成功且默认实现不为空时,即是非IPC通信
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getBookList();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.zyh.ipc.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.zyh.ipc.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);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addBook(book);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.zyh.ipc.IBookManager sDefaultImpl;
}
//静态变量,每个方法对应一个静态变量
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
//设置默认的实现
public static boolean setDefaultImpl(com.zyh.ipc.IBookManager impl) {
//当客户端和服务端在同一个进程中会被调用
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.zyh.ipc.IBookManager getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
//java文件的接口
public java.util.List<com.zyh.ipc.Book> getBookList() throws android.os.RemoteException;
//java文件的接口
public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException;
}
上面生成的代码还是蛮多的,看起来有点乱的原因是定义了太多内部类,首先就是IBookManager.java这个类,它继承了IInterface接口,同时它自己也是一个接口,所有可以在Binder中传输的接口都必须继承至IInterface接口。
同时在IBookManager.java中定义了俩个方法,这俩个方法就是我们在aidl中定义的方法,是IPC希望实现的功能接口。同时定义了俩个静态整形的id分别用来标识这俩个方法,这个俩个id用于标识在transact过程中客户端所请求的到底是哪个方法。
然后是IBookManager.java中的第一个内部类:Default,该类是默认实现,可以无需关注。
IBookManager.java中第二个内部类:Stub,其中Stub的意思是"存根",表示一些接口的实现类。该类是核心,它首先是继承至Binder,同时实现了IBookManager接口,下面详细列举一下该类中的实现细节:
- DESCRIPTOR,这个是Binder的唯一表示,一般用当前Binder的类名表示。
- asInterface,用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换是区分进程的,如果客户端和服务端在同一个进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是封装后的Stub.proxy对象。
- asBinder,用于返回当前Binder对象。
- onTransact,该方法会运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由该方法来处理。
该方法的参数非常重要,服务端通过code可以确定客户端所请求的目标方法是什么,然后从data中取出目标方法所需要的参数,然后执行目标方法,当目标方法执行完毕后,会向reply中写入返回值。这里注意,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们不希望随便一个进程都能远程调用服务。
- Proxy类,该类是Stub的内部类,当客户端和服务端不在一个线程时,asInterface就会创建Proxy类的对象,然后构造函数传入的IBinder对象还是服务端的Binder对象。
- Proxy#getBookList,这个方法运行在客户端中,当客户端调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List,然后把该方法的参数信息写入_data中,接着调用transact方法来发起RPC请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,知道RPC过程返回后,当前线程继续执行,并且从_reply中取出RPC过程返回的结果。
上面便是AIDL的生成文件的简单分析。
手写一个Binder使用实例
在前面我们也说了.aidl文件只是生成Java代码的规则,而且这里我们也分析了AIDL生成的文件,所以我们就可以不用AIDL来手动创建Binder对象,这样一样可以实现IPC通信。
- 先声明一个跨进程的接口,该接口只需要继承IInterface接口即可,IInterface有一个asBinder方法,这个接口定义如下:
public interface ICustomBookManager extends IInterface {
static final java.lang.String DESCRIPTOR = "com.zyh.ipc.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);
public java.util.List<com.zyh.ipc.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.zyh.ipc.Book book) throws android.os.RemoteException;
}
这里定义了ICustomBookManager的Java接口,并且定义了俩个方法以及方法的唯一表示符号。
- 然后是新建实现该接口的Java类,同时继承至Binder:
//继承至Binder,同时实现ICustomBookManager接口
public class CustomBookManagerImpl extends Binder implements ICustomBookManager {
public CustomBookManagerImpl() {
this.attachInterface(this, DESCRIPTOR);
}
//必须的方法,把IBinder对象转成接口类型
public static ICustomBookManager asInterface(IBinder obj){
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.zyh.ipc.CustomBookManagerImpl))) {
return ((com.zyh.ipc.CustomBookManagerImpl)iin);
}
return new com.zyh.ipc.Proxy(obj);
}
//模板代码,在服务端被调用
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(descriptor);
java.util.List<com.zyh.ipc.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(descriptor);
com.zyh.ipc.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.zyh.ipc.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
@Override
public List<Book> getBookList() throws RemoteException {
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
}
//返回Binder
@Override
public IBinder asBinder() {
return this;
}
}
- 在上面代码我们知道当是真的IPC时,是需要一个代理类的,由代理类来进行数据转换和接口请求:
public class Proxy implements ICustomBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override
public List<Book> getBookList() throws RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.zyh.ipc.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.zyh.ipc.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(Book book) throws 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);
}
boolean _status = mRemote.transact(IBookManager.Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
可以发现上面代码和生成的代码几乎一样,只不过可读性多了很多,里面关键的地方可以归纳如下几点:
- 跨进程的接口必须要继承IInterface接口。
- 接口的Java实现类必须继承至Binder。
- asInterface必不可少,可以把Binder对象转成接口类型,这里由于要区分是否是跨进程,如果是跨进程需要调用Proxy代理类来辅助。
- onTransact是调用transact后回调,是服务端处理请求,在线程池中执行。
- Proxy代理类就是用来调用接口,以及方法相关参数的序列化与反序列化过程。
总结
不知不觉本篇文章的子树已经非常多了,内容较多,下面做个总结:
- 在AIDL使用篇,主要就是AIDL文件的写法要尤其注意,以及使用回调和重连机制的使用。
- 在AIDL生成文件分析中,我们要明确看出其不同内部类的作用。
- 在手写Binder通信时,我们需要知道IInterface的含义,以及实现接口和继承至Binder的Java类。
- 其中要会根据是否跨进程来选择是否使用代理类,其中onTransact是在服务端线程池中调用,而Proxy中的方法是同步执行,会挂起。
笔者水平有限,如有问题,欢迎大家评论指正。