android中进程间通信,简称IPC(Inter-Process-Communication),可以理解为android中两个进程之间进程数据交互的通信过程。那我们首选说下进程和线程的却别,即:线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在PC和移动设备中指一个程序或者一个应用。一个进程可以保持多个线程,它们是包含和被包含的关系。
IPC不是android中独有的,像Windows上可以通过剪贴板、邮槽来进行进程间通信;linux可以通过命名管道、共享内容、信号量来进行进程间通信。虽然android是基于linux的一款移动操作系统,但它的进程间通信并不完全继承自linux,在android中通过Binder可以轻松的实现进程间通信。
那什么情况下会用到IPC,肯定是在多进程的模式下需要用到,这里有两种模式,一是因为一个应用本身因某些原因自身需要采用多进程,比如有些模块需要单独运行在一个进程中,又或者一个应用为了加大可使用的内存通过多进程来获取多份内存空间;另一种情况是应用需要向其他应用获取数据,由于是两个应用,所以必须使用跨进程的方式来进行通信,像我们通过内容提供者ContentProvider去查询数据的时候,底层实现也是基于进程间通信来实现,只不过对我们应用层来发者屏蔽了,感知不到而与。
一个应用如何开启多进程模式呢,可以给四大组件(Activity、Service、Receiver、ContentProvider)在AndroidMenifest中指定android:process属性,除了可以用AS中的DDMS可以查看进行信息,还可以通过shell命令来查看,命令为:adb shell ps | grep com.kaishui.test。其中com.kaishui.test是包名。
多进程带来的问题:
如果在一个应用中自身采用了多进程模式,会面临一些问题:android中为每一个进程分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致不同的虚拟机中访问同一个类的对象会产生多份副本,在对应的进程中访问某个类,只会影响当前的进程,对其他进程不会造成任何影响。
1)静态成员和单例模式完全失效。(不是同一个对象)
2)线程同步机制完全失效。(锁对象和锁全局类不是同一个对象)
3)SharedPreferences的可靠性下降。(多进行并发读写,可能会造成数据丢失)
3)Application会多次创建。(一个进程,独立的虚拟机,就启动一次应用)
IPC基础概念介绍
当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable,需要对象序列化。还有的时候需要把对象持久化到存储设备上或者通过网络传输给其他客户端,需要使用Serializable来完成对象的持久化。
Serializable接口
Serializable是java提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程。
1 public class User implements Serializable{
2 private static final long serialVersionUID = 52155995445221852523L;
3 public int userId;
4 public String userName;
5 ....
6}
Parcelable接口
Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。
1public class User implements Parcelable{
2public int userId;
3public String userName;
4public boolean isMale;
5
6public Book book;
7
8public User() {
9}
10
11public User(int userId, String userName, boolean isMale) {
12 this.userId = userId;
13 this.userName = userName;
14 this.isMale = isMale;
15}
16
17public int describeContents() {
18 return 0;
19}
20
21public void writeToParcel(Parcel out, int flags) {
22 out.writeInt(userId);
23 out.writeString(userName);
24 out.writeInt(isMale ? 1 : 0);
25 out.writeParcelable(book, 0);
26}
27
28public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
29 public User createFromParcel(Parcel in) {
30 return new User(in);
31 }
32
33 public User[] newArray(int size) {
34 return new User[size];
35 }
36};
37
38private User(Parcel in) {
39 userId = in.readInt();
40 userName = in.readString();
41 isMale = in.readInt() == 1;
42 book = in
43 .readParcelable(Thread.currentThread().getContextClassLoader());
44}
45
46@Override
47public String toString() {
48 return String.format(
49 "User:{userId:%s, userName:%s, isMale:%s}, with child:{%s}",
50 userId, userName, isMale, book);
51}
可看到,序列化功能由writeToParcel方法来完成,并由read方法来完成反序列化过程。系统已经为我们提供了许多实现了Parcelable接口的类,都是可以直接序列化的,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的。
Serializeable是java中的序列化接口,使用简单但开销大,序列化和反序列化过程需要大量I/O操作。而Parcelable是android中的序列化,缺点是使用起来稍微麻烦点,但它的效率高,主要用在内存序列化上。
Binder
Binder,它实现了IBinder接口,从IPC角度来说,Binder是android中的一种跨进程通信方式,Binder还可理解为一种虚拟的物理设备,它的设备驱动是、dev/binder,该通信方式在Linux中没有;从Android FrameWork角度来说,Binder是ServiceManager连接各种Manger(ActivityManger、WindowManager,等)和相应MangerService的桥梁;从android应用层来说,BInder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的binder对象,通过这个binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。
android开发中,Binder主要用在service中,包括AIDL和Messnger,其中普通service中的Binder不涉及进程间通信,所以较为简单,无法处理Binder的核心,所以这里选择用AIDL来分析Binder的工作机制,详情如demo。
Android中的IPC方式
跨进程通信方式,具体方式有很多,使用Bundle、使用文件共享、使用Messenger、使用AIDL、使用ContentProvider、使用Socket。
使用Bundle
我们知道四大组件中的三大组件(Activity、Service、Receiver)都支持Intent中传递Bundle数据,且Bundle实现了Parcelable接口,可以在不同的进程间传输。例如我们在一个进程中启动了另一个进程的Activity、Service、Receiver,就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。
使用文件共享
共享文件也是一种不错的进程间通信方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据,当然并发读/写可能会出问题,这点需要妥善处理。
使用Messenger
Messenger可翻译为信使,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松的实现数据的进程间传递了。Messager是一种轻量级的IPC方案,它的底层实现是AIDL,通过它的构造方法就可以看出来,IMessager和Stub.asInterface,都表明它的底层是AIDL。
1public Messenger(IBinder target) {
2 mTarget = IMessenger.Stub.asInterface(target);
3}
具体的演示接下来会以demo的形式写出来。
使用AIDL
我们上面了解了Messenger来进行进程间通信的方法,可以发现,Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,么么用Messenger就不太合适。同时Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情况用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。具体的演示接下来也会以demo的形式写出来。
使用ContentProvider
ContentProvider是android中提供的专门用于不同应用间进行数据共享的方式,它天生就适合进程间通信。和Messenger一样,contentProvider的底层实现同样也是Binder,但它的使用过程要比AIDL简单许多,这是因为系统已经为我们做了封装使得我们无需关心底层细节即可轻松实现IPC,接下来也会结合具体的代码进行演示。
使用Socket
通过Socket来实现进程间的通信,其中Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字,分别对应网络传输控制层中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能,TCP连接的建立需要经过“三次握手”才能完成,提供超时重传机制,具有很高的稳定性;而UDP是无连接的,提供不稳定的单向通信,在性能上,UDP有更好的性能,缺点是不保证数据一定正确传输,尤其在网络拥塞的情况下,接下来也会通过具体的demo来实践。
参考:《android开发艺术探索》书籍