在我们使用 Android 手机的时候,有时我们使用的软件会需要消耗比较大的内存,也经常会需要同时打开多个软件。这些时候,我们都会需要使用到多进程技术。作为 Android 开发者,相信我们都知道如何去开启应用的单个多进程,但是开启多进程之后,如何进行「进程间通信(IPC)」呢?进程通信的方法有很多,他们适用于不同的使用场景,下面我们来逐个了解。
前置知识
- Android 四大组件
- Java 序列化
IPC简介
相信学过大学「操作系统」这门课的同学都还记得 进程间通信
信号量机制
这些名词,今天我们学习的也是操作系统的通信,不过是针对以 Linux 为内核的 Android 操作系统。我们通常会以一个软件或者程序为进程。而 Android 是可以使用多进程的,对于稍微大型一些的软件也都会使用到多进程。使用多进程的目的有如下几个:
- 进程隔离,以达到某些特殊的业务需求
- 扩大软件的内存大小。
多进程的开启很简单,其唯一方法是给注册文件 AndroidManifest.xml
中的四大组件部分添加 android:process
属性即可
<activity
android:name="com.qxy.potatos.module.mine.activity.WebViewActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:exported="false"
android:process=":h5"
android:screenOrientation="portrait"
android:theme="@style/BlackTheme" />
上述的 android:process=":h5"
指的是该程序下的私有进程,是 com.qxy.potatos:h5
的简写。如果属性值换为全局形式的 com.qxy.potatos.h5
,则表示该进程是全局的,可共享给其他应用的。
而多进程出现后会导致如下的一些问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharePreferences 的可靠性下降
- Application 多次创建
上述问题为何会出现呢?
由于 Android 为每个独立进程都分配了一个虚拟机,那么虚拟机的内存空间必然是不同的,所以不同的量在不同内存中都有一份副本,在不同进程中只能修改其内存下的副本。所以1和2中,无论是加何种锁,不作用在同一片内存空间中都是失效的。
而3则是由于 SharePreferences 是存储在 xml 文件中的,不同进程对该文件的并发读写时会导致数据出错的
4中则是由于 Android 的启动机制是每次都要由启动新的 Application,则每个进程都会有一个自己的 Application。我们也需要着重注意这个问题,在Application 中做好启动分类,在多进程启动阶段,防止不需要的资源多次加载
基于上述的原因和问题,我们需要深入了解 IPC 机制,让跨进程通信更好的服务于我们,解决多进程所带来的问题。
IPC基础知识
序列化与反序列化
概述
在谈论序列化与反序列化问题之前,我们需要先了解他们是什么,且作用有哪些。
序列化的意思就是将对象转化为字节序列的过程
反序列化则是将字节序列恢复为对象的过程
那么将对象序列化为字节序列有什么用呢?
将对象序列化为字节序列,可以在传递和保存对象的时候,保证对象的完整性和可传递性。使其易于保存在本地或者在网络空间中传输。
而反序列化,可以将字节流中保存的对象重建为对象
所以,其最核心的作用就是,对象状态的保存和重建
序列化优点
- 序列化后的为字节流的对象,存储在硬盘中方便JVM重启调用
- 序列化后的二进制序列能够减少存储空间,方便永久性保存对象
- 序列化成二进制字节流的对象方便进行网络传输
- 序列化后的对象可以进行进程间通信
Android中的序列化手段
基于上述的讨论,我们知道了何为序列化以及序列化的作用和优点。这其中提到序列化的一大特性就是用于进程间通信,而在后续提到的进程间通信手段中,他们共同的点都是传递信息时将对象序列化,接收信息时则是将对象反序列化。
在Android中需要学习使用到的序列化手段有两个,分别是 Serializable
和 Parcelable
-
Serializable
Serializable
是 Java 自带的序列化接口,我们使用者只需要继承Serializable
接口即可实现对该对象的序列化了。而具体去调用对其序列化和反序列化过程的也是 Java 提供的API。ObjectOutputStream
可以实现对象的序列化,ObjectInputStream
实现对象的反序列化。//序列化 User user = new User(1, "hello world", false); ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("cache.txt")); objectOutputStream.writeObject(user); objectOutputStream.close(); //反序列化 ObjectInputStream objectInputStream = new ObjectInputStream( new FileInputStream("cache.txt")); User user = (User) objectInputStream.readObject();//会在此处进行检查,是否同一 serialVersionUID objectInputStream.close();
需要注意的是,被序列化的
User
类一般情况下需要指定一个serialVersionUID
,其作用是对该类做唯一标识,在反序列化时候会进行 serialVersionUID 的比对,如果不一致则会认为版本不同出现报错。但是,如果不指定该 ID 也是可以正常实现序列化和反序列化的,因为系统会自动生成该类的 hash 值赋给 serialVersionUID 。那么为什么我们还要建议手动复制呢?因为 hash 值是根据类的变化在变化的,如果 ID 是 hash 值的话,我们在序列化对象后更改了对象的结构就会导致前后 ID 不一致,使得该对象无法被反序列化。但是手动指定的 ID 可以让被更改过的对象依旧可以被反序列化,可以最大限度地恢复其内容。
public class User implements Serializable { private static final long serialVersionUID = 519067123721295773L;//静态成员不参与序列化过程,代表类的状态 public int userId; public String userName; public boolean isMale;