Android——IPC机制(一)IPC概念以及Binder机制

时间:2021-10-05 16:15:18

由于IPC机制牵扯的东西比较多,所以这里将分为一个系列进行总结
主要介绍内如如下:

  • IPC简介
  • Android中的多进程模式
    • 开启多进程模式
    • 多进程模式的运行机制
  • IPC基础概念介绍
    • Serializable接口
    • Parcelable接口
    • Binder
  • Android中的IPC方式
    • 使用Bundle
    • 使用文件共享
    • 使用Messenger
    • 使用AIDL
    • 使用ContentProvider
    • 使用Socket
  • Binder连接池
  • 选用合适的IPC方式

IPC简介

IPC是Inter-Process Communication的缩写,其含义是进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程。说起进程间通信,我们首先要理解什么是进程,什么是线程,进程和线程是截然不同的概念。按照操作系统中的描述,线程是CPU调度最小的单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在PC和移动设备上指一个应用程序或一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。最简单的情况下,一个进程中可以只有一个线程,即主线程,在Android里面主线程也叫作UI线程,在UI线程里面才能操作界面的元素。很多时候,一个进程中需要执行大量的耗时的任务,如果这些任务放在主线程中去执行可能会造成界面无法响应,严重影响用户体验,这种情况在PC系统和移动系统中都存在,在Android中有一个特殊的名字叫做ANR(Application Not Responding),即应用无响应。解决这个问题就需要用到线程,把一些耗时的操作放到线程中去执行即可。而IPC的使用场景也很多,比如:一个应用因为某些原因自身需要采用多进程的模式来实现,至于原因,可能有很多,比如有些模块由于特殊原因需要运行在单独的进程中,又或者为了加大一个应用可使用的内存所以需要通过多进程来获取多份内存空间等等…。

Android中的多进程模式

在正式介绍进程间通信之前,我们必须先要理解Android中的多进程模式。通过给四大组件指定Android:process属性,我们可以轻易地开启多进程模式,这看起来很简单,但是实际使用过程中却暗藏杀机,多进程远远没有我们想的那么简单,有时候我们通过多进程得到的好处甚至都不足以弥补使用多进程所带来的代码层面的负面影响。下面会详细分析这些问题。

开启多进程模式

正常情况下,在Android中多进程是指一个应用中存在多个进程的情况,因此这里不讨论两个应用之间的多进程情况。首先,在Android中使用多进程只有一种方法,那就是给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidMenifest文件中指定android:process属性,除此之外没有其他办法,也就是说我们无法给一个线程或者一个实体类指定其运行时所在的进程。其实还有另外一种非常规的多进程方法,那就是通过JNI在native层去fork一个新的进程,但是这种方法属于特殊情况,也不是常用的创建多进程的方式,因此我们不考虑这种方式。下面通过一个示例,描述如何Android中创建多进程:

        <activity
android:name=".MainActivity"
android:configChanges="screenLayout">

<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:configChanges="screenLayout"
android:launchMode="singleTask"
android:process=":remote">
</activity>
<activity
android:name=".ThirdActivity"
android:configChanges="screenLayout"
android:launchMode="singleTask"
android:process="com.layoutinflate.mk.www.myapplication.remote">
</activity>

上面的示例分别为SecondActivity和ThirdActivity指定了process属性,并且他们的属性值不同这意味着当前应用又增加了两个新进程。假设当前应用的包名为“com.layoutinflate.mk.www.myapplication”,当SecondActivity启动时,系统会为它创建一个单独的进程,进程名为“com.layoutinflate.mk.www.myapplication:remote”;当ThirdActivity启动时,系统也会为它创建一个单独的进程,进程名为“com.layoutinflate.mk.www.myapplication.remote”(这里需要注意的是这个进程名并不一样,SecondActivity和ThirdActivity并不在一个进程中,后面会详细讲解)。同时入口Activity是MainActivity,没有为它指定process属性,那么它运行在默认进程中,默认进程的进程名是包名。下面我们来运行下看下效果,如图所示:
Android——IPC机制(一)IPC概念以及Binder机制

可以看到进程列表中存在者3个进程,进程ID分别为:26864、27148、27174这说明我们的应用已经成功地使用了多进程技术。除了使用IDE的DDMS视图查看进程信息,还可以使用shell命令来查看,命令为:adb shell ps 或者 adb shell ps | grep com.layoutinflate.mk.www.myapplication。其中,com.layoutinflate.mk.www.myapplication是包名。

