(一)Android IPC简介
1.IPC是什么?
进程间通讯或者跨进程通讯 ,两个进程间进行数据交互的一个过程。java
2.进程与线程之间的关系?
线程是CPU调度的最小单元。android
而进程通常指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程能够包含多个线程,所以进程和线程是包含与被包含的关系。git
补充:进程的五种优先级
前台进程
即用户当前操做所必需的进程。若是一个进程知足如下任一条件,即视为前台进程:
托管用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)
托管某个 Service,该Service绑定到用户正在交互的 Activity
托管正在前台运行的 Service(服务已调用 startForeground())
托管正在执行生命周期回调的 Service(onCreate()、onStart() 或 onDestroy())
托管正执行其 onReceive() 方法的 BroadcastReceiver
一般,在任意给定时间前台进程都为数很少,只有在内存不足以支持它们同时继续运行这一状况下系统才会终止它们。 此时,设备每每已达到内存分页状态,所以须要终止一些前台进程来确保用户界面正常响应
可见进程
没有任何前台组件,但仍会影响用户在屏幕上所见内容的进程。若是一个进程知足如下任一条件,即视为可见进程:
托管不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,若是前台 Activity 启动了一个覆盖一部分屏幕的对话框时,就会出现这种状况
托管绑定到可见或前台Activity 的 Service
可见进程被视为是极其重要的进程,除非为了维持全部前台进程同时运行而必须终止,不然系统不会终止这些进程
服务进程
正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别的进程。
尽管服务进程与用户所见内容没有直接关联,可是它们一般在执行一些用户关心的操做(例如,在后台播放音乐或从网络下载数据)。所以,除非内存不足以维持全部前台进程和可见进程同时运行,不然系统会让服务进程保持运行状态
后台进程
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。
这些进程对用户体验没有直接影响,系统可能会随时终止它们以回收内存供前台进程、可见进程或服务进程使用。 一般会有不少后台进程在运行,所以它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。若是某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,由于当用户导航回该 Activity 时,Activity 会恢复其全部可见状态
空进程
不含任何活动应用组件的进程。
保留这种进程的的惟一目的是用做缓存,以缩短下次在其中运行组件所需的启动时间。为使整体系统资源在进程缓存和底层内核缓存之间保持平衡,系统每每会终止这些进程
3.什么是ANR应用无响应?
主线程中去执行耗时任务,就会形成界面没法响应(ANR),严重影响用户体验。github
解决方法:把一些耗时的任务放在子线程中便可。shell
4.不一样平台的IPC机制。
Windows上:剪贴板、管道和邮槽等;数组
Linux上:命名管道,共享内容、信号量等;缓存
Android:Binder实现进程间通讯,Socket也能够实现任意两个终端之间的通讯。bash
5.多进程通讯的应用场景?
ContentProvider;
多进程应用:单进程容易受到内存限制而OOM,部分模块好比WebView能够单独做为一个进程
(二)Android中的多进程模式
使用android:process属性能够轻易开启多进程模式;但多进程未必比单个进程好。服务器
(1)开启多进程模式
Android中多进程是指一个应用中存在多个进程的状况,通常只有给四大组件(Activity、BroadcastReceiver、Service、Receiver)指定android:process一种方法。网络
2.1.1相关代码:
<activity android:name =".MainActivity" >
<intent-filter >
<action android:name ="android.intent.action.MAIN" />
<category android:name ="android.intent.category.LAUNCHER" />
</intent-filter >
</activity >
<activity android:name =".Main2Activity" android:process =":remote" />
<activity android:name =".Main3Activity" android:process = "com.example.hzk.myapplication1.remote" />
复制代码
2.1.2 效果:
使用DDMS视图或者命令能够查看:adb shell ps | findstr com.example.hzk.myapplication1 前提是通讯:
2.1.3 注意事项:
Main2Activity和Main3Activity的“:”和“.”的区别:
(1)“:”是简写;“.”须要使用全称,包括包名。
(2)”:”开头的属当前应用的私有进程,其余应用的组件不能够和它跑在同一个进程中,而进程名不以”:”开头的进程属于全局进程,其余应用经过ShareUID方式能够和它跑在同一进程中。 每一个应用有一个惟一的UID,具备相同UID的应用才能共享数据。两个应用经过ShareUID跑在同一个进程由是有要求的,须要这两个应用有相同的ShareUID而且签名相同才能够。分享的信息包括:data目录、组件信息等。
(2)多进程模式的运行机制
2.2.2多进程存在的问题
静态成员和单例模式彻底失效(每一个进程都分配一个独立的虚拟机,有不一样的地址空间,因而有不一样的对象备份,而不是同一份);
线程同步机制彻底失效(不是一块内存,因此不一样进程锁住的不是同一个对象);
SharedPreferences的可靠性降低(不支持两个进程同时执行写操做)。
Application会屡次建立(两个不一样的虚拟机和Application的)。
2.2.3实现跨进程通讯的方式
Intent来传递数据,
共享文件和SharedPreferences,
基于Binder的Messenger和AIDL
Socket
(三)IPC基础概念介绍
Serializable和Parcelable能够完成对象的序列化过程,当咱们须要经过Intent和Binder传输数据时,或者在对象持久化到存储设备上或者经过网络传输给其余客户端就须要使用序列化。
序列化是将对象的状态信息转换为能够存储或传输的二进制字节流 形式的过程。
(1)Serializable接口
Serializable是java中提供的一个序列化接口,它是一个空接口 ,对象实现这个Serializable接口,就标记这个对象是可序列化的 。
serialVersioUID做用:
工做机制:序列化的时候会把当前类的serialversionUID写进序列化的文件中,当反序列化的时候系统会去检测文件中serialversionUID,看它是否和当前类的serialversionUID一致,若是一致就说明序列化的类的版本和当前类的版本是相同的,这个时候能够成功反序列化;不然就说明当前类和序列化的类相比发生了某些变换,好比成员,类型可能发生了变化,这个时候是没法正常的反序列化的。
当版本升级后,咱们可能删除了某个变量或者新增长了一些新的成员变量,只要serialVersionUID不变,这个时候咱们的反向序列化过程仍然可以成功,程序仍然可以最大限度的恢复数据。
但如修改了类名,修改了成员常量的类型,即便serialVersionUID经过验证,也会反序列化失败。
若是没有手动指定serialVersionUID,系统也会根据当前类的结构生成它的hahs值。
须要注意的是:
(1)静态成员变量属于类不属于对象,因此不会参与序列化过程;
(2)transient关键字标记的成员变量不参与序列化过程。
(2)Parcelable接口
Parcelable是一个接口,一个类的对象就能够实现序列化并能够经过Intent和Binder 传递。 3.2.1代码实现:
public class User implements Parcelable {
public int userId;
public String userName;
public boolean isMale;
protected User (int userId,String userName,boolean isMale) {
this .userId = userId;
this .userName = userName;
this .isMale = isMale;
}
@Override
public void writeToParcel (Parcel out, int flags) {
out.writeInt(userId);
out.writeString(userName);
out.writeInt(isMale?1 :0 );
}
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel (Parcel in) {
return new User(in);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
public User (Parcel in) {
userId = in.readInt();
userName =in.readString();
isMale = in.readInt() ==1 ;
}
@Override
public int describeContents () {
return 0 ;
}
}
复制代码
3.2.2每一个实现方法的功能: 序列化功能由writeToParcel方法来完成,最终是经过Parcel中的一系列write方法来完成的,
反序列化功能由CREATOR来完成,其内部标明了如何建立序列化对象和数组,并经过Parcel的一系列read方法来完成反序列化过程。User方法从序列化后的对象中建立原始对象。
在Book(Parcel in)方法中,若是有一个成员变量是另外一个可序列化对象,在反序列化过程当中须要传递当前线程的上下文类加载器,不然会报没法找到类的错误。
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
复制代码
这里我推荐一个插件android-parcelable-intellij-plugin ,专门用于自动生成这些重复样本代码。
Parcelable和Serializable如何选择?
二者最大的区别在于 存储媒介的不一样,Serializable 使用 I/O 读写存储在硬盘上,而 Parcelable 是直接 在内存中读写。很明显,内存的读写速度一般大于 IO 读写,因此在 Android 中传递数据优先选择 Parcelable
Serializable 会使用反射,序列化和反序列化过程须要大量 I/O 操做, Parcelable 自已实现封送和解封(marshalled &unmarshalled)操做不须要用反射,数据也存放在 Native 内存中,效率要快不少。
Parcelable 的性能比 Serializable 好,在内存开销方面较小,因此在内存间数据传输时推荐使用 Parcelable(如 Activity 间传输数据)。
而 Serializable 可将数据持久化方便保存,因此在须要保存或网络传输数据时选择 Serializable,由于 Android 不一样版本 Parcelable 可能不一样,因此不推荐使用 Parcelable进行数据持久化.
2.3.3 Binder
Binder是Android中的一个类,实现了 IBinder 接口。从IPC角度说,Binder是Andoird的一种跨进程通信方式,Binder还能够理解为一种虚拟物理设备,它的设备驱动是/dev/binder。从Android Framework角度来讲,Binder是 ServiceManager 链接各类Manager( ActivityManager· 、 WindowManager )和相应 ManagerService 的桥梁。从Android应用层来讲,Binder是客户端和服务端进行通讯的媒介,当bindService时,服务端返回一个包含服务端业务调用的Binder对象,经过这个Binder对象,客户端就能够获取服务器端提供的服务或者数据( 包括普通服务和基于AIDL的服务)。
Binder通讯采用C/S架构,从组件视角来讲,包含Client、Server、ServiceManager以及binder驱动,其中ServiceManager用于管理系统中的各类服务。 图中的Client,Server,Service Manager之间交互都是虚线表示,是因为它们彼此之间不是直接交互的,而是都经过与Binder驱动进行交互的,从而实现IPC通讯方式。其中Binder驱动位于内核空间,Client,Server,Service Manager位于用户空间。Binder驱动和Service Manager能够看作是Android平台的基础架构,而Client和Server是Android的应用层,开发人员只需自定义实现client、Server端,借助Android的基本平台架构即可以直接进行IPC通讯。
Android中Binder主要用于 Service ,包括AIDL和Messenger。普通Service的Binder不涉及进程间通讯,Messenger的底层实际上是AIDL,因此下面经过AIDL分析Binder的工做机制。
由系统根据AIDL文件自动生成.java文件
Book.java 表示图书信息的实体类,实现了Parcelable接口。
Book.aidl Book类在AIDL中的声明。
IBookManager.aidl 定义的管理Book实体的一个接口,包含 getBookList 和 addBook 两个方法。尽管Book类和IBookManager位于相同的包中,可是在IBookManager仍然要导入Book类。
IBookManager.java 系统为IBookManager.aidl生产的Binder类,在 gen 目录下。
IBookManager继承了 IInterface 接口,全部在Binder中传输的接口都须要继IInterface接口。结构以下:
声明了 getBookList 和 addBook 方法,还声明了两个整型id分别标识这两个方法,用于标识在 transact 过程当中客户端请求的究竟是哪一个方法。
声明了一个内部类 Stub ,它继承自Binder,而且是一个抽象类,并无实现getBookList 和 addBook 方法,这两个方法须要服务端 new Stub的时候去实现。
当客户端和服务端位于同一进程时,方法调用不会走跨进程的 transact 。
当两者位于不一样进程时,方法调用须要走 transact 过程,就会调用到 Stub 的内部代理类 Proxy中的方法
Stub 的内部代理类 Proxy,由客户端来使用。它实现了IBookManager接口,而且实现了 getBookList 和 addBook 方法,可是里面只是把数据装进data这个Parcel对象,经过mRemote的transact方法发送给服务端,接着用reply这个Parcel对象等待服务端数据的返回,这一切都是经过mRemote这个IBinder对象进行,mRemote表明着Binder对象的本地代理,mRemote会经过Binder驱动来完成与远程服务端的Stub的通讯。
这个接口的核心实现就是它的内部类 Stub 和 Stub 的内部代理类 Proxy 。
Stub和Proxy类的内部方法和定义
DESCRIPTOR Binder的惟一标识,通常用Binder的类名表示。
asInterface(android.os.IBinder obj) 将服务端的Binder对象转换为客户端所需的AIDL接口类型的对象,若是C/S位于同一进 程,此方法返回就是服务端的Stub对象自己,不然返回的就是系统封装后的Stub.proxy对 象。因此,跨进程通讯中客户端是使用代理对象来实现通讯的。
asBinder 返回当前Binder对象。
onTransact 这个方法运行在服务端的Binder线程池中,由客户端发起跨进程请求时,远程请求会经过 系统底层封装后交由此方法来处理。该方法的原型是
java public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)
复制代码
服务端经过code肯定客户端请求的目标方法是什么,
接着从data取出目标方法所需的参数,而后执行目标方法。
执行完毕后向reply写入返回值( 若是有返回值) 。
若是这个方法返回值为false,那么服务端的请求会失败,利用这个特性咱们能够来作权限验证。
Proxy#getBookList 和Proxy#addBook 这两个方法运行在客户端,内部实现过程以下:
首先建立该方法所须要的输入型对象Parcel对象_data,输出型Parcel对象_reply和返回值对象List。
而后把该方法的参数信息写入_data( 若是有参数)_
_接着调用transact方法发起RPC( 远程过程调用) ,同时当前线程挂起
而后服务端的onTransact方法会被调用知道RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。
AIDL文件不是必须的,之因此提供AIDL文件,是为了方便系统为咱们生成IBookManager.java,但咱们彻底能够本身写一个。
Binder的工做机制
因为当前线程会被挂起知道服务端进程返回数据,因此远程请求若是很耗时,则不能在UI线程在发起;
Binder方法须要用同步的方法去实现,由于他已经运行在一个线程中了。
linkToDeath和unlinkToDeath
若是服务端进程异常终止,咱们到服务端的Binder链接断裂。可是,若是咱们不知道Binder链接已经断裂,那么客户端功能会受影响。经过linkTODeath咱们能够给Binder设置一个死亡代理,当Binder死亡时,咱们就会收到通知。
首先,声明一个 DeathRecipient 对象。 DeathRecipient 是一个接口,只有一个方法 binderDied ,当Binder死亡的时候,系统就会回调 binderDied 方法,而后咱们就能够从新绑定远程服务。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied () {
if (mBookManager == null ){
return ;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0 );
mBookManager = null ;
}
}
复制代码
而后,在客户端绑定远程服务成功后,给binder设置死亡代理:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
复制代码
另外,能够经过Binder的 isBinderAlive 判断Binder是否死亡。
2.4 Android中的IPC方式
主要有如下方式:
Intent中附加extras
共享文件
Binder
ContentProvider
Socket
2.4.1 使用Bundle
四大组件中的三大组件( Activity、Service、Receiver) 都支持在Intent中传递 Bundle 数据。
Bundle实现了Parcelable接口,所以能够方便的在不一样进程间传输。
当咱们在一个进程中启动了另外一个进程的Activity、Service、Receiver,能够再Bundle中附加咱们须要传输给远程进程的消息并经过Intent发送出去。被传输的数据必须可以被序列化。
可是在Intent 传输数据的过程当中,用到了 Binder,Intent中的数据,即Bundle数据,会做为 Parcel 存储在Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输,而这个 Binder 事务缓冲区具备一个有限的固定大小,约为1MB,并且这个1Mb并非当前进程所独享,而是全部进程共享的,因此因为1Mb的限制,Bundle不能存放大量的数据,否则会报TransactionTooLargeException,而且Bundle中存放的数据也要求可以被序列化,因此Bundle只适用于数据量小和简单的进程间通讯。
2.4.2 使用文件共享
咱们能够序列化一个对象到文件系统中的同时从另外一个进程中恢复这个对象。
经过 ObjectOutputStream / ObjectInputStream 序列化一个对象到文件中,或者在另外一个进程从文件中反序列这个对象。注意:反序列化获得的对象只是内容上和序列化以前的对象同样,本质是两个对象 。
Android基于Linux,文件支持并发读写。而文件并发读写会致使读出的对象可能不是最新的 。因此文件共享方式适合对数据同步要求不高的进程之间进行通讯 ,而且要妥善处理并发读写问题。
SharedPreferences 底层实现采用XML文件来存储键值对。系统对它的读/写有必定的缓存策略,即在内存中会有一份 SharedPreferences 文件的缓存,所以在多进程模式下,系统对它的读/写变得不可靠,面对高并发读/写时 SharedPreferences 有很大概率丢失数据,所以不建议在IPC中使用 SharedPreferences 。
2.4.3 使用Messenger
Messenger能够在不一样进程间传递Message对象。是一种轻量级的IPC方案,底层实现是AIDL 。它对AIDL进行了封装,使得咱们能够更简便的进行IPC。
具体使用时,分为服务端和客户端:
服务端:建立一个Service来处理客户端请求,同时建立一个Handler并经过它来建立一个 Messenger,而后在Service的onBind中返回Messenger对象底层的Binder便可。 private final Messenger mMessenger = new Messenger (new xxxHandler());
客户端:绑定服务端的Sevice,利用服务端返回的IBinder对象来建立一个Messenger,经过这个Messenger就能够向服务端发送消息了,消息类型是 Message 。若是须要服务端响应,则须要建立一个Handler并经过它来建立一个Messenger( 和服务端同样) ,并经过 Message 的 replyTo 参数传递给服务端。服务端经过Message的 replyTo 参数就能够回应客户端了。
总而言之,就是客户端和服务端 拿到对方的Messenger来发送 Message 。只不过客户端经过bindService 而服务端经过 message.replyTo 来得到对方的Messenger。
Messenger在 Hanlder 以串行的方式处理队列中的消息 ,因此不存在并发执行,所以咱们不用考虑线程同步 的问题。
缺点:
Messenger是串行处理消息的,不适合大量并发请求
Messenger用来传递消息,没法跨进程调用服务端的方法。
2.4.4 使用AIDL
AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言 ,是用于定义服务端和客户端通讯接口的一种描述语言,能够拿来生成用于 IPC 的代码。 使用步骤以下:
服务端须要建立Service来监听客户端请求,而后建立一个AIDL文件,将暴露给客户端的接口在AIDL文件中声明,最后在Service中实现这个AIDL接口便可。
客户端首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就能够调用AIDL中的方法了。
AIDL支持的数据类型:
基本数据类型、String、CharSequence
List:只支持ArrayList,里面的每一个元素必须被AIDL支持
Map:只支持HashMap,里面的每一个元素必须被AIDL支持
Parcelable
全部的AIDL接口自己也能够在AIDL文件中使用
自定义的Parcelable对象和AIDL对象,无论它们与当前的AIDL文件是否位于同一个包,都必须显式import进来。 若是AIDL文件中使用了自定义的Parcelable对象,就必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。
package com.ryg.chapter_2.aidl;
parcelable Book;
复制代码
AIDL接口中的参数除了基本类型之外都必须代表方向in/out。
AIDL接口文件中只支持方法,不支持声明静态常量。
建议把全部和AIDL相关的类和文件放在同一个包中,方便管理。
AIDL方法是在服务端的Binder线程池中执行的,所以当多个客户端同时链接时,管理数据的集合直接采用 CopyOnWriteArrayList 来进行自动线程同步,只要这个集合类实现了List接口就可使用,不必定非要是ArrayList,可是Binder中仍是会造成一个ArrayList。相似的还有 ConcurrentHashMap 。
由于客户端的listener和服务端的listener不是同一个对象,因此 RecmoteCallbackList 是系统专门提供用于删除跨进程listener的接口,支持管理任意的AIDL接口。它内部经过一个Map接口来保存全部的AIDL回调,这个Map的key是 IBinder 类型,value是 Callback 类型。当客户端解除注册时,遍历服务端全部listener,找到和客户端listener具备相同Binder对象的服务端listenr并把它删掉。
客户端调用远程方法的时候线程会被挂起,因为被调用的方法运行在服务端的Binder线程池中 ,若是很耗时,就会让客户端一直等待甚至ANR,因此客户端不能在主线程中去调用服务端的方法,要开启一个子线程。同理,服务端调用客户端的listener的方法时,调用的方法运行在客户端的Binder线程池中,因此不能在这里作UI操做,除非切换到UI线程。
Binder可能会意外死亡,若意外中止,则重连服务。
方法一:给Binder设置DeathRecipient监听,Binder死亡,收到Binderdied回调,在binderDied中咱们能够重连远程服务。
方法二:onServiceDisconnected中进行重连操做。
权限验证 默认状况下,咱们的远程服务任何人均可以链接,咱们必须加入权限验证功能,权限验证失败则没法调用服务中的方法。一般有两种验证方法:
在onBind中验证,验证不经过返回null。 验证方式好比permission验证,在AndroidManifest声明Android自定义权限和使用权限。这种方法也适用于Messager。
在服务端的onTransact中验证,验证不经过返回false。能够permission验证,还能够验证包名。
2.4.5 使用ContentProvider
ContentProvider是四大组件之一,天生就是用来进程间通讯。和Messenger同样,其底层实现是用Binder 。
系统预置了许多ContentProvider,好比通信录、日程表等。要RPC访问这些信息,只须要经过ContentResolver的query、update、insert和delete方法便可。
建立自定义的ContentProvider,只需继承ContentProvider类并实现 onCreate 、 query 、 update 、 insert 、 getType 六个抽象方法便可。getType用来返回一个Uri请求所对应的MIME类型,剩下四个方法对应于CRUD操做。这六个方法都运行在ContentProvider进程中,除了 onCreate 由系统回调并运行在主线程里,其余五个方法都由外界调用并运行在Binder线程池中。
ContentProvider是经过Uri来区分外界要访问的数据集合,例如外界访问ContentProvider中的表,咱们须要为它们定义单独的Uri和Uri_Code。根据Uri_Code,咱们就知道要访问哪一个表了。
query、update、insert、delete四大方法存在多线程并发访问,所以方法内部要作好线程同步。
若采用SQLite而且只有一个SQLiteDatabase,SQLiteDatabase内部已经作了同步处理。如果多个SQLiteDatabase或是采用List做为底层数据集,就必须作线程同步。
2.4.6 使用Socket
Socket也称为“套接字”,分为流式套接字和用户数据报套接字两种,分别对应于TCP和UDP协议。Socket能够实现计算机网络中的两个进程间的通讯,固然也能够在本地实现进程间的通讯。。
在远程Service创建一个TCP服务,而后在主界面中链接TCP服务。服务端Service监听本地端口,客户端链接指定的端口,创建链接成功后,拿到 Socket 对象就能够向服务端发送消息或者接受服务端发送的消息。
除了采用TCP套接字,也能够用UDP套接字。实际上socket不只能实现进程间的通讯,还能够实现设备间的通讯(只要设备之间的IP地址互相可见)。
可是它的传输效率也是很是的低
2.5 Binder链接池
前面提到AIDL的流程是:首先建立一个service和AIDL接口,接着建立一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,客户端在Service的onBind方法中拿到这个类的对象,而后绑定这个service,创建链接后就能够经过这个Stub对象进行RPC。 那么若是项目庞大,有多个业务模块都须要使用AIDL进行IPC,随着AIDL数量的增长,咱们不能无限制地增长Service,咱们须要把全部AIDL放在同一个Service中去管理。 服务端只有一个Service,把全部AIDL放在一个Service中,不一样业务模块之间不能有耦合 服务端提供一个 queryBinder 接口,这个接口可以根据业务模块的特征来返回响应的Binder对象给客户端 不一样的业务模块拿到所需的Binder对象就能够进行RPC了
2.6 选用合适的IPC方式