首页 > 代码库 > Android IPC机制全解析<一>
Android IPC机制全解析<一>
概要
- 多进程概念及多进程常见注意事项
- IPC基础:Android序列化和Binder
- 跨进程常见的几种通信方式:Bundle通过Intent传递数据,文件共享,ContentProvider,基于Binder的AIDL和Messenger以及Socket。
- Binder连接池
- 各种进程间通信方式的优缺点及适用场景
IPC是 Inter-Process Communication的缩写,意为进程间通信或跨进程通信,是指两个进程之间进行数据交换的过程。
线程是CPU调度的最小单元,同时线程是一种有限的系统资源。进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。最简单的情况下,一个进程中只可以有一个线程,即主线程,在Android中也叫UI线程。
IPC不是Android中所独有的,任何一个操作系统都需要相应的IPC机制,比如Windows上可以通过剪贴板等来进行进程间通信。Android是一种基于Linux内核的移动操作系统,它的进程间通信方式并不能完全继承自Linux,它有自己的进程间通信方式。
1. Android中的多进程模式
通过给四大组件指定android:processs属性可以开启多进程模式,我们无法给一个线程或一个实体类指定其运行所在的进程。
<activity android:name=".SecondActivity" android:process=":remote"/> <activity android:name=".ThirdActivity" android:process="com.example.remote"/>
应用默认的进程是当前包名,以上分别给两个Activity指定了进程,意味着当前应用又增加了两个新进程。假设当前应用包名为com.ipc.example,那么SecondActivity所在的进程为com.ipc.example.remote,“:”的含义是要在当前进程名前面附上当前的包名,而且已“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一进程。不以“:”开头的进程是全局进程,如指定了ThirdActivity所在的进程为全局进程。
2. 多进程模式的运行机制
新建UserManager类:
public class UserManager {
public static int sUserId = 1;
}
在MainActivity中将sUserId的值修改为2,由于MainActivity在应用默认进程,SecondActivity在指定进程,在SecondActivity中打印sUserId的值会发现sUserId值并没有变,还是1。
出现这样的原因是SecondActivity运行在单独的进程,Android为每个应用分配了一个独立的虚拟机或者说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在内存上都有不同的地址空间,这就导致在不同的虚拟机中访问同一个对象会产生多分副本。上面示例中MainActivity修改了sUserId的值只会影响当前进程,对其他进程不会造成 任何影响。
所有运行在不同进程的四大组件,只要它们之间需要通过内存来共享数据都会失败。一般来说多进程会造成以下几个问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效(不在同一进程,,锁的不是同一对象,所以不管锁对象还是锁全局类都无法保证线程同步)
- SharePreferences的可靠性下降
- Application会多次创建(在Application的onCreate()中打印当前进程名证实了同一应用不同进程下,Application会多次创建。这也说明了多进程模式下,不同进程的组件的确会拥有独立的虚拟机、Application以及内存空间)
3. Serializable和Parcelable接口
Serializable和Parcelable可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要进行序列化,Serializable是Java提供的一个空接口,为对象提供标准的序列化和反序列化操作。使用简单但是序列化和反序列都会做大量的I/O读写操作,内存开销较大。
Parcelable是Android中的序列化方式,使用稍微复杂,但是效率高。
4. Android中的IPC方式:
4.1: 使用Bundle
四大组件中的其中三个(Activity、Service、Receiver)都是支持在Intent中传递Bundle数据,由于Bundle实现了Parcelable接口,所以它可以方便的在不同进程间传输。
4.2:使用文件共享
文件共享也是一种不错的进程间通信方式,两个进程通过读写同一个文件来交换数据,比如A进程中把数据写入文件,B进程通过读取这个文件来获取数据。但是这种方式也是又一定局限性的,比如并发读写的问题会导致我们读出的数据不是最新的,因此要避免并发读写的发生或者考虑使用线程同步来限制多个线程的读写操作。基于这样的问题,文件共享方式适合对数据同步要求不高的进程之间进行通信。
4.3:使用Messenger
Messenger是一种轻量级的IPC方案,它的底层实现是AIDL,通过Messenger可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据就可以轻松实现数据在进程间传递了。
Messenger的使用很简单,由于它一次处理一个请求,因此在服务端不用考虑线程同步的问题。Messenger实现进程间通信大致可以分为以下几步,分为服务端和客户端。
- 服务端进程
首先需要在服务端新建一个Service来处理客户端发起的请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。 - 客户端进程
客户端进程中首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送类型为Message的消息了。
注册Service,使其运行在单独的进程,AndroidManifest.xml中配置:
Log日志:
在Messenger中进行数据传递必须将数据放入Message中,而Messenger和Message都实现了Parcelable接口,因此可以跨进程传输。上面的实例只是介绍了如何在服务端接受客户端中发送的请求,但是有时候还需要回应客户端。每当客户端发来一条消息,服务端就自动回复一条“来自服务端的自动回复:消息已收到”。如果需要客服端能够回应客户端,那么和服务端一样,在客户端还需要创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。
修改后的服务端代码:
修改后的客户端代码:
4.4: 使用AIDL
Messenger是以串行的方式处理客户端的请求,如果有大量的请求同时发送到服务端,服务端仍然只能一个一个处理,此时Messenger就不大合适了,同时Messenger的作用是为了传递消息,且只能传递Bundle支持的数据类型,很多时候需要跨进程调用服务端的方法,这时候可以实用AIDL来实现跨进程的方法调用。AIDL也是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统作了封装。实用AIDL进行进程间通信的流程分为服务端和客户端两个方面,下面分别介绍:
- 服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可 - 客户端
客户端要做的事情稍微简单些,首先绑定服务端的Service,绑定成功后将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了 - AIDL接口的创建
详情参考:https://www.zhihu.com/question/21581761需要注意的是,如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明为Parcelable类型。上面代码中我们用到了BookEntity这个类,所以必须创建BookEntity.aidl,然后将BookEntity定义为Parcelable类型。
-
服务端的实现
运行在独立进程:
<service android:name=".service.BookManagerService" android:process="com.ipc.example.remote"/>
- 客户端实现
这是一次完整的使用AIDL实现进程间通信,但是远远还没有完,AIDL的复杂性远不止这些。
待续.....
Sample链接: https://github.com/NullUsera/IPC
Android IPC机制全解析<一>