解密 Android IPC 机制

在我们使用 Android 手机的时候,有时我们使用的软件会需要消耗比较大的内存,也经常会需要同时打开多个软件。这些时候,我们都会需要使用到多进程技术。作为 Android 开发者,相信我们都知道如何去开启应用的单个多进程,但是开启多进程之后,如何进行「进程间通信(IPC)」呢?进程通信的方法有很多,他们适用于不同的使用场景,下面我们来逐个了解。

前置知识

  • Android 四大组件
  • Java 序列化

IPC简介

相信学过大学「操作系统」这门课的同学都还记得 进程间通信 信号量机制 这些名词,今天我们学习的也是操作系统的通信,不过是针对以 Linux 为内核的 Android 操作系统。我们通常会以一个软件或者程序为进程。而 Android 是可以使用多进程的,对于稍微大型一些的软件也都会使用到多进程。使用多进程的目的有如下几个:

  1. 进程隔离,以达到某些特殊的业务需求
  2. 扩大软件的内存大小。

多进程的开启很简单,其唯一方法是给注册文件 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,则表示该进程是全局的,可共享给其他应用的。

而多进程出现后会导致如下的一些问题:

  1. 静态成员和单例模式完全失效
  2. 线程同步机制完全失效
  3. SharePreferences 的可靠性下降
  4. Application 多次创建

上述问题为何会出现呢?

由于 Android 为每个独立进程都分配了一个虚拟机,那么虚拟机的内存空间必然是不同的,所以不同的量在不同内存中都有一份副本,在不同进程中只能修改其内存下的副本。所以1和2中,无论是加何种锁,不作用在同一片内存空间中都是失效的。

而3则是由于 SharePreferences 是存储在 xml 文件中的,不同进程对该文件的并发读写时会导致数据出错的

4中则是由于 Android 的启动机制是每次都要由启动新的 Application,则每个进程都会有一个自己的 Application。我们也需要着重注意这个问题,在Application 中做好启动分类,在多进程启动阶段,防止不需要的资源多次加载

基于上述的原因和问题,我们需要深入了解 IPC 机制,让跨进程通信更好的服务于我们,解决多进程所带来的问题。

IPC基础知识

序列化与反序列化

概述

在谈论序列化与反序列化问题之前,我们需要先了解他们是什么,且作用有哪些。

序列化的意思就是将对象转化为字节序列的过程

反序列化则是将字节序列恢复为对象的过程

那么将对象序列化为字节序列有什么用呢?

将对象序列化为字节序列,可以在传递和保存对象的时候,保证对象的完整性和可传递性。使其易于保存在本地或者在网络空间中传输。

而反序列化,可以将字节流中保存的对象重建为对象

所以,其最核心的作用就是,对象状态的保存和重建

序列化优点
  1. 序列化后的为字节流的对象,存储在硬盘中方便JVM重启调用
  2. 序列化后的二进制序列能够减少存储空间,方便永久性保存对象
  3. 序列化成二进制字节流的对象方便进行网络传输
  4. 序列化后的对象可以进行进程间通信
Android中的序列化手段

基于上述的讨论,我们知道了何为序列化以及序列化的作用和优点。这其中提到序列化的一大特性就是用于进程间通信,而在后续提到的进程间通信手段中,他们共同的点都是传递信息时将对象序列化,接收信息时则是将对象反序列化。

在Android中需要学习使用到的序列化手段有两个,分别是 SerializableParcelable

  • 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;
     
     

猜你喜欢

转载自blog.csdn.net/m0_70748458/article/details/130684176