安卓开发如何实现跨进程
本节主要介绍IPC中的一些基础概念,主要包含接口、Parcelable接口、AIDLMessenger以及Binder,只有熟悉这三方面的内容后,我们才能更好地理解 跨进程通信的各种方式。和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,这
IPC机制
参考资料: 《Android开发艺术探索》 任玉刚
IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。
既然是跨进程通信那肯定要讨论多进程了,多进程的情况分为两种。第一种情况是一个应用因为某些原因自身需要采用多进程模式来实现,至于原因,可能有很多,比如有些模块由于特殊原因需 要运行在单独的进程中,又或者为了加大一个应用可使用的内存所以需要通过多进程来获取多份内存空间。Android对单个应用所使用的最大内存做了限制,早 期的一些版本可能是16MB,不同设备有不同的大小。另一种情况是当前应用需要向其他应用获取数据,由于是两个应用,所以必须采用跨进程的方式来获取所 需的数据,甚至我们通过系统提供的ContentProvider去查询数据的时候,其实也是一种进程间通信,只不过通信细节被系统内部屏蔽了,我们无法感知而已。后 续章节会详细介绍ContentProvider的底层实现
Android中的多进程模式
在正式介绍进程间通信之前,我们必须先要理解Android中的多进程模式。通过给四大组件指定android:process属性,我们可以轻易地开启多进程模式,这看 起来很简单,但是实际使用过程中却暗藏杀机,多进程远远没有我们想的那么简单,有时候我们通过多进程得到的好处甚至都不足以弥补使用多进程所带来的 代码层面的负面影响。下面会详细分析这些问题。
开启多进程模式
正常情况下,在Android中多进程是指一个应用中存在多个进程的情况,因此这里不讨论两个应用之间的多进程情况。首先,在Android中使用多进程只有一 种方法,那就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidMenifest中指定android:process属性,除此之外没有其他办法,也就是说我们无 法给一个线程或者一个实体类指定其运行时所在的进程。其实还有另一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程,但是这种方法属 于特殊情况,也不是常用的创建多进程的方式,因此我们暂时不考虑这种方式。下面是一个示例,描述了如何在Android中创建多进程:
多进程模式的运行机制
在多进程模式中,不同进程的组件的确会拥有独立的虚拟机、Application以及内存空间,这会给实际的开发带来很 多困扰,是尤其需要注意的。或者我们也可以这么理解同一个应用间的多进程:它就相当于两个不同的应用采用了SharedUID的模式,这样能够更加直接地理解 多进程模式的本质。
IPC基础概念介绍
本节主要介绍IPC中的一些基础概念,主要包含Serializable接口、Parcelable接口、AIDL、Messenger以及Binder,只有熟悉这三方面的内容后,我们才能更好地理解 跨进程通信的各种方式。Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。 还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,这个时候也需要使用Serializable来完成对象的持久化
Serializable
使用Serializable来实现序列化相当简单,只需要让类实现Serializable这个接口即可。通过Serializable方式来实现对象的序列化,实现起来非常简单,几乎所有工作都被系统自动完成了。如何进行对象的序列化和反序列化也非常简单,只需要 采用ObjectOutputStream和ObjectInputStream即可轻松实现。下面举个简单的例子
//序列化过程
User user = new User(0,"jake",true);
ObjectOutputStream out = new ObjectOutputStream(
new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
//反序列化过程
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();
除此之外,为了反序列化的安全,我们应该手动指定一个serialVersionUID。serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。serialVersionUID的详细工作机制是这样的:序列化的时候系统会把当前类的 serialVersionUID写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一 致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成 员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的,
因此我们应该手动指定serialVersionUID的值,比如1L或者任意值,也可以让编译器根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时两者的 serialVersionUID是相同的,因此可以正常进行反序列化。如果不手动指定serialVersionUID的值,反序列化时当前类稍有所改变就导致反序列化失败,程序就会出现crash。
Parcelable
上一节我们介绍了通过Serializable方式来实现序列化的方法,本节接着介绍另一种序列化方式:Parcelable。Parcelable也是一个接口,只要实现这个接口,一 个类的对象就可以实现序列化并可以通过Intent和Binder传递。下面的示例是一个典型的用法。
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
public Book book;
public User(int userId,String userName,boolean isMale) {
this.userId = userId;
this.userName = userName;
this.isMale = isMale;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out,int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale ? 1 : 0);
out.writeParcelable(book,0);
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.
Creator<User>() {
public User createFromParcel(Parcel in) {
return new User(in);
}
public User[] newArray(int size) {
return new User[size];
}
};
private User(Parcel in) {
userId = in.readInt();
userName = in.readString();
isMale = in.readInt() == 1;
book = in.readParcelable(Thread.currentThread().getContextClass
Loader());
}
}
从上述代码中可以看出,在序列化过程中需要实现的功能有序列化、反 序列化和内容描述。序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的;反序列化功能由CREATOR来完成,其内部标明了 如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程;内容描述功能由describeContents方法来完成,几乎在所有情况下这个方法都应 该返回0,仅当当前对象中存在文件描述符时,此方法返回1。需要注意的是,在User(Parcel in)方法中,由于book是另一个可序列化对象,所以它的反序列化过程 需要传递当前线程的上下文类加载器,否则会报无法找到类的错误。详细的方法说明参看下表
Parcelable方法如下表
| 方法 | 功能 |
|---|---|
createFromParcel(Parcel in) |
从序列化对象后的对象中创建原始对象 |
newArray(int size) |
创建指定长度的原始对象数组 |
Book(Parcel in) |
从序列化后的对象中创建原始对象 |
writeToParcel(Parcel out, int flags) |
将当前对象写入序列化结构中,其中flags标识有两种:0或1, 为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎都为1 |
describeContents |
返回当前对象的内容描述,如果含有文件描述符,返回1,否则返回0 几乎所有情况都返回0 |
这里解释一下,读者可能觉得构造Book(Parcel in)和createFromParcel(Parcel in)在功能上重复了,但是createFromParcel方法接收的是一个包含序列化数据的Parcel对象,然后它的工作是调用相应的构造函数来实际创建对象实例,并将Parcel传递进去,它本身通常不直接进行数据读取和赋值。也就是说,createFromParcel负责创建实例,构造负责初始化实例。
AIDL
AIDL,即Android Interface Definition Language,安卓接口定义语言,我们可以简单理解为一套语法,通过该语法系统会为我们生成对应的Java接口代码(在 build/generated/aidl 目录下),使得不同进程中的客户端和服务端能够进行通信,它支持跨进程的方法调用。后面会距离它的使用。
可以把 AIDL 理解为 “客户端和服务端的通信协议”,双方都需要遵守这个协议才能正常交互,服务端 “实现” AIDL 接口,客户端 “调用” AIDL 接口,二者缺一不可,AIDL 是连接双方的桥梁。
Messenger
Messenger翻译过来是信使的意思,它提供了一种基于消息的进程间通信方式。它实际上是对AIDL进行了封装,开发者无需直接编写AIDL文件。
Binder
Binder太复杂了。本节的侧重点是介绍Binder的使用以及上层原理,为接 下来的几节内容做铺垫。
直观来说,Binder是Android中的一个类,它继承了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式、;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、 WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个 包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,所以较为简单,无法触及Binder的核心,而 Messenger的底层其实是AIDL,所以这里选择用AIDL来分析Binder的工作机制。为了分析Binder的工作机制,我们需要新建一个AIDL示例,SDK会自动为我们生产 AIDL所对应的Binder类,然后我们就可以分析Binder的工作过程。
//Book.java
package com.ryg.chapter_2.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId,String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out,int flags) {
out.writeInt(bookId);
out.writeString(bookName);
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.
Creator<Book>() {
public Book createFromParcel(Parcel in) {
return new Book(in);
}
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in) {
bookId = in.readInt();
bookName = in.readString();
}
}
//Book.aidl
package com.ryg.chapter_2.aidl;
parcelable Book;
// IBookManager.aidl
package com.ryg.chapter_2.aidl;
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
上面三个文件中,Book.java是一个表示图书信息的类,它实现了Parcelable接口。Book.aidl是Book类在AIDL中的声明。IBookManager.aidl是我们定义的一个接 口,里面有两个方法:getBookList和addBook,其中getBookList用于从远程服务端获取图书列表,而addBook用于往图书列表中添加一本书,当然这两个方法主要 是示例用,不一定要有实际意义。我们可以看到,尽管Book类已经和IBookManager位于相同的包中,但是在IBookManager中仍然要导入Book类,这就是AIDL的 特殊之处。
下面是系统自动生成的Binder接口文件,位置在generated目录下。
//IBookManager.java
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\\workspace\\Chapter_2\\src\\com\\ryg\\chapter_2\\
aidl\\IBookManager.aidl
*/
package com.ryg.chapter_2.aidl;
public interface IBookManager extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.ryg.chapter_2.aidl.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.ryg.chapter_2.aidl.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this,DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.ryg.chapter_2.aidl.IBookManager
* interface,generating a proxy if needed.
*/
public static com.ryg.chapter_2.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.ryg.chapter_2.aidl.
IBookManager))) {
return ((com.ryg.chapter_2.aidl.IBookManager) iin);
}
return new com.ryg.chapter_2.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.Remote
Exception {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.ryg.chapter_2.aidl.Book> _result = this.
getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.ryg.chapter_2.aidl.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.ryg.chapter_2.aidl.Book.CREATOR.create
FromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code,data,reply,flags);
}
private static class Proxy implements com.ryg.chapter_2.aidl.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public java.util.List<com.ryg.chapter_2.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.ryg.chapter_2.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList,_data,
_reply,0);
_reply.readException();
_result = _reply
.createTypedArrayList(com.ryg.chapter_2.aidl.
Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.ryg.chapter_2.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();
}
}
}
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.ryg.chapter_2.aidl.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.ryg.chapter_2.aidl.Book book) throws android.os.RemoteException;
}
可以看到根据IBookManager.aidl系统为我们生成了IBookManager.java 这个类,它继承了IInterface这个接口,同时它自己也还是个接口,**所有可以在Binder中传输的接口都需要继承IInterface接口。**这个类刚开始看起来逻辑混乱,但是 实际上还是很清晰的,通过它我们可以清楚地了解到Binder的工作机制。这个类的结构其实很简单,首先,它声明了两个方法getBookList和addBook,显然这就是 我们在IBookManager.aidl中所声明的方法,同时它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个 方法。接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者 位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。这么来看,IBookManager这个接口的确很简单,但是我们也应该认识 到,这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy,下面详细介绍针对这两个类的每个方法的含义。
| 类/角色 | 所在位置 | 主要职责 |
|---|---|---|
| IBookManager | public interface | 对外暴露的能力接口,继承 IInterface,声明了两个方法:List<Book> getBook()、void addBook(Book book)。 |
| Default | public static class | IBookManager 的“空实现”,用于占位或默认返回(如 getBook 返回 null,addBook 无操作)。实际业务通常不使用它,而是实现 Stub。 |
| Stub | public static abstract class | AIDL 的服务端桩类,继承 Binder 并实现 IBookManager。提供 asInterface(IBinder) 将远端 IBinder 转为接口类型;内部定义事务码 TRANSACTION_getBook / TRANSACTION_addBook;核心方法 onTransact 负责解包参数、分派方法、写回结果。 |
| Proxy | Stub 的静态内部类 | 客户端的代理类,持有远端 IBinder mRemote。客户端调用 getBook/addBook 时,Proxy 把参数写入 Parcel,调用 transact 发送到服务端,再从返回 Parcel 读取结果,完成“像本地调用一样”的远程调用。 |
| _Parcel | Stub 的静态内部类 | 辅助方法,用于在 Parcel 与 Parcelable 之间读写集合与对象(如 readTypedList/writeTypedList/writeTypedObject),简化集合与自定义 Parcelable 的编解码。 |
| asInterface | 将服务端的**IBinder转换为客户端所需的AIDL接口类型对象,使调用者可以像调用本地接口一样发起方法调用。该转换是“按进程区分”的:同一进程直接返回本地实现(Stub),跨进程则返回代理对象(Proxy),从而屏蔽底层Binder**通信细节 |
接下来,我们介绍Binder的两个很重要的方法linkToDeath和unlinkToDeath。我们知道,Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这 个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。更为关键的是,如果我们不知道Binder连接已经断裂,那么客户端的 功能就会受到影响。为了解决这个问题,Binder中提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当 Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。
首先,声明一个DeathRecipient对象。DeathRecipient是一个接口,其内部只有一个方法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回 调binderDied方法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.Death
Recipient() {
@Override
public void binderDied() {
if (mBookManager == null)
return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:这里重新绑定远程Service
}
};
接着,在客户端绑定远程服务成功后,给binder设置死亡代理:
mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
其中linkToDeath的第二个参数是个标记位,我们直接设为0即可。经过上面两个步骤,就给我们的Binder设置了死亡代理,当Binder死亡的时候我们就可以收到通知了。另外,通过Binder的方法isBinderAlive也可以判断Binder是否死亡。 到这里,IPC的基础知识就介绍完毕了,下面开始进入正题,直面形形色色的进程间通信方式。
Android中的IPC方式
在上节中,我们介绍了IPC的几个基础知识:序列化和Binder,本节开始详细分析各种跨进程通信方式。具体方式有很多,比如可以通过在Intent中附加extras 来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,ContentProvider天生就是支持跨进程访问的,因此我们也可以采 用它来进行IPC。此外,通过网络通信也是可以实现数据传递的,所以Socket也可以实现IPC。上述所说的各种方法都能实现IPC,它们在使用方法和侧重点上都有很大的区别,下面会一 一进行展开。
使用Bundle
我们知道,四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地 在不同的进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity、Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的 信息并通过Intent发送出去。当然,我们传输的数据必须能够被序列化,比如基本类型、实现了Parcellable接口的对象、实现了Serializable接口的对象以及一些 Android支持的特殊对象,具体内容可以看Bundle这个类,就可以看到所有它支持的类型。Bundle不支持的类型我们无法通过它在进程间传递数据。
这是一种最简单的进程间通信方式, 除了直接传递数据这种典型的使用场景,它还有一种特殊的使用场景。比如A进程正在进行一个计算,计算完成后它要启动B进程的一个组件并把计算结果 传递给B进程,可是遗憾的是这个计算结果不支持放入Bundle中,因此无法通过Intent来传输,这个时候如果我们用其他IPC方式就会略显复杂。可以考虑如下方式:我们通过Intent启动进程B的一个Service组件(比如IntentService),让Service在后台进行计算,计算完毕后再启动B进程中真正要启动的目标组件,由于Service 也运行在B进程中,所以目标组件就可以直接获取计算结果,这样一来就轻松解决了跨进程的问题。这种方式的核心思想在于将原本需要在A进程的计算任务转移到B进程的后台Service中去执行,这样就成功地避免了进程间通信问题,而且只用了很小的代价。
使用文件共享
共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。 我们知道,在Windows上,一个文件如果被加了排斥锁将会导致其他线程无法对其进行访问,包括读和写,而由于Android系统基于Linux,使得其并发读/写文件 可以没有限制地进行,甚至两个线程同时对同一个文件进行写操作都是允许的,尽管这可能出问题。通过文件交换数据很好使用,除了可以交换一些文本信息 外,我们还可以序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象。通过文件共享这种方式来共享数据对文件格式是没有具体要求的,比如可以是文本文件,也可以是XML文件,只要读/写双方约定数据格式即可。
通过文件共享的方式也是有局限性的,比如并发读/写的问题,像上面的那个例子,如果并发读/写,那么我们读出的内容就有可能不是最新的,如果是并发写的话那就更严重了。因此我们要尽量避免并发写这种情况的发生或者考虑使用线程同步来限制多个线程的写操作。通过上面的分析,我们可以知道,文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。
但是要特别注意,SharedPreferences是个特例,众所周知,SharedPreferences是Android中提供的轻量级存储方案,它通过键值对的方式来存储数据,在底层实现上它采用 XML文件来存储键值对,每个应用的SharedPreferences文件都可以在当前包所在的data目录下查看到。一般来说,它的目录位于/data/data/package name/shared_prefs 目录下,其中package name表示的是当前应用的包名。从本质上来说,SharedPreferences也属于文件的一种,但是由于系统对它的读/写有一定的缓存策略,即在内 存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问,Sharedpreferences有很大几率会丢 失数据,因此,不建议在进程间通信中使用SharedPreferences。
使用Messenger
Messenger的使用也比较简单,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间 传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,为什么这么说呢,我们大致看一下Messenger这个类的构造方法就明白了。下面是Messenger 的两个构造方法,从构造方法的实现上我们可以明显看出AIDL的痕迹,不管是IMessenger还是Stub.asInterface,这种使用方法都表明它的底层是AIDL。
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
它对AIDL做了封装,使得我们可以更简便地进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。实现一个Messenger有如下几个步骤,分为服务端和客户端。
服务端进程
首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回 这个Messenger对象底层的Binder即可。
客户端进程
客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通 过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。这听起来可能还是有点抽象,下面举个例子,让服务端和客户端都能通信
首先看服务端代码,先创建一个服务,然后
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.i(TAG,"receive msg from Client:" + msg.getData().getString
("msg"));
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null,MyConstants.MSG_
FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply","嗯,你的消息我已经收到,稍后会回复你。");
relpyMessage.setData(bundle);
try {
client.send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new MessengerHandler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
接着再看客户端
public class MessengerActivity extends Activity {
private static final String TAG = " MessengerActivity";
private Messenger mService;
private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Log.i(TAG,"receive msg from Service:" + msg.getData().
getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,IBinderservice) {
mService = new Messenger(service);
Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello,this is client.");
msg.setData(data);
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
使用AIDL
上一节我们介绍了使用Messenger来进行进程间通信的方法,可以发现,Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务 端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需 要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,因此 Messenger本质上也是AIDL,只不过系统为我们做了封装从而方便上层的调用而已。在上一节中,我们介绍了Binder的概念,大家对Binder也有了一定的了解,在 Binder的基础上我们可以更加容易地理解AIDL。这里先介绍使用AIDL来进行进程间通信的流程,分为服务端和客户端两个方面。
服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现 这个AIDL接口即可。
客户端
客户端所要做事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用 AIDL中的方法了。上面描述的只是一个感性的过程,AIDL的实现过程远不止这么简单,接下来会对其中的细节和难点进行详细介绍。
AIDL接口的创建
如下所示,我们创建了一个后缀为AIDL的文件,在里面声明了一个接口和两个接口方法
// IBookManager.aidl
package com.ryg.chapter_2.aidl;
import com.ryg.chapter_2.aidl.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
在AIDL文件中,并不是所有的数据类型都是可以使用的,那么到底AIDL文件支持哪些数据类型呢?如下所示。
- 基本数据类型(
int、long、char、boolean、double等); String和CharSequence;List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value;Parcelable:所有实现了Parcelable接口的对象;- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
以上6种数据类型就是AIDL所支持的所有类型,其中自定义的Parcelable对象和AIDL对象必须要显式import进来,不管它们是否和当前的AIDL文件位于同一个 包内。比如IBookManager.aidl这个文件,里面用到了Book这个类,这个类实现了Parcelable接口并且和IBookManager.aidl位于同一个包中,但是遵守AIDL的规范, 我们仍然需要显式地import进来
我们需要注意,AIDL中每个实现了Parcelable接口的类都需要按照上面那种方式去创建相应的AIDL文件并声明那个类为parcelable。除此之外,AIDL中除了基 本数据类型,其他类型的参数必须标上方向:in、out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出型参数,至于它们具体的区别,这个 就不说了。我们要根据实际需要去指定参数类型,不能一概使用out或者inout,因为这在底层实现是有开销的。最后,AIDL接口中只支持方法,不支持声明静态 常量,这一点区别于传统的接口。
服务端接口方法的实现
上面讲述了如何定义AIDL接口,接下来我们就需要实现这个接口了。我们先创建一个Service,称为BookManagerService,代码如下:
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArray
List<Book>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(2,"Ios"));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
上面是一个服务端Service的典型实现,首先在onCreate中初始化添加了两本图书的信息,然后创建了一个Binder对象并在onBind中返回它,这个对象继承自 IBookManager.Stub并实现了它内部的AIDL方法,这个过程在Binder那一节已经介绍过了,这里就不多说了。这里主要看getBookList和addBook这两个AIDL方法的 实现,实现过程也比较简单,注意这里采用了CopyOnWriteArrayList,这个CopyOnWriteArrayList支持并发读/写。在前面我们提到,AIDL方法是在服务端的Binder 线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,而我们这里直接使用 CopyOnWriteArrayList来进行自动的线程同步。
前面我们提到,AIDL中能够使用的List只有ArrayList,但是我们这里却使用了CopyOnWriteArrayList(注意它不是继承自ArrayList),为什么能够正常工作呢? 这是因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终 形成一个新的ArrayList传递给客户端。所以,我们在服务端采用CopyOnWriteArrayList是完全可以的。和此类似的还有ConcurrentHashMap,读者可以体会一下这种 转换情形。然后我们需要在XML中注册这个Service,如下所示。注意BookManagerService是运行在独立进程中的,它和客户端的Activity不在同一个进程中,这样 就构成了进程间通信的场景。
客户端的实现
客户端的实现就比较简单了,首先要绑定远程服务,绑定成功后将服务端返回的Binder对象转换成AIDL接口,然后就可以通过这个接口去调用服务端的远程方法了,代码如下所示
public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> list = bookManager.getBookList();
Log.i(TAG,"query book list:" + list.toString());
Book newBook = new Book(3,"一本新书");
bookManager.addBook(newBook);
Log.i(TAG,"add book:" + newBook);
List<Book> newList = bookManager.getBookList();
Log.i(TAG,"query book list:" + newList.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(this,BookManagerService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
使用ContentProvider
ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信。和Messenger一样,ContentProvider的 底层实现同样也是Binder,由此可见,Binder在Android系统中是何等的重要。虽然ContentProvider的底层实现是Binder,但是它的使用过程要比AIDL简单许多,这 是因为系统已经为我们做了封装,使得我们无须关心底层细节即可轻松实现IPC。ContentProvider虽然使用起来很简单,包括自己创建一个ContentProvider也不是 什么难事,尽管如此,它的细节还是相当多,比如CRUD操作、防止SQL注入和权限控制等。由于章节主题限制,在本节中,笔者暂时不对ContentProvider的使用 细节以及工作机制进行详细分析,而是为读者介绍采用ContentProvider进行跨进程通信的主要流程,至于使用细节和内部工作机制会在后续章节进行详细分析。
系统预置了许多ContentProvider,比如通讯录信息、日程表信息等,要跨进程访问这些信息,只需要通过ContentResolver的query、update、insert和delete方法即 可。在本节中,我们来实现一个自定义的ContentProvider,并演示如何在其他应用中获取ContentProvider中的数据从而实现进程间通信这一目的。首先,我们创建 一个ContentProvider,名字就叫BookProvider。创建一个自定义的ContentProvider很简单,只需要继承ContentProvider类并实现六个抽象方法即可:onCreate、query、 update、insert、delete和getType。这六个抽象方法都很好理解,onCreate代表ContentProvider的创建,一般来说我们需要做一些初始化工作;getType用来返回一个Uri 请求所对应的MIME类型(媒体类型),比如图片、视频等,这个媒体类型还是有点复杂的,如果我们的应用不关注这个选项,可以直接在这个方法中返回null或 者“/”;剩下的四个方法对应于CRUD操作,即实现对数据表的增删改查功能。根据Binder的工作原理,我们知道这六个方法均运行在ContentProvider的进程中, 除了onCreate由系统回调并运行在主线程里,其他五个方法均由外界回调并运行在Binder线程池中,这一点在接下来的例子中可以再次证明。
ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,对于每个表格来说,它们都具有行和列的层次性,行往往对应一条记录,而列对应一 条记录中的一个字段,这点和数据库很类似。除了表格的形式,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表格数据的结构不同,因此处 理这类数据时可以在ContentProvider中返回文件的句柄给外界从而让文件来访问ContentProvider中的文件信息。Android系统所提供的MediaStore功能就是文件类型 的ContentProvider,详细实现可以参考MediaStore。
下面看一个最简单的示例,它演示了ContentProvider的工作工程。首先创建一个BookProvider类,它继承自ContentProvider并实现了ContentProvider的六个必须 需要实现的抽象方法。在下面的代码中,我们什么都没干,尽管如此,这个BookProvider也是可以工作的,只是它无法向外界提供有效的数据而已。
// ContentProvider.java
public class BookProvider extends ContentProvider {
private static final String TAG = "BookProvider";
@Override
public boolean onCreate() {
Log.d(TAG,"onCreate,current thread:" + Thread.currentThread().
getName());
return false;
}
@Override
public Cursor query(Uri uri,String[] projection,String selection,
String[] selectionArgs,String sortOrder) {
Log.d(TAG,"query,current thread:" + Thread.currentThread().
getName());
return null;
}
@Override
public String getType(Uri uri) {
Log.d(TAG,"getType");
return null;
}
@Override
public Uri insert(Uri uri,ContentValues values) {
Log.d(TAG,"insert");
return null;
}
@Override
public int delete(Uri uri,String selection,String[] selectionArgs) {
Log.d(TAG,"delete");
return 0;
}
@Override
public int update(Uri uri,ContentValues values,String selection,
String[] selectionArgs) {
Log.d(TAG,"update");
return 0;
}
}
接着我们需要注册这个BookProvider,如下所示。其中android:authorities是Content-Provider的唯一标识,通过这个属性外部应用就可以访问我们的 BookProvider,因此,android:authorities必须是唯一的,这里建议读者在命名的时候加上包名前缀。为了演示进程间通信,我们让BookProvider运行在独立的进程中 并给它添加了权限,这样外界应用如果想访问BookProvider,就必须声明“com.ryg.PROVIDER”这个权限。ContentProvider的权限还可以细分为读权限和写权限,分 别对应android:readPermission和android:writePermission属性,如果分别声明了读权限和写权限,那么外界应用也必须依次声明相应的权限才可以进行读/写操作,否 则外界应用会异常终止。关于权限这一块,请读者自行查阅相关资料,本章不进行详细介绍。
ContentProvider本身是Android四大组件之一,这里简单做个演示,就不详细展开了。
使用Socket
Socket也称为“套接字”,是网络通信中的概念。我们线上聊天通信都用到了socket,用于在两个不同进程间通信,很显然,这也是一种IPC方式。如果大家写过类似聊天室的demo就完成了IPC通信。
Binder连接池
上面简单讲了IPC,但如果现在有10个不同的业务模块都需要使用 AIDL来进行进程间通信,那我们该怎么处理呢?也许你会说:“就按照AIDL的实现方式一个个来吧”,这是可以的,如果用这种方法,首先我们需要创建10个 Service,这好像有点多啊!如果有100个地方需要用到AIDL呢,先创建100个Service?到这里,读者应该明白问题所在了。随着AIDL数量的增加,我们不能无限 制地增加Service,Service是四大组件之一,本身就是一种系统资源。而且太多的Service会使得我们的应用看起来很重量级,因为正在运行的Service可以在应用详 情页看到,当我们的应用详情显示有10个服务正在运行时,这看起来并不是什么好事。针对上述问题,我们需要减少Service的数量,将所有的AIDL放在同一个 Service中去管理。
在这种模式下,整个工作机制是这样的:每个业务模块创建自己的AIDL接口并实现此接口,这个时候不同业务模块之间是不能有耦合的,所有实现细节我们要单独开来,然后向服务端提供自己的唯一标识和其对应的Binder对象;对于服务端来说,只需要一个Service就可以了,服务端提供一个queryBinder接口,这个接口能够根据业务模块的特征来返回相应的Binder对象给它们,不同的业务模块拿到所需的Binder对象后就可以进行远程方法调用了。由此可见,Binder连接池 的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程。
下面对Binder连接池的代码实现做一下说明。首先,为了说明问题,我们提供了两个AIDL接口(ISecurityCenter 和ICompute)来模拟上面提到的多个业务模块都要使用AIDL的情况,其中ISecurityCenter接口提供加解密功能,声明如下:
//加密解密接口
interface ISecurityCenter {
String encrypt(String content);
String decrypt(String password);
}
//计算接口
interface ICompute {
int add(int a,int b);
}
接着对上面两个AIDL接口的实现,也比较简单,代码如下
public class SecurityCenterImpl extends ISecurityCenter.Stub {
private static final char SECRET_CODE = '^';
@Override
public String encrypt(String content) throws RemoteException {
char[] chars = content.toCharArray();
for (int i = 0; i < chars.length; i++) {
chars[i] ^= SECRET_CODE;
}
return new String(chars);
}
@Override
public String decrypt(String password) throws RemoteException {
return encrypt(password);
}
}
public class ComputeImpl extends ICompute.Stub {
@Override
public int add(int a,int b) throws RemoteException {
return a + b;
}
}
接下来就是服务端和Binder连接池的工作了。
首先,为Binder连接池创建AIDL接口IBinderPool.aidl,代码如下所示。
interface IBinderPool {
/**
* @param binderCode,the unique token of specific Binder<br/>
* @return specific Binder who's token is binderCode.
*/
IBinder queryBinder(int binderCode);
}
接着,为Binder连接池创建远程Service并实现IBinderPool,下面是queryBinder的具体实现,可以看到请求转发的实现方法,当Binder连接池连接上远程服务 时,会根据不同模块的标识即binderCode返回不同的Binder对象,通过这个Binder对象所执行的操作全部发生在远程服务端。
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
case BINDER_COMPUTE: {
binder = new ComputeImpl();
break;
}
default:
break;
}
return binder;
}
远程Service的实现就比较简单了,代码如下所示。
public class BinderPoolService extends Service {
private static final String TAG = "BinderPoolService";
private Binder mBinderPool = new BinderPool.BinderPoolImpl();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG,"onBind");
return mBinderPool;
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
下面还剩下Binder连接池的具体实现,在它的内部首先它要去绑定远程服务,绑定成功后,客户端就可以通过它的queryBinder方法去获取各自对应的Binder, 拿到所需的Binder以后,不同业务模块就可以进行各自的操作了,Binder连接池的代码如下所示。
public class BinderPool {
private static final String TAG = "BinderPool";
public static final int BINDER_NONE = -1;
public static final int BINDER_COMPUTE = 0;
public static final int BINDER_SECURITY_CENTER = 1;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BinderPool getInsance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent(mContext,BinderPoolService.class);
mContext.bindService(service,mBinderPoolConnection,
Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* query binder by binderCode from binder pool
*
* @param binderCode
* the unique token of binder
* @return binder who's token is binderCode<br>
* return null when not found or BinderPoolService died.
*/
public IBinder queryBinder(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.queryBinder(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
private ServiceConnection mBinderPoolConnection = new ServiceConne
ction() {
@Override
public void onServiceDisconnected(ComponentName name) {
// ignored.
}
@Override
public void onServiceConnected(ComponentName name,IBinder service) {
mBinderPool = IBinderPool.Stub.asInterface(service);
try {
mBinderPool.asBinder().linkToDeath(mBinderPoolDeathReci
pient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
mConnectBinderPoolCountDownLatch.countDown();
}
};
private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(TAG,"binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient,
0);
mBinderPool = null;
connectBinderPoolService();
}
};
public static class BinderPoolImpl extends IBinderPool.Stub {
public BinderPoolImpl() {
super();
}
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDER_SECURITY_CENTER: {
binder = new SecurityCenterImpl();
break;
}
case BINDER_COMPUTE: {
binder = new ComputeImpl();
break;
}
default:
break;
}
return binder;
}
}
}
Binder连接池的具体实现就分析完了,它的好处是显然易见的,针对上面的例子,我们只需要创建一个Service即可完成多个AIDL接口的工作,下面我们来验 证一下效果。新创建一个Activity,在线程中执行如下操作:
private void doWork() {
BinderPool binderPool = BinderPool.getInsance(BinderPoolActivity.
this);
IBinder securityBinder = binderPool
.queryBinder(BinderPool.BINDER_SECURITY_CENTER);
;
mSecurityCenter = (ISecurityCenter) SecurityCenterImpl
.asInterface(securityBinder);
Log.d(TAG,"visit ISecurityCenter");
String msg = "helloworld-安卓";
System.out.println("content:" + msg);
try {
String password = mSecurityCenter.encrypt(msg);
System.out.println("encrypt:" + password);
System.out.println("decrypt:" + mSecurityCenter.decrypt(password));
} catch (RemoteException e) {
e.printStackTrace();
}
Log.d(TAG,"visit ICompute");
IBinder computeBinder = binderPool
.queryBinder(BinderPool.BINDER_COMPUTE);
mCompute = ComputeImpl.asInterface(computeBinder);
try {
System.out.println("3+5=" + mCompute.add(3,5));
} catch (RemoteException e) {
e.printStackTrace();
}
}
有了BinderPool可以大大方便日常的开发工作,比如如果有一个新的业务模块需要添加新的AIDL,那么在它实现了自己的AIDL接口后,只需要修改 BinderPoolImpl中的queryBinder方法,给自己添加一个新的binderCode并返回对应的Binder对象即可,不需要做其他修改,也不需要创建新的Service。由此可 见,BinderPool能够极大地提高AIDL的开发效率,并且可以避免大量的Service创建,因此,建议在AIDL开发工作中引入BinderPool机制。
更多推荐



所有评论(0)