Android IPC机制(一)IPC基础概念

IPC简介

IPC的含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程

多进程的情况分为两种:
1. 一个应用因为某些原因自身需要采用多进程模式来实现
2. 当前应用需要向其他应用获取数据

Android中的多进程模式

开启多进程模式

正常情况下,在Android中多进程是指一个应用中存在多个进程的情况。

在Android中使用多进程的方法:

  1. 给四大组件在AndroidMenifest中指定android:process属性(我们无法给一个线程或一个实体类指定其运行时所在的进程)
  2. 非常规方法:通过JNI在native层去fork一个新的进程

暂时先看第一种,AndroidMenifest中没有给某个组件指定process属性时默认运行在默认进程中,默认进程的名字为程序包名。如果要指定,有两种方式。

android:process=":remote"
android:process="com.ryg.chapter_2.remote"

两种方式的区别:

  • 第一种 ":"的含义是在当前进程名前附加上当前的包名,这是一种简写的方法,完整进程名为:com.ryg.chapter_2:remote
  • 第二种是一种完整的命名方式,不会附加包名信息
  • 第一种属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中
  • 第二种属于全局进程,其他应用可以通过ShareUID的方式和它跑在同一个进程中

注:Android系统会为每一个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。
两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用具有相同的ShareUID并且签名相同才可以(它们看起来就像一个应用的两个部分)
具有相同ShareUID并且签名相同的两个应用,可以共享对方私有数据,比如data目录、组件信息等。
如果ShareUID相同、签名相同且跑在同一进程中,那么除了能共享data目录、组件信息等,还能共享内存数据。

多进程模式的运行机制

我们新建一个类UserManager,类中有一个public的静态成员变量(静态变量是可以在所有的地方共享的,并且一处有修改处处都会同步)

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

在MainActivity的onCreate中我们把这个sUserId重新赋值为2,打印这个静态变量值后再启动SecondActivity,在SecondActivity中再打印一下sUserId的值。运行结果却是SecondActivity中打印的sUserId的值还是1。

问题出现的原因

Android为每一个应用(或每个进程)分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致在不同的虚拟机中访问同一个类的对象会产生多份副本。 拿本例来说,在进程com.ryg.chapter_2和com.ryg.chapter_2:remote中都存在一个UserManager类,这两个类是互不干扰的,在一个进程中修改sUserId的值只会影响当前进程,对其他进程不会造成任何影响。

多进程带来的主要影响:所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败(正常情况下通过一些中间层来共享数据)

多进程会造成的问题

  • 静态成员和单例模式完全失效
  • 线程同步机制完全失效(不管是锁对象还是锁全局类都无法保证线程同步,因为不同进程锁的不是同一个对象)
  • SharedPreferences的可靠性下降(不支持两个进程同时执行写操作,否则会导致一定几率的数据丢失。底层是通过读/写XML文件实现,并发写可能会出问题)
  • Application会多次创建
    当一个组件跑在一个新的进程中的时候,由于系统要在创建新的进程同时分配独立的虚拟机,所以这个过程其实就是启动一个应用的过程。相当于系统把这个应用重新启动了一遍,运行在不同进程中的组件是属于两个不同的虚拟机和Application的。如果连续启动三个同一个应用内但属于不同进程的Activity,Application的onCreate应该执行三次并打印出三次进程名不同的log。这就证实了不同进程的组件的确会拥有独立的虚拟机、Application以及内存空间,它就相当于两个不同的应用采用了SharedUID的模式。

IPC基础概念

主要包含三方面内容:Serializable接口、Parcelable接口以及Binder。当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输(以字节流的方式对数据进行传输)给其他客户端,也需要Serializable来完成对象的持久化。

序列化

我们传输的数据如果是基本数据类型直接进行相关传递即可,但是数据类型比较复杂的时候就需要进行序列化。
它的目的:

  1. 永久地保存对象数据,保存对象的字节序列到本地文件中
  2. 通过序列化对象在网络中传递对象
  3. 通过序列化在进程间传递对象

Serializable接口

java中的序列化接口

使用Serializable实现序列化:

  1. 这个类实现Serializable接口
  2. 声明一个serialVersionUID(不是必需的,不声明会对反序列化过程产生影响)
private static final long serialVersionUID = 519067123721295773L;
    //序列化过程
    User user = new User(0, "jake", true);
    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();

serialVersionUID

工作机制

序列化的时候系统把当前类的serialVersionUID写入序列化的文件中(也有可能是其他中介),当反序列化的时候就回去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这时候就可以成功反序列化;否则说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量、类型可能发生了改变,这个时候是无法正常反序列化的(报java.io.InvalidClassException)。

赋值的两种方法

一般来说,应该手动指定serialVersionUID的值,比如1L,也可以让系统根据当前类的结构自动生成它的hash值(本质一样)。
如果不手动指定serialVersionUID的值,反序列化时当前类有所改变,比如增加或删除了某些成员变量,系统会重新计算当前类的hash值并把它赋值给serialVersionUID,这时候类的serialVersionUID就和序列化数据中的serialVersionUID不一致,于是反序列化失败,程序就会出现crash,所以手动指定serialVersionUID可以很大程度避免反序列化过程的失败。如果类结构有了毁灭性的改变(修改类名,修改成员变量的类型等),尽管serialVersionUID验证通过,反序列化过程还是会失败。

注:静态成员变量属于类不属于对象,不会参与序列化过程;用transient关键字标记的成员变量不参与序列化过程

Parcelable接口

Android中的序列化方式

//不仅仅需要声明,还需要实现内部的相应方法
public class User implements Parcelable {
    public int userId;
    public String userName;
    //另一个可序列化对象
    public Book book;

    public User(int userId, String userName) {
        this.userId = userId;
        this.userName = userName;
    }

    /**
     * 内容描述功能
     * 仅当当前对象中存在文件描述符时,此方法返回1
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 将当前对象写入序列化结构中
     * @param out
     * @param flags 为1时标识当前对象需要作为返回值返回,不能立即释放资源,几乎所有情况都为0
     */
    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(userId);
        out.writeString(userName);
        //writeParcelable()里又调用了 writeToParcel()
        out.writeParcelable(book, 0);
    }

    //反序列化
    public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
        /**
         * 从序列化后的对象中创建原始对象
         * 其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。
         * @param in
         * @return
         */
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        /**
         * 创建原始对象数组
         * @param size
         * @return
         */
        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    private User(Parcel in) {
        //如果元素数据类型是list时, list = new ArrayList<?>(); in.readList(list, getClass().getClassLoader(); 
        
        userId = in.readInt();
        userName = in.readString();
        //反序列化过程需要传递当前线程的上下文类加载器,否则会报无法找到类的错误
        book = in.readParcelable(Thread.currentThread().getContextClassLoader());
    }
}

注:写入数据的顺序和读出数据的顺序必须是相同的.
未提供writeBoolean方法 1为true 0为false

可以直接序列化(实现了Parcelable接口的类)的比如Intent、Bundle、Bitmap等,同时List和Map也可序列化,前提是它们里面的每个元素都是可序列化的。

Serializable和Parcelable比较

SerializableParcelable
java的序列化的接口,使用简单Android中的序列化方式,使用麻烦
开销大,序列化和反序列话的时候需要大量的I/O操作基于内存操作,效率高

Parcelable主要用于内存序列化。Serializable主要用于序列化到存储设备或网络传输。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值