$ adb shell ps | grep com.layoutinflate.mk.www.myapplication
u0_a92 4129 32487 415604 26416 ffffffff 00000000 S com.layoutinflate.mk.www.myapplication
u0_a92 4155 32487 415636 26460 ffffffff 00000000 S com.layoutinflate.mk.www.myapplication:remote
u0_a92 4181 32487 420560 28064 ffffffff 00000000 S com.layoutinflate.mk.www.myapplication.remote

说到这里不知道有没有注意到,SecondActivity和ThirdActivity的android:process属性分别为:”:remote” 和 “com.layoutinflate.mk.www.myapplication.remote”,那么这两种方式有什么区别么?其实是有区别的,区别主要体现在两方面:首先,“:”的含义是指在当前的进程名前面附件上当前的包名,这是一种简写的方法,对于SecondActivity来说,它的完整进程名为”com.layoutinflate.mk.www.myapplication:remote”,这一点可以通过上面给出的效果图以及我们通过adb shell 都可以查看到,二对于ThirdActivity中的生命方式,它是一种完整的命名方式,不会附加包名信息;其次,进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以
“:”开头的进程属于全局进程,其他应用可以通过ShareUID方式可以和它跑在同一个进程。

关于如何不同应用中如何设置相同的ShareUID可以参考这些文章:

Android系统会每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据(这个UID是可以手动设置的)。这里需要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以。在这种情况下,他们可以互相访问对方的私有数据,比如data目录,组件信息等,不管它们是否跑在同一个进程中。当然如果它们跑在同一个进程中,那么除了能共享data目录、组件信息,还可以共享内存数据,或者说它们看起来就像是一个应用的两个部分。

多进程模式的运行机制

大部分人都认为开启多进程是很简单的事情,只需要给四大组件指定android:process属性即可,但是使用多进程真的就那么简单么?可以说“当应用开启了多进程以后,各种奇怪的现象都出现了”,为什么这么说呢?下面我们就通过一个例子来验证使用多进程时会出现的一些问题:还是之前说的那个例子,其中SecondActivity通过指定android:process属性从而是其运行在一个独立的进程中,这里做了一些改动,我们新建了一个类,叫做UserManager,这个类中有一个public的静态成员变量,如下所示:

public class UserManager {
public static int sUserId = 1;
}

我们要做的操作非常简单,我们只需要在MainActivity的onCreate方法中吧这个sUserId重新赋值为2,打印这个静态变量后的值后在启动SecondActivity,在SecondActivity中我们在打印一下sUserID的值,按照我们的正常逻辑,静态变量是可以在所有地方共享的,并且一处修改处处同步,那么按照我们的猜想那么SecondActivity中打印的sUserID的值也应该为2,我们看下结果再来讨论;

08-27 10:00:34.299 5804-5804/? E/MainActivity: sUserId = 2
08-27 10:00:37.141 5833-5833/? E/SecondActivity: sUserId = 1

咦!怎么打印的结果和我们猜想的不一样呢?从日志上可以看到SecondActivity中打印的sUserId的值还是为“1”,可是我们的确已经在MainActivity中把sUserId重新赋值为2了。看到这里,大家应该明白了这就是多进程所带来的问题,多进程绝非只是仅仅指定一个android:process属性那么简单。

上述问题出现的原因是SecondActivity运行在一个单独的进程中,我们知道Android为每一个应用分配了一个独立的虚拟机,或者说为每一个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个对象会产生多份副本。那我们这个例子来说,在进程“com.layoutinflate.mk.www.myapplication”和进程“com.layoutinflate.mk.www.myapplication:remote”中我们都存在一个UserManager类,并且这两个类是互不干扰的,在一个进程中修改sUserId的值只会影响当前进程,对其他进程不会造成任何影响,这样我们就可以理解为什么在MainActivity中修改了sUserid的值,但是在SecondActivity中sUserId的值却没有发生改变这个现象。

所有运行在不同进程中的四大组件,只要他们之间需要通过内存来共享数据,都会共享失败。(只有在同一个进程中才可以共享内存),这也是多进程多带来的影响。正常情况下,四大组件中间不可能通过一些中间层来共享数据,那么简单地指定进程名来开启多进程多会无法正确运行。当然特殊情况下,某些组件之间不需要共享数据,这个时候可以直接指定android:process属性来开启多进程,但是这种场景是不常见的,几乎所有情况都需要共享数据。

