0%

第2章——IPC机制

提到IPC的使用场景就必须提到多进程,如果只有一个进程,又何必使用进程间通信。多进程的情况分为两种:第一种是应用本身需要采用多进程模式实现(比如通过多个进程来获取多份内存空间);第二种是当前应用需要向其他应用获取数据,由于是两个应用,因此必须采用跨进程通信方式。

Android中的多进程模式

我们不讨论两个应用之间的多进程情况。

1、开启多进程

,Android中使用多进程有两种方法:第一种是给四大组件在AndroidManifest中指定 android:process 属性;第二种是非常规方式,通过JNI在native层去fork一个新的进程。第二种情况属于特殊情况,暂时不考虑。

2、多线程模式的运行机制

如果用一句话形容多进程,那就是:“当应用开启了多进程以后,各种奇怪的现象都出现了”,开启多线程只需要给四大组件指定 android:process 属性,但是是否正常运行就是另外一回事了。看个例子:

有 MainActivity 和 SecondActivity,其中 SecondActivity 指定运行在一个新的进程中,并且项目还新建一个 UserManager 类,类中有个public 的静态变量:

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

在 MainActivity 的 onCreate 中把 sUserId 的值改为2,打印sUserId,之后再启动 SecondActivity ,在 SecondActivity 中打印 sUserId 。

可以发现在 MainActivity 中打印的值是2,在 SecondActivity 中打印的值是 1 ,看到这里,大家应该明白了多进程带来的问题,绝非只是指定一个 android:process 这么简单

分析:我们知道,Android 系统为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本。拿上面的例子来说,两个进程中都存在一个 UserManager 类,并且这两个类是互相不干扰的,在一个进程中修改 sUserId 的值只会影响当前进程,对其他进程不会造成任何影响。

通过以上可以知道,运行在不同进程中的四大组件,只要他们之间需要通过内存来共享数据,都会共享失败,这也是多进程所带来的主要影响。一般来说,使用多进程会造成如下几个方面的问题:

  • 静态成员和单例模式完全失效

上面做了分析

  • 线程同步机制完全失效

既然都不是一块内存了,那么不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象

  • SharedPreference 的可靠性下降

是因为 SharedPreference 不支持两个进程同时去执行写操作,否则会导致可能的数据丢失(因其本质是通过读写xml文件来实现的)

  • Application 会多次创建

这个问题是显而易见的,由于系统要在创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程,因此相当于系统又把应用重新启动了一遍,自然就创建了新的Application。还可以这么理解,运行在同一个进程中的组件是属于同一个虚拟机和同一个Application的;同理,运行在不同进程中的组件是属于两个不同的虚拟机和Application。

IPC 基础概念介绍

IPC 中的基础概念包括3方面内容: Serializable 接口、Parcelable接口、Binder。

1、Serializable 接口

Serializable 是Java提供的一个空的序列化接口,为对象提标准的序列化和反序列化操作。使用 Serializable 实现序列化非常简单,只需要类实现 Serializable 接口,并且在类的声明中指定一个类似下面的标识:

private static final long serialVersionUID = 12345L

实际上,这个 serialVersionUID 也不是必需的,因为serialVersionUID 的机制是这样的: 序列化时,系统会把当前类的 serialVersionUID 写入序列化的文件中;当反序列化的时候,会去检测文件中的 serialVersionUID 是否和当前类的 serialVersionUID 一致,如果一致说明序列化的类版本和当前类的版本是相同的,就可以成功反序列化;否则的话,说明当前类和序列化的类相比发生了某些变换,就无法正常反序列化。以下例子说明 Serializable 的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//序列化的类
public class User implements Serializable{
private static final long serialVersionUID = 12345L;
public int id;
public String userName;
}


/**********使用*****************/

//序列化
User user = new User(12,"tom");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
out.writeObject(user);
out.close();

//反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cache.txt"));
User newUser = (User)in.readObject();
in.close();

另外,系统默认的序列化过程也是可以改变的,通过重写 writeObject 和 readObject 方法即可,只不过大部分情况下我们无需去重写这两个方法。

2、Parcelable 接口

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

具体使用方法可以查看官方文档

既然 Parcelable 和 Serializable 都能实现序列化并且都可用于 Intent 间的数据传递,那如何取舍呢?Serializable 是Java中的序列化接口,序列化和反序列化需要大量I/O操作;而Parcelable 是 Android 中的序列化方式,主要用在内存序列化上,使用起来稍显麻烦,但是效率高,所以这是 Android 官方推荐的序列化方式。综上所述,将对象序列化存储到设备或者通过网络传输时使用 Serializable ,否则使用 Parcelable 。

3、Binder

Binder 是 Android 中的一种 IPC 方式,还可以理解为一种虚拟的物理设备,它的设备驱动是 dev/binder。

Android 中的 IPC 方式

使用Bundle

由于Bundle 实现了 Parcelable 接口,所以它可以方便地再不同的进程间传输,基于这一点,当我们在一个进程中启动了另一个进程的 Activity、Service 和 Receiver,我们就可以在Bundle 中附加我们需要传给其他进程的信息,并通过Intent 发送出去,这是一种最简单的进程间通信方式。

使用文件共享

共享文件是一种不错的进程间通讯方式,适合在对数据同步要求不高的进程间通信。当然,SharedPreferences 是个特例,由于系统对它的读写会有一定的缓存策略,即在内存中会有一份 SharedPreferences 文件的缓存,因此在多进程模式下,面对高并发的的读写会有很大几率丢失数据,因此不建议在进程间通信中使用 SharedPreferences。

使用 Messenger

顾名思义可以翻译成信使,通过它可以在不同的进程中传递 Message 对象,它是轻量级的 IPC 方案,底层实现是 AIDL 。Messenger 只是一串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理。具体可以参考官方文档

使用 AIDL

由于 Messenger 服务端只能串行处理,所以可以使用 AIDL 来实现跨进程调用。具体内容可以参考官方文档

使用 ContentProvider

这是Android 中提供的专门用于不同应用建进行数据共享的方式。

使用 Socket

Socket 是网络通信中的概念,也称为“套接字”,它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和UDP协议

选择合适的 IPC 方式

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输Bundle支持的数据类型 四大组件之间的通信
文件共享 简单易用 不适合高并发 无并发,数据实时性要求不高
AIDL 功能强大,支持一对多,支持实时 使用复杂 一对多通信且有RPC需求
Messenger 一对多串行通信,支持实时 高并发困难,不支持RPC,只能传输Bundle支持的数据 低并发的一对多通信
ContentProvider 数据源访问功能强大 理解为受约束的AIDL,主要提供数据源的 CRUD 操作 一对多的进程间数据共享
Socket 支持一对多并发实时通信,支持字节流 实现繁琐 网络数据交换
谢谢你的鼓励