Android多进程机制(一)IPC基础概念

2018-06-10  本文已影响28人  Utte

Android IPC 基础概念

多进程应用场景

  1. 应用自身需要采用多进程模式来实现。
//获取应用限制的内存大小和进程限制的内存大小
//我设备测试的是 heapGrowthLimit: 256m ,heapSize: 512m
ActivityManager activityManager =(ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int heapGrowthLimit = activityManager.getMemoryClass();// 单个应用可用最大内存
int heapSize = activityManager.getLargeMemoryClass();//单个进程可用的最大内存
Log.d(TAG, "heapGrowthLimit: " + heapGrowthLimit + "\nheapSize: " + heapSize);
  1. 需要向其他应用获取数据,也就是跨进程访问数据。

如何跑在同一进程

  1. 两个应用能跑在同一进程的前提条件是具有相同的ShareUID和签名。
  2. 具有相同ShareUID并且签名相同的两个应用,可以共享对方私有数据,比如data目录、组件信息等。
  3. 如果ShareUID相等、签名相同且跑在同一进程中,那么除了能共享data目录、组件信息等,还能共享内存数据。

开启多进程的方式

使一个应用开启多进程模式,有以下两种方式:

  1. AndroidMenifest中指定android:process属性。
  2. 通过JNI在native层去fork一个新进程。

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

android:process=":jtt"
android:process="com.utte.test.jtt"

两种方式还是有区别的:

多进程运行机制

Android为每个应用也就是说为每个进程都分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,就导致在不同的虚拟机中访问同一个类的对象时会产生多份副本。

所有运行在不同进程中的四大组件都不能通过共享内存来共享数据,所以会造成以下影响:

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

会分配不同虚拟机,所以会产生多个对象副本。

  1. 线程同步机制完全失效

不同进程中的对象不是同一个对象,所以加锁达不到效果。

  1. SharePreferences的可靠性降低

SharePreferences不支持两个进程同时进行写操作,会导致一定几率数据丢失,SharePreferences底层是通过读写XML来实现的,并且内存中会有缓存。

  1. Application会多次创建

当一个组件跑在新进程中时,系统会在创建新进程时同时分配独立的虚拟机,这个过程是一个应用启动的过程,相当于把应用重新启动了一次,自然Application也会重新创建。

运行在两个进程中的两个组件会拥有独立的虚拟机、独立的Application、独立的内存空间。

它们也就相当于是两个应用拥有相同的ShareUID和签名,但不跑在同一进程中,它们可以访问对方数据,但是不能通过共享内存的方式。虽然不能共享内存了,但是还有很多方式可以实现跨进程的数据交互。

多进程通信的主要方式

Bundle、文件共享、Socket比较简单,也基本都使用过,其他的都与Binder有关,包括ContentProvider,它的底层其实也是Binder。

序列化

我们传输的数据必须能够被序列化,比如基本类型、实现了Parcelable接口的对象、实现Serializable接口的对象以及Android支持的一些特殊对象。

Serializable接口

通过Serializable使一个对象实现序列化:

  1. 类实现Serializable接口
  2. 声明serialVersionUID (非必需)

类定义:

public class Book implements Serializable {
    private static final long serialVersionUID = 199898L;
    \\...
}

序列化使用ObjectOutputStream.writeObject(),反序列化使用ObjectInputStream.read()。

序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才可以被正常的反序列化。如果不手动指定serialVersionUID,系统就会根据类信息计算hash值赋值给serialVersionUID。这样的话如果在序列化后对类进行了一些更改,在没有指定serialVersionUID的情况下,会反序列化失败报异常,如果指定了,在非毁灭性改变的前提下,程序会尽可能最大限度的恢复数据。

Parcelable接口

更详细的内容可以看这篇博客,Android中Serializable和Parcelable序列化对象详解

Parcelable的用法如下,需要实现Parcelable序列化、反序列化、描述三个逻辑。

public class User implements Parcelable {

    private boolean isMale;
    private Book mBook; //Serializable对象
    private Bag mBag;   //Parcelable对象

    public User(boolean isMale, Book book, Bag bag) {
        this.isMale = isMale;
        mBook = book;
        mBag = bag;
    }

    //描述
    @Override
    public int describeContents() {
        return 0;
    }
    
    //序列化
    @Override
    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(isMale ? 1 : 0);
        out.writeSerializable(mBook);
        out.writeParcelable(mBag, 0);
    }

    //反序列化
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
    
    //给反序列化创建对象调用
    private User(Parcel in) {
        isMale = in.readInt() == 1;
        mBook = (Book) in.readSerializable();
        mBag = in.readParcelable(Bag.class.getClassLoader());
    }
    
}

获取到文件描述符可以完成所有文件相关的操作,因为作用大,所以为了防止泄露,需要禁止在Bundle传输Parcel时包含文件描述符,如果通过Parcel中包含ParcelFileDescriptor的Bundle使用时就会抛出IllegalArgumentException。这个值是在系统内部进行安全保护所使用的,其他情况下填0即可。

Serializable和Parcelable比较

Serializable Parcelable
使用比较简单 使用稍微麻烦
IO流形操作,性能较低 基于内存操作,效率高

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

上一篇下一篇

猜你喜欢

热点阅读