一般来说使用多进程会造成如下几方面的问题:

  • 1、静态成员和单例模式完全失效。
  • 2、线程同步机制完全失效。
  • 3、SharedPreferences的可靠性下降。
  • 4、Application会多次创建
    第1个问题上面已经进行了分析。第2个问题本质上和第一个问题是类似的,既然都不是一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象。第三个问题是因为SharePreferences不支持两个进程去执行写操作,否则会导致一定几率的数据丢失,这是因为SharePreference底层是通过读/写XML文件来实现的,并发写显然是可能出问题的,甚至并发读/写都有可能出问题。第四个问题也是显而易见的,当一个组件跑在一个新的进程中的时候,由于系统要创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。因此,相当于系统有吧这个应用重新启动了一遍,既然都重新启动了,那么自然会创建新的Application。这个问题其实可以这么理解,运行在同一个进程中的组件是属于同一个虚拟机和同一个Application,同理,运行在不同进程中的组件是属于两个不同虚拟机和Application。为了更加清晰地展示这个点,下面我们来做个测试,首先在Application的onCreate方法中打印出当前进程的名字,然后连续启动三个同一个应用内但不属于同一个进程的Activity,按照期望,Application的onCreate方法应该会执行三次并打印三次进程名不同的Log,代码如下所示:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
String curProcessName = getCurProcessName(getApplicationContext());
Log.e("MyApplication", "application process name = " + curProcessName);
}

private String getCurProcessName(Context context) {
int pid = android.os.Process.myPid();
ActivityManager mActivityManager = (ActivityManager) context
.getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager
.getRunningAppProcesses()) {
if (appProcess.pid == pid) {

return appProcess.processName;
}
}
return null;
}
}

运行下开一下log,如图所示:

08-27 10:56:19.029 8231-8231/? E/MyApplication: application process name = com.layoutinflate.mk.www.myapplication
08-27 10:56:21.745 8282-8282/? E/MyApplication: application process name = com.layoutinflate.mk.www.myapplication:remote
08-27 10:56:23.022 8308-8308/? E/MyApplication: application process name = com.layoutinflate.mk.www.myapplication.remote

通过log可以看出,Application共执行了三次onCreate,并且每次的进程名称和进程ID都不一样,它们的进程名是和我们为Activity指定的android:process属性是一致的。这也就证实了在多进程模式中,不同进程的组件的确会拥有独立的虚拟机、Application以及内存空间,这会给实际的开发带来很多困扰。

IPC基础概念介绍

IPC的基础概念主要包含三方面的内容:Serializable接口、Parcelable接口以及Binder,只有熟悉这三方面的内容后,我们才能更好地理解跨进程通信的各种方式。Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,这个时候也需要使用Serializable来完成对象的持久化。

Serializable接口

Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程:

private static final long serialVersionUID = 1L;

上面提到,想让一个对象实现序列化,只需要这个类实现Serializable接口并声明一个serialVersionUID即可,实际上,甚至这个serialVersionUID也不是必需的,我们不声明这个serialVersionUID同样也可以实现序列化,但是这将会对反序列化过程产生影响,具体什么影响后面在介绍。下面我们来看个例子:

public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int userId;
private String name;


public User(int userId, String name) {
this.setUserId(userId);
this.setName(name);
}

public static long getSerialVersionUID() {
return serialVersionUID;
}

public int getUserId() {
return userId;
}

public void setUserId(int userId) {
this.userId = userId;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

通过Serializable方式来实现对象的序列化,实现起来非常简单,几乎所有的工作都被系统自动完成了。如果进行对象的序列化和反序列化也非常简单,只需要采用ObjectOutputStream和ObjectInputStream即可轻松实现。下面给出代码:

//序列化过程
try {
User user = new User(0, "mk");
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream(new File(getCacheDir(),"cache.text")));
objectOutputStream.writeObject(user);
objectOutputStream.close();
} catch (IOException e) {

}
//反序列化过程
try {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File(getCacheDir(),"cache.text")));
User user = (User) objectInputStream.readObject();
Log.e("mainUser", "user" + user + "name" + user.getName() + "userid" + user.getUserId());
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}

上述代码演示了采用Serializable方式序列化对象的典型过程,很简单,只需要把实现了Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象user和user的内容完全一样,但是两者并不是同一个对象。

刚开始提到,即使不指定serialVersionUID也可以实现序列化,那到底要不要指定呢?如果指定的话,serialVersionUID后面的那一长串数字又是什么含义呢?我们要明白,系统既然提供了这个serialVersionUID,那么它必须是有用的。这个serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化。serialVersionUID的详细工作机制是这样的:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(也可能是其他中介),当反序列化的时候系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的。

一般来说,我们应该手动指定serialVersionUID的值,比如1L,也可以让Eclipse根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常反序列化。如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋值给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,于是反序列化失败,程序就会出现crash。所以,我们明显感觉到serialVersionUID的作用,当我们手动指定了它以后,就可以在很大程度上避免反序列化过程的失败。比如当前版本升级后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反向序列化过程仍然能够成功,程序仍然能够最大限度地恢复数据,相反,如果不指定serialVersionUID的话,程序则会挂掉。当然我们还要考虑另外一种情况,如果类结构发生了非常规性的改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。(给serialVersionUID指定为1L或者采用Eclipse根据当前类结构去生成hash值,这两者没有本质区别,效果完全一样。)

Parcelable接口

Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

Binder

直观来说,Binder是Android中的一个类,它实现了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从AndroidFramework角度来说,Binder是ServiceManger连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Android开发中,Binder主要用在Service中,包括AIDL和Messager,其中普通Service中的Binder不涉及进程间通信,所以较为简单,无法设计Binder的核心(startService),而Messager的底层其实是AIDL,所以这里选用AIDL来分析Binder的工作机制,这里我们只分析系统为我们自动生成的Binder类,我们都知道我们在使用AIDL的时候,都需要创建一个接口(这个接口就是我们的Binder),这里我们就暂时创建一个IBookManager.aidl文件,代码如下所示:
我们自己创建的AIDL文件:

使用Android studio的可以参考这篇文章来生成接口的Java文件 Android Studio实现Service AIDL

// IBookManager.aidl
package com.layoutinflate.mk.www.myapplication;

// Declare any non-default types here with import statements

interface IBookManager {
void addBook();
}

系统为IBookManager生成的Binder类:

/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: G:\\android_github_project\\GitLab\\MyApplication\\app\\src\\main\\aidl\\com\\layoutinflate\\mk\\www\\myapplication\\IBookManager.aidl
*/

package com.layoutinflate.mk.www.myapplication;
// Declare any non-default types here with import statements

public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/

public static abstract class Stub extends android.os.Binder implements com.layoutinflate.mk.www.myapplication.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.layoutinflate.mk.www.myapplication.IBookManager";

/**
* Construct the stub at attach it to the interface.
*/

public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

/**
* Cast an IBinder object into an com.layoutinflate.mk.www.myapplication.IBookManager interface,
* generating a proxy if needed.
*/

public static com.layoutinflate.mk.www.myapplication.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.layoutinflate.mk.www.myapplication.IBookManager))) {
return ((com.layoutinflate.mk.www.myapplication.IBookManager) iin);
}
return new com.layoutinflate.mk.www.myapplication.IBookManager.Stub.Proxy(obj);
}

@Override
public android.os.IBinder asBinder() {
return this;
}

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
this.addBook();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

private static class Proxy implements com.layoutinflate.mk.www.myapplication.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 void addBook() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}

static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}

public void addBook() throws android.os.RemoteException;
}

上述代码是系统生成的,在gen目录下(使用Android studio是在build/generated/resource/aidl/debug下)可以看到根据IBookManager.aidl系统为我们生成了IBookManager.java 类,它继承了IInterface这个接口,同时它自己也还是个接口,所以可以在Binder中传输的接口都需要继承IInterface接口。这个类刚开始看起来逻辑很混乱,但是实际上还是很清晰的,通过它我们可以很清楚地了解到Binder的工作机制。这个类的结构其实很简单,首先,它声明了一个方法addBook,显然这就是我们在IBookManager.aidl中声明的方法,同时它还声明了一个整型的id用于标识这个方法(TRANSACTION_addBook),这个id用于标识在transact过程中客户端所请求的到底是那个方法。接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,同时也是IBookManager的具体实现,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑有Stub的内部代理类Proxy来完成。这么看来,IBookManager这个接口的确很简单,但是我们也应该认识到,这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy,下面详细介绍针对这两个类的每个方法的含义:

  • 1、DESCRIPTOR

    Binder的唯一标识,一般用当前Binder的类名表示,比如本例中的“com.layoutinflate.mk.www.myapplication.IBookManager”

  • 2、asInterface(android.os.IBinder obj)

    用于将服务端的Binder对象转换为客户端所需要的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

  • 3、asBinder

    此方法用于返回当前Binder对象。

  • 4、onTransact

    这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交友此方法来处理。该方法的原型为 public Boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)。服务端通过code可以确定可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact方法的执行过程就是这样的。需要注意的是,如果此方法返回false,那么客户端会请求失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。

  • 5、Proxy#addBook

    这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法需要的输入型Parcel对象_data、输出型对象_reply;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据。

通过上面的分析,我们应该已经了解了Binder的工作机制,但是有两点还是需要额外说明一下:首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;其次由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了,为了更好地说明Binder,给出Binder的工作机制图:

Android——IPC机制(一)IPC概念以及Binder机制

可以根据上述例子中的Proxy#addBook 结合onTransact来分析上图的具体执行流程。

关于更多Binder的文章:

由于篇幅的原因,关于进程间通信方式的介绍就放到下章了。。。