美文网首页
Android多进程机制(一)IPC基础概念

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

作者: Utte | 来源:发表于2018-06-10 16:58 被阅读28次

    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"
    

    两种方式还是有区别的:

    • 第一种分号的意思是进程名前面加上当前包名,进程名为"com.utte.test:jtt"。
    • 第二种的进程名直接就是属性值,进程名为"com.utte.test.jtt"。
    • 第一种属于当前应用的私有进程,其他应用的组件不能与其跑在同一进程中。
    • 第二种为全局进程,其他应用可通过ShareUID的方式与其跑在同一进程中。

    多进程运行机制

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

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

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

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

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

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

    1. SharePreferences的可靠性降低

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

    1. Application会多次创建

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

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

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

    多进程通信的主要方式

    • Binder
    • Bundle
    • 文件共享
    • AIDL
    • Messenger
    • ContentProvider
    • Socket

    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());
        }
        
    }
    
    
    • Parcel为内部包装了可序列化的数据。
    • writeToParcel()的flag如果为1就表示当前对象需要作为返回值返回,不能立即释放,0表示不需要,几乎所有情况都是0。
    • describeContents()返回0表示不含文件描述符,存在文件描述符时返回1,几乎所有时候都应该返回0。

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

    Serializable和Parcelable比较

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

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

    相关文章

      网友评论

          本文标题:Android多进程机制(一)IPC基础概念

          本文链接:https://www.haomeiwen.com/subject/kdxqeftx.html