文章目录
一、Android IPC 简介
IPC 是 Inter-Process Communication 的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。说起进程间通信,我们首先要理解什么是进程,什么是线程,进程和线程是截然不同的概念。按照操作系统中的描述,线程是 CPU 调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在 PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含和被包含的关系。最简单的情况下,一个进程中可以只有一个线程,即主线程,在 Android 里面主线程也叫 UI 线程,在 UI 线程里才能操作界面元素。很多时候,一个进程中需要执行大量耗时的任务,如果这些任务放在主线程中去执行就会造成界面无法响应,严重影响用户体验,这种情况在 PC 系统和移动系统中都存在,在 Android 中有一个特殊的名字叫做 ANR(Application Not Responding),即应用无响应。解决这个问题就需要用到线程,把一些耗时的任务放在线程中即可。
1.1、开启多进程模式
- 常规方法:给四大组件在 AndroidMenifest 中指定 android:process 属性。
- 特殊方法(这里不考虑):通过 JNI 在 native 层去 fork 一个新的进程。
示例
- 在 MainActivity 中启动 SecondActivity,在 SecondActivity 中启动 ThirdActivity,然后使用 adb shell “ps|grep com.mryuan.learndemo” 命令查看进程信息。
- 使用 adb shell ps 查看当前运行的全部进程。
- 使用 adb shell “ps|grep com.mryuan.learndemo” 查看 com.mryuan.learndemo 包名下的进程信息。
<activity
android:name=".MainActivity"
android:configChanges="orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:process=":remote" />
<activity
android:name=".ThirdActivity"
android:process="com.mryuan.learndemo.remote" />
-
MainActivity 运行在名称为 com.mryuan.learndemo 的进程中,也是默认进程,进程名就是包名。
-
对于 SecondActivity,:remote 是一种简写的方法,“:” 前面会自动附加包名,创建名称为 com.mryuan.learndemo:remote 的新进程,属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。
-
对于 ThirdActivity ,是一种完整的命名方式,创建名称为 com.mryuan.learndemo.remote 的新进程,属于全局进程,其他应用通过 ShareUID 方式可以和它跑在同一个进程中。
-
关于 UID 。
-
默认情况下,Android 系统会为每个应用分配一个普通级别的唯一的 UID,
-
两个应用间互相访问对方的私有数据,比如 data 目录,组件信息等,就必须要 UID 相同才可以。
-
两个应用通过 ShareUID 的方式跑在同一个进程中,需要两个应用有相同的 UID 并且签名相同才可以。如果跑在同一进程中,除了能共享 data 目录,组件信息等,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。
要想两个应用具有相同的 UID,就需要设置相同的 android:sharedUserId 属性。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.mryuan.learndemo"
android:sharedUserId="com.mryuan.learndemo">
1.2、多进程模式的运行机制
多进程会造成的问题。
-
静态成员和单例模式完全失效。
-
线程同步机制完全失效。
每一个应用都有一个独立的虚拟机,每个进程都会分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,如果在不同的虚拟机中访问同一个类的对象会产生多个副本,是互不干扰的。
所有运行在不同进程中的四大组件,如果它们之间通过内存来共享数据,都会分享失败。
-
SharedPreferences 的可靠性下降。
SharedPreferences 不支持两个进程同时去执行写操作,否则会导致一定几率的数据丢失。这是因为 SharedPreferences 底层是通过读/写 XML 文件实现的,并发写是可能出问题的,甚至并发读/写都可能出问题。
-
Application 会多次创建。
当一个组件跑在一个新的进程中的时候,由于系统要在创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。因此,相当于系统又把这个应用重新启动了一遍,既然重新启动了,那么就会创建新的 Application。
二、IPC 基础概念介绍
2.1、Serializable 接口
-
Serializable 是 Java 所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。
-
想让一个对象实现序列化,只需要这个类实现 Serializable 接口并声明一个 serialVersionUID 即可,这个 serialVersionUID 不是必须的,不声明同样可以序列化,但是这将会对反序列化过程产生影响。
-
关于 serialVersionUID。
- 序列化的时候系统会把当前类的 serialVersionUID 写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的 serialVersionUID,看它是否和当前类的 serialVersionUID 一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的。
- 一般来说,我们应该手动指定 serialVersionUID 的值,比如 1L,也可以根据当前类的结构自动生成它的 hash 值,其效果是一样的。如果不手动指定 serialVersionUID,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的 hash 值并把它赋值给 serialVersionUID,导致序列化前后 serialVersionUID 值不一样,就会反序列化失败。
- 当手动指定了 serialVersionUID 的值后,就可以很大程度上避免反序列化的失败。比如当版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化仍然能够成功,程序仍然能最大限度的恢复数据。不过还需要考虑一种情况,如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,此时尽管 serialVersionUID 验证通过了,反序列化还是会失败,因为类结构有了毁灭性的改变,已经无法从老版本的数据中还原一个新的类结构的对象。
-
静态成员变量属于类不属于对象,不会参与序列化过程;用关键字 transient 标记的成员变量不参与序列化过程。
-
系统默认的序列化过程也是可以改变的,需要重写 writeObject 和 readObject 方法,这里不介绍,大部分情况下并不需要。
例
public class User implements Serializable {
private static final long serialVersionUID = -5521252965650258169L;
public int userId;
public String userName;
public boolean isMan;
...
}
//序列化过程
User user = new User(1, "张三", true);
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();
} catch (IOException e) {
e.printStackTrace();
}
//反序列化过程
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User) in.readObject();
in.close();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
2.2、Parcelable 接口
- Parcelable 也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过 Intent 和 Binder 传递。
- 这里先说一些 Parcel,Parcel内部包装了可序列化的数据,可以在 Binder 中自由传输。
- Parcelable 在序列化过程中需要实现的功能有序列化、反序列化和内容描述。序列化功能由 writeToParcel 方法来完成,最终是通过 Parcel 中的一系列 write 方法来完成的;反序列化功能由 CREATOR 来完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法来完成反序列化过程;内容描述功能由 describeContents 方法来完成,几乎在所有情况下这个方法都应该返回 0,仅当当前对象中存在文件描述符时,此方法返回 1。
- 系统已经为我们提供了许多实现了 Parcelable 接口的类,都是可以直接序列化的,比如 Intent、Bundle、Bitmap 等,同时 List 和 Map 也可以序列化,前提是里面的每个元素都是可序列化的。
- 详细的方法说明。
方法 | 功能 | 标记位 |
---|---|---|
createFromParcel(Parcel source) | 从序列化后的对象中创建原始对象 | |
newArray(int size) | 创建指定长度的原始对象数组 | |
writeToParcel(Parcel dest, int flags) | 当当前对象写入序列化结构中,其中 flags 标识有两种值:0 或 1(参见右侧标记位)。为 1 时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为 0 | PARCELABLE_WRITE_RETURN_VALUE |
describeContents() | 返回当前对象的内容描述。如果含有文件描述符,返回 1(参见右侧标记位),否则返回 0,几乎所有情况都返回 0 | CONTENTS_FILE_DESCRIPTOR |
例
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMan;
public User(int userId, String userName, boolean isMan) {
this.userId = userId;
this.userName = userName;
this.isMan = isMan;
}
public int getUserId() {
return userId;
}
public String getUserName() {
return userName;
}
public boolean isMan() {
return isMan;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.userId);
dest.writeString(this.userName);
dest.writeByte(this.isMan ? (byte) 1 : (byte) 0);
}
protected User(Parcel in) {
this.userId = in.readInt();
this.userName = in.readString();
this.isMan = in.readByte() != 0;
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
}
2.3、Serializable 和 Parcelable 的选择
- Serializable 是 Java 中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量 I/O 操作。而 Parcelable 是 Android 中的序列化方式,缺点就是使用起来较为复杂,但是其效率很高,是 Android 推荐的序列化方式,因此我们要首选 Parcelable。
- Parcelable 主要用在内存序列化上,通过 Parcelable 将对象序列化到存储设备中或者将对象序列化后通过网络传输,虽说可行,但过程复杂,在这两种情况下建议使用 Parcelable。
2.4、Binder
直观来说,Binder 是 Android 中的一个类,它实现了 IBinder 接口。从 IPC 角度来说,Binder 是 Android 中的一种跨进程通信方式,Binder 还可以理解为一种虚拟的物理设备,它的设备驱动是 /dev/binder,该通信方式在 Linux 中没有;从 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 的工作机制。
AIDL示例
一、首先创建一个 Java 文件 Book.java 和两个 aidl 文件 Book.aidl、IBookManager.aidl。其中 Book.java 表示一个图书信息的类,实现了 Parcelable 接口。Book.aidl 是 Book 类在 AIDL 中的声明。IBookManager.aidl 是我们定义的一个接口,里面有两个方法:getBookList 和 addBook,其中 getBookList 用于从远程服务端获取图书列表,addBook 用于往图书列表中添加一本书。
可以看到,尽管 Book 类已经和 IBookManager 位于相同的包中,但是在 IBookManager 中仍然要导入 Book 类,这就是 AIDL 的特殊之处。
public class Book implements Parcelable {
public int bookId;
public String bookName;
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.bookId);
dest.writeString(this.bookName);
}
protected Book(Parcel in) {
this.bookId = in.readInt();
this.bookName = in.readString();
}
public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
}
package com.mryuan.learndemo;
parcelable Book;
package com.mryuan.learndemo;
import com.mryuan.learndemo.Book;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
}
二、创建好上述文件后,编译代码,系统会自动生成 AIDL 所对应的 Binder 类。在这里生成的类名是 IBookManager,它是一个接口类,又继承了 IInterface 接口,所有可以在 Binder 中传输的接口都需要继承 IInterface 接口。
在这个类中,声明了两个方法 getBookList 和 addBook,显然这就是我们在 IBookManager 中所声明的方法,同时它还声明了两个整型的 id,TRANSACTION_getBookList 和 TRANSACTION_addBook,分别用于标识这两个方法, 用于标识在 transact 过程中客户端所请求的到底是哪个方法。
接着声明了一个内部类 Stub,这个 Stub 就是一个 Binder 类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的 transact 过程,而当两者位于不同进程时,方法调用需要走 transact 过程,这个逻辑由 Stub 的内部代理类 Proxy 来完成。IBookManager 这个接口的核心实现就是它的内部类 Stub 和 Stub 的内部代理类 Proxy。
下面介绍针对 Stub 和 Proxy 这两个类的每个方法的含义。
DESCRIPTOR:Binder 的唯一标识,一般用当前 Binder 的类名表示,比如本例中的“com.mryuan.learndemo.IBookManager”;
asInterface(android.os.IBinder obj):用于将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的 Stub 对象本身,否则返回的是系统封装后的 Stub.proxy 对象。
asBinder():此方法用于返回当前 Binder 对象。
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags):这个方法运行在服务端中的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过 code 可以去确定客户端所请求的目标方法是什么,接着从 data 中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向 reply 中写入返回值(如果目标方法有返回值的话),onTransact 方法的执行过程就是这样的。需要注意的时,如果此方法返回 false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。
Proxy#getBookList():这个方法运行在客户端,当客户端远程调用此方法是,它的内部实现是这样的:首先创建该方法所需要的输入型 Parcel 对象 _data、输出型 Parcel 对象 _reply 和返回值对象 List;然后把该方法的参数信息写入 _data 中(如果有参数的话);接着调用 transact 方法来发起 RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的 onTransact 方法会被调用,直到 RPC 过程返回后,当前线程继续执行,并从 _reply 中取出 RPC 过程的返回结果;最后返回 _reply 中的数据。
Proxy#addBook(com.mryuan.learndemo.Book book):这个方法运行在客户端,它的执行过程和 getBookList 是一样的,addBook 没有返回值,所以它不需要从 _reply 中取出返回值。
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.mryuan.learndemo;
public interface IBookManager extends android.os.IInterface
{
/** Default implementation for IBookManager. */
public static class Default implements com.mryuan.learndemo.IBookManager
{
@Override public java.util.List<com.mryuan.learndemo.Book> getBookList() throws android.os.RemoteException
{
return null;
}
@Override public void addBook(com.mryuan.learndemo.Book book) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.mryuan.learndemo.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.mryuan.learndemo.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.mryuan.learndemo.IBookManager interface,
* generating a proxy if needed.
*/
public static com.mryuan.learndemo.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.mryuan.learndemo.IBookManager))) {
return ((com.mryuan.learndemo.IBookManager)iin);
}
return new com.mryuan.learndemo.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
{
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.mryuan.learndemo.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(descriptor);
com.mryuan.learndemo.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.mryuan.learndemo.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.mryuan.learndemo.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.mryuan.learndemo.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.mryuan.learndemo.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getBookList();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.mryuan.learndemo.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.mryuan.learndemo.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.mryuan.learndemo.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.mryuan.learndemo.IBookManager impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.mryuan.learndemo.IBookManager getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public java.util.List<com.mryuan.learndemo.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.mryuan.learndemo.Book book) throws android.os.RemoteException;
}
三、通过上面的分析,对 Binder 的工作机制有了一定了解,但是还需要注意两点:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在 UI 线程中发起此远程请求;其次,由于服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方法去实现,因为它已经运行在一个线程中了。
2.5、手动实现 Binder 类
当我们使用 Service 时,我们会返回一个实现了 IBookManager 接口方法的 Stub 对象。可以看出,我们可以把 Stub 类提取出来直接作为一个独立的 Bindler 类来实现。
public class MyService extends Service {
private final IBookManager.Stub mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
手动实现一个 Binder
一、声明一个 AIDL 性质的接口,只需要继承 IInterface 接口即可。我们声明了一个 Binder 描述符和另外两个 id,这两个 id 分别表示的是 getBookList 和 addBook 方法。如果还有其他方法,按照此固定格式声明新方法即可。
public interface IBookManager2 extends IInterface {
String DESCRIPTOR = "com.mryuan.learndemo.IBookManager2";
int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);
int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
List<Book> getBookList() throws RemoteException;
void addBook(Book book) throws RemoteException;
}
二、实现 Stub 类和 Stub 类中的 Proxy 代理类。
public class BookManager2Impl extends Binder implements IBookManager2 {
public BookManager2Impl() {
this.attachInterface(this, DESCRIPTOR);
}
public static IBookManager2 asInterface(IBinder obj) {
if ((obj == null)) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBookManager2))) {
return ((IBookManager2) iin);
}
return new BookManager2Impl.Proxy(obj);
}
@Override
public IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(descriptor);
List<Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
Book _arg0;
if ((0 != data.readInt())) {
_arg0 = Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements IBookManager2 {
private IBinder mRemote;
Proxy(IBinder remote) {
mRemote = remote;
}
@Override
public IBinder asBinder() {
return mRemote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public List<Book> getBookList() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
List<Book> result;
try {
data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(TRANSACTION_getBookList, data, reply, 0);
reply.readException();
result = reply.createTypedArrayList(Book.CREATOR);
} finally {
reply.recycle();
data.recycle();
}
return result;
}
@Override
public void addBook(Book book) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
data.writeInt(1);
book.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
mRemote.transact(TRANSACTION_addBook, data, reply, 0);
reply.readException();
} finally {
reply.recycle();
data.recycle();
}
}
}
@Override
public List<Book> getBookList() throws RemoteException {
return null;
}
@Override
public void addBook(Book book) throws RemoteException {
}
}
三、我们使用手动实现的 Binder 类和系统生成的 Binder 类是一模一样的,AIDL 的本质只是系统为我们提供了一种快速实现 Binder 的工具。
四、对于 Binder,还有两个很重要的方法 linkToDeath 和 unlinkToDeath。我们知道,Binder 运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们与服务端的 Binder 连接断裂(称之为 Binder 死亡),会导致我们的远程调用失败。更为关键的是,如果我们不知道 Binder 连接已经断裂,那么客户端的功能就会受到影响。为了解决这个问题,Binder 中提供了两个配对的方法 linkToDeath 和 unlinkToDeath,通过 linkToDeath 我们可以给 Binder 设置一个死亡代理,当 Binder 死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。
在客户端绑定远程服务成功后,给 binder 设置死亡代理。linkToDeath 的第二个参数是个标记位,直接设置为 0 即可。
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mBookManager = bookManager;
try {
mBookManager.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBookManager = null;
}
};
声明一个 DeathRecipient 对象,实现 binderDied 方法,当 binder 死亡的时候,系统就会回调这个方法,然后我们就可以移除之前绑定的 binder 代理并重新绑定远程服务。另外,通过 Binder 的方法 isBinderAlive 也可以判断 Binder 是否死亡。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (mBookManager == null) return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
// TODO: 这里重新绑定远程 Service
}
};
三、Android 中的 IPC 方式
Android 的跨进程通信方式有很多,比如可以通过在 Intent 中附加 extras 来传递信息,或者通过共享文件的方式来共享数据,还可以采用 Binder 方式来跨进程通信,另外,ContentProvider 天生就是支持跨进程访问的,也可以用来进行 IPC。此外,通过网络通信也是可以实现数据传递的,所以 socket 也可以实现 IPC。
3.1、使用 Bundle
Android 四大组件中的三大组件(Activity、Service、Receiver)都是支持在 Intent 中传递 Bundle 数据的,由于 Bundle 实现了 Parcelable 接口,所以它可以方便的在不同的进程间传输。
3.2、使用文件共享
共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如 A 进程把数据写入文件,B 进程通过读取这个文件来获取数据。通过文件交换数据很好使用,除了可以交换一些文本信息外,还可以序列化一个对象到文件系统中的同时从另一个进程中恢复这个对象,下面继续 2.1 中的例子。
我们在 MainActivity 中序列化一个User 对象到 SD 卡上的一个文件里,然后在 SecondActivity 中去反序列化。首先 User 实体类。
public class User implements Serializable {
private static final long serialVersionUID = 4904966828959582188L;
public int userId;
public String userName;
public boolean isMan;
public User(int userId, String userName, boolean isMan) {
this.userId = userId;
this.userName = userName;
this.isMan = isMan;
}
public int getUserId() {
return userId;
}
public String getUserName() {
return userName;
}
public boolean isMan() {
return isMan;
}
@NonNull
@Override
public String toString() {
return userId + " " + userName + " " + isMan;
}
}
在 MainActivity 中序列化 User 对象,注意 Android 6.0 以上需要动态申请权限,Android 8.0、Android 10 新建文件也需要特别配置,参考博客。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
public class MainActivity extends AppCompatActivity {
public static final String FILE_PATH = Environment.getExternalStorageDirectory().getPath() + "/test/";
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_jump).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
final RxPermissions rxPermissions = new RxPermissions(MainActivity.this);
if (rxPermissions.isGranted(Manifest.permission.READ_EXTERNAL_STORAGE)
&& rxPermissions.isGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
persistToFile();
} else {
requestPermissions();
}
}
private void persistToFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = new User(1, "张三", true);
File dir = new File(FILE_PATH);
if (!dir.exists()) {
dir.mkdir();
}
File cachedFile = new File(FILE_PATH + "test");
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream(new FileOutputStream(cachedFile));
objectOutputStream.writeObject(user);
Log.d(TAG, "persist user:" + user);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (objectOutputStream != null) {
objectOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
private void requestPermissions() {
final RxPermissions rxPermissions = new RxPermissions(MainActivity.this);
rxPermissions.requestEach(Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
.subscribe(new Consumer<Permission>() {
@Override
public void accept(Permission permission) throws Throwable {
if (permission.granted) {
// 申请权限成功
} else if (permission.shouldShowRequestPermissionRationale) {
// 用户拒绝了该权限,没有选中『不再询问』(Never ask again),那么下次再次启动时,还会提示请求权限的对话框
} else {
// 用户拒绝了该权限,并且选中『不再询问』
}
}
});
}
}
查看 log,可以看到在 SecondActivity 中成功地从文件中恢复了之前存储的 User 对象的内容,之所以说内容,是因为反序列化的对象只是在内容上和序列化之前的对象是一样的,但它们本质上还是两个对象。
public class SecondActivity extends AppCompatActivity {
public static final String FILE_PATH = Environment.getExternalStorageDirectory().getPath() + "/test/";
private static final String TAG = "SecondActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
recoverFromFile();
}
private void recoverFromFile() {
new Thread(new Runnable() {
@Override
public void run() {
User user = null;
File cachedFile = new File(MainActivity.FILE_PATH + "test");
if (cachedFile.exists()) {
ObjectInputStream inputStream = null;
try {
inputStream = new ObjectInputStream(new FileInputStream(cachedFile));
user = (User) inputStream.readObject();
Log.d(TAG, "recover user:" + user.toString());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
通过文件共享这种方式来共享数据对文件格式没有具体要求,比如可以是文本文件,也可以是 XML 文件,只需读/写双方约定数据格式。不过局限就是并发读/写的问题,并发读/写回导致读取的内容不是最新的,如果是并发写会更严重。因此我们要尽量避免并发写这种情况或考虑使用线程同步来限制多个线程的操作。文件共享方式适合在对数据同步要求不高的进程之间通信,并要妥善处理并发读/写的问题。
SharedPreferences 从本质上说也属于文件的一种,但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一份 SharedPreferences 文件的缓存,因此在多进程下,系统对它的读/写就变得不可靠,当面对高并发读/写访问。SharedPreferences 有很大几率会丢失数据,因此不建议在进程间通信中使用 SharedPreferences。
3.3、使用 Messenger
Messenger 是一种轻量级的 IPC 方案,它的底层实现是 AIDL。使用方法很简单,它对 AIDL 做了封装,使我们可以更简便地使用进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。实现一个 Messenger 分为服务端和客户端。
一、服务端进程
首先我们需要在服务端创建一个 Service 来处理客户端的连接请求,同时也创建一个 Handler 并通过它来创建一个 Messenger 对象,然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
public static final int MSG_FROM_CLIENT = 0;
private final Messenger mMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_FROM_CLIENT:
Bundle bundle = msg.getData();
Log.d(TAG, "receive msg from client:" + bundle.getString("msg"));
break;
default:
super.handleMessage(msg);
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind()");
return mMessenger.getBinder();
}
}
注册 service,并设置 process 属性,让其运行在单独的进程中。
<service
android:name=".MessengerService"
android:process=":remote" />
二、客户端进程
首先要绑定服务端的 Service,绑定成功后用服务端返回的 IBinder 对象创建一个Messenger,通过这个 Messenger 就可以向服务端发送消息了,发消息类型为 Message 对象。
public class MainActivity extends AppCompatActivity {
private Messenger mMessenger;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mMessenger = new Messenger(service);
Message msg = Message.obtain(null, MessengerService.MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "这是客户端的信息!");
msg.setData(bundle);
try {
mMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
运行程序,可以看到服务端已经收到了客户端的信息(查看 log 注意:不同的进程日志会分别打印)。
如果需要服务端能够回应客户端,就和服务端一样,我们还需要在客户端创建一个 Handler 并创建一个新的 Messenger,并把这个 Messenger 对象通过 Message 的 replyTo 参数传递给服务端。首先修改服务端代码。
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
public static final int MSG_FROM_CLIENT = 0;
public static final int MSG_FROM_SERVICE = 1;
private final Messenger mMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_FROM_CLIENT:
Bundle bundle = msg.getData();
Log.d(TAG, "receive msg from client:" + bundle.getString("msg"));
//返回给客户端的数据
Messenger client = msg.replyTo;
Message replyMsg = Message.obtain(null, MSG_FROM_SERVICE);
Bundle clientBundle = new Bundle();
clientBundle.putString("reply", "这里是服务端返回的信息");
replyMsg.setData(clientBundle);
try {
client.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind()");
return mMessenger.getBinder();
}
}
再修改客户端的代码。为了接受服务端的回复,客户端也需要准备一个接受消息的 Messenger 和 Handler。当客户端发送消息的时候,需要把接收服务端返回消息的 Messenger 通过 Message 的 replyTo 参数传递给服务端。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Messenger mReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MessengerService.MSG_FROM_SERVICE:
Bundle bundle = msg.getData();
Log.d(TAG, "receive msg from Service:" + bundle.getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Messenger messenger = new Messenger(service);
Message msg = Message.obtain(null, MessengerService.MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("msg", "这是客户端的信息!");
msg.setData(bundle);
msg.replyTo = mReplyMessenger;
try {
messenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
说明一点,在上面的示例中处理都是在一个应用程序中,不过我们分配了不同的进程,这跟在两个应用程序中的进程间通信并没有本质区别。
3.4、使用 AIDL
3.5、使用 ContentProvider
使用 ContentProvider
3.6、使用 Socket
使用 Socket
四、Binder 连接池
Binder 连接池
五、选用合适的 IPC 方式
选用合适的 IPC 方式