文章目录

  • 一、Android中的多进程模式
  • 1. 多进程的情况
  • 2. 开启多进程模式
  • 3. 多进程模式的运行机制
  • 二、IPC基础概念
  • 1. Serializable接口
  • 2. Parcelable接口
  • 3. Serializable 和 Parcelable 区别
  • 4. Binder
  • 三、Android中的IPC方式
  • 1. 使用Bundle
  • 2. 使用文件共享
  • 3. 使用Messager
  • 4. 使用AIDL
  • 5. 使用ContentProvider
  • 6. 使用Socket
  • 7. 六种方式对比
  • 四、Binder连接池



IPC 是 Inter-Process Communication 的缩写,含义为进程间通信或跨进程通信。任何一个操作系统都有IPC机制,对于Android来说,Binder是其特色的进程间通信方式。

一、Android中的多进程模式

1. 多进程的情况
  • 一个应用因为某些原因自身需要采用多进程模式来实现,比如某些模块可能需要运行在单独的进程中,或者需要通过多进程加大内存
  • 当前应用需要向其他应用获取数据
2. 开启多进程模式

在Android中,多进程只有一种方法,就是给自大组件在AndroidManifest中指定 android:process 属性(通过JNI在native层去fork一个新的进程暂不考虑)

<activity
	android:process=":remote"
	android:process="com.test.remote"/>

假设包名为com.test

  • 第一种方式 “:” ,进程属于当前应用私有进程,其他应用的组件不可以和它跑在同一个进程中,最终进程名为"come.test:remote",要在当前进程名前面附加上当前包名
  • 第二种方式属于全局进程,是完整的进程名,其他应用通过ShareUID可以和它跑在同一个进程中
  • 没有指定此属性的在默认进程中,进程名为包名
3. 多进程模式的运行机制

Android为每一个应用分配了一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,所有运行在不同进程中的四大组件,只要它们之间通过内存来共享数据,都会共享失败

使用多进程会造成:

  • 静态成员和单例模式完全失效
  • 线程同步机制完全失效 (不同进程锁的不是同一个对象)
  • SharedPreference 的可靠性下降 (SP不支持两个进程同时执行写操作,会导致一定几率的数据丢失,其底层是读写XML文件,并发写有问题)
  • Application 会多次创建 (不同进程的组件拥有独立的虚拟机、Application以及内存空间,运行在同一个进程中的组件属于同一个虚拟机和同一个Application,运行在不同进程中的组件属于两个不同的虚拟机和Application)

二、IPC基础概念

1. Serializable接口

Serializable是Java提供的一个序列化接口,是一个空接口,通过Serializable实现对象序列化,只需要实现Serializable接口并声明一个serialVersionUID(可选)

serialVersionUID作用

序列化的时候系统会把当前类的serialVersionUID写入序列化文件(或其他中介),反序列化时系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,无法正常反序列化

如果不手动指定serialVersionUID值,系统会根据自动计算当前类的hash值并赋给serialVersionUID,这样当类有所改变时(比如删除了某个成员变量),其serialVersionUID也会被系统自动重新计算赋值,导致反序列化失败;手动指定serialVersionUID的值,可以在当前类发生某些变化后,仍最大限度恢复数据;注意,如果类的结构发生了改变(比如改变类名),尽管serialVersionUID验证通过,反序列化还是会失败

不参与序列化的值

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

demo

一个实现了序列化的类

public class User implements Serializable{

    private static final long serialVersionUID = 123456789043215647L;

    public int userId;
    public String userName;
    public boolean isMale;

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

序列化与反序列化

private void serialize() {
        User user = new User(0,"jake",true);
        try {
            ObjectOutputStream out = new ObjectOutputStream(
                    new FileOutputStream(context.getFilesDir().getPath().toString() + "/serialize.txt"));
            out.writeObject(user);
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
private void deserialize() {
        try {
            ObjectInputStream in = new ObjectInputStream(
                    new FileInputStream(context.getFilesDir().getPath().toString() + "/serialize.txt"));
            User newUser = (User)in.readObject();
            Log.e("wyc",newUser.toString());
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

注:从本地存储中恢复的newUser和user的内容完全一样,但两者并不是同一个对象

2. Parcelable接口

系统已经为我们提供了许多实现了Parcelable接口的类,比如Intent、Bundle、Bitmap等,同时List和Map也可以序列化,前提是它们里面的每个元素都是可序列化的

自定时实现:

public class UserParcel implements Parcelable {

    public int userId;
    public String userName;
    public boolean isMale;

    public Book book;

    public UserParcel(int userId, String userName, boolean isMale) {
        this.userId = userId;
        this.userName = userName;
        this.isMale = isMale;
    }


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(userId);
        parcel.writeString(userName);
        parcel.writeInt(isMale ? 1 : 0);
        parcel.writeParcelable(book, 0);
    }

    public static final Parcelable.Creator<UserParcel> CREATOR = new Parcelable.Creator<UserParcel>() {

        @Override
        public UserParcel createFromParcel(Parcel parcel) {
            return new UserParcel(parcel);
        }

        @Override
        public UserParcel[] newArray(int i) {
            return new UserParcel[i];
        }
    };

    private UserParcel(Parcel parcel) {
        userId = parcel.readInt();
        userName = parcel.readString();
        isMale = parcel.readInt() == 1;
        book = parcel.readParcelable(Thread.currentThread().getContextClassLoader());
    }
}

Parcel 内部包装了可序列化的数据,可以再Binder中自由传输。

Parcelable实现序列化:

  • writeToParcel:实现序列化
  • CREATOR:反序列化,内部标明了创建序列化对象和数组
  • describeContents:内容描述,默认返回0,仅当对象中存在文件描述符,返回1

注意:User中book是另一个可序列化对象,所以它的反序列化过程需要传递当前线程的上下文类加载器

3. Serializable 和 Parcelable 区别
  • Serializable是Java中的序列化接口,使用起来开销很大,序列化和反序列化都需要大量的I/O操作。
  • Parcelable是Android中的序列化方法,更适用于Android平台,缺点是使用稍麻烦,但是效率很高,Android推荐。
  • Parcelable主要用于内存序列化,通过Parcelable将对象序列化到存储设备或者进行网络传输会比较复杂,这两种情况建议使用Serializable。
4. Binder

Binder是Android中的一个类,实现了IBinder接口;从IPC角度,Binder是Android中的一种跨进程通信方式,BInder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder;从 Android Framework 角度来说,Binder是ServiceManager 连接各种Manager和相关ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介。Binder主要用在Service中,包括AIDL和Messager。

AIDL Android接口定义语言

作用:方便系统为我们生成代码从而实现跨进程通讯

以一个AIDL(Android接口定义语言)demo分析Binder的工作机制(也可以自己写,使用AIDL主要是为了方便系统为我们生成代码):

完整代码参见:AIDL-DEMO

1)新建一个Book.java类 实现了Parcelable接口,是一个表示图书信息的类

package com.wyc.cpt2.aidl;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {

    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(bookId);
        parcel.writeString(bookName);
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel parcel) {
            return new Book(parcel);
        }

        @Override
        public Book[] newArray(int i) {
            return new Book[i];
        }
    };

    private Book(Parcel parcel) {
        bookId = parcel.readInt();
        bookName = parcel.readString();
    }
    
    @Override
    public String toString() {
        return String.format("[bookId:%s, bookName:%s]", bookId, bookName);
    }
}

2)新建Book.aidl,是Book类在AIDL中的声明

// Book.aidl
package com.wyc.cpt2.aidl;
parcelable Book;

3)新建IBookManager.aidl 是我们定义的一个接口,里面有两个方法,getBookList用于从远程服务端获取图书列表,addBook用于往图书列表中添加一本书

// IBookManager.aidl
package com.wyc.cpt2.aidl;

import com.wyc.cpt2.aidl.Book;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

注意:尽管Book类和IBookManager在同一个包,在IBookManager中仍要导入Book类

再一个,aidl包是和java包平级的,在src->main下,其他位置无效,这个右键生成AIDL文件,自动就会生成这个文件夹并将生成的.aidl文件放在其下相应目录中了

android ipc binder限制大小 android ipc机制_ipc

然后学习系统为IBookManager生成的Binder类,在如图位置:

android ipc binder限制大小 android ipc机制_ide_02

先大概了解一下Binder机制流程:

android ipc binder限制大小 android ipc机制_ipc_03

看下一这个类:

android ipc binder限制大小 android ipc机制_ipc_04

  • 这个类继承了IInterface接口,同时自己也是一个接口,所有可以再Binder中传输的接口都需要继承IInterface;
  • DESCRIPTOR 是Binder的唯一标识
  • 声明了一个内部类Stub,这个是一个Binder类

android ipc binder限制大小 android ipc机制_ide_05

  • 声明了两个方法 getBookList 和 addBook,同时声明了两个 id用于标识这两个方法

android ipc binder限制大小 android ipc机制_序列化_06

  • asInterface:用于将服务端的Binder对象转换成客户端需要的AIDL接口类型的对象,如果客户端和服务端位于同一个进程,那么返回服务端service对象本身,否则返回系统封装后的Stub.proxy代理对象
  • asBinder:返回当前的Binder对象

android ipc binder限制大小 android ipc机制_android_07

  • onTransact:这个方法运行在服务端中的BInder线程池中,客户端发起跨进程请求,远程请求会通过系统底层封装后交由此方法处理。首先通过code确定请求目标,接着从data中取出参数,然后执行

android ipc binder限制大小 android ipc机制_ipc_08

  • Proxy内部类#getBookList#addBook:这两个方法运行在客户端,当客户端调用此方法时,首先创建输入型对象 _data,输出型对象 _reply,返回值对象 _result,接着把参数写入 _data,再调transact发起RPC(远程过程调用)请求,同时当前线程挂起,服务端的onTransact会被调用,直到RPC过程返回,当前线程继续执行,并从 _reply 中取出返回结果

注意

  • 客户端发起远程请求时,当前线程会被挂起直至服务端进程返回数据,所以远程方法如果是耗时的,不能再UI进程中发起请求
  • 服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方法去实现(这个还不太理解)

三、Android中的IPC方式

1. 使用Bundle

Activity、Srevice、Receiver都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便的在不同进程间传输

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

2. 使用文件共享

两个进程通过读写同一个文件来交换数据,对文件格式没有要求,注意并发操作

SharePreference也是文件的一种,底层上采用XML文件存储键值对,但是系统对它的读写操作有一定的缓存策略,即内存中会有缓存,因此多进程模式下读写就不可靠

3. 使用Messager

Messager是一种轻量级的IPC方案,底层实现是AIDL

在不同进程中传递Message对象,在Message中放入我们要传递的数据

一次只处理一个请求,服务端不用考虑线程同步问题

实现Messager:

1)服务端进程创建一个Service来处理客户端请求,同时创建一个Handler对象,并通过它来创建一个Messager对象,然后再Service的onBind中返回这个Messager对象底层的Binder

2)客户端进程绑定服务端Service,用服务端返回的IBinder对象创建Messager,通过这个Messager就可以向服务端发消息了,消息类型为Message对象

3)如果需要服务端回应,客户端也需要创建一个Handler和Messager,并把这个Messager通过Message的replyTo参数传递给服务器

4)服务器通过这个replyTo参数就可以回应客户端

注意

Message中能使用的载体只有what、arg1、arg2、Bundle以及replyTo,object对象2.2以前不支持跨进程传输,2.2以后也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输

4. 使用AIDL

1)服务端创建一个Service和一个AIDL接口,AIDL中声明暴露给客户端的接口,Service中shixian这个接口

2)客户端绑定服务,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了

AIDL支持的数据类型:

  • 基本数据类型
  • String 和 CharSequence
  • List:ArrayList且里面的每个元素都被AIDL支持
  • Map:HashMap且里面的每个元素都被AIDL支持
  • Parcelable接口的实现对象
  • AIDL 接口本身

注意:

  • 自定义Parcelable对象和AIDL对象必须显示import
  • AIDL中用到的自定义Parcelable对象,必须新建一个同名的.aidl文件
  • AIDL中除了基本数据类型,其他类型必须标注方向:in/out/inout
  • AIDL的包结构在服务端和客户端要保持一致

demo同Binder机制分析时的demo,还有RemoteCallbackList未添加,使用AIDL做权限验证未学习

5. 使用ContentProvider

ContentProvider底层实现同样也是Binder

自定义ContentProvider只需要继承ContentProvider类并实现六个方法:onCreate、query、update、insert、delete和getType;其中onCreate由系统回调并运行在主线程,其他五个方法由外界回调并运行在Binder线程池中

ContentProvider对底层的数据存储方式没有任何要求,可以使用SQLite数据库,也可以使用普通的文件等

要观察一个ContentProvider中的数据改变情况,可通过ContentProvider的registerContentObserver方法来注册观察者,通过unregisterContentObserver方法来解除观察者

6. 使用Socket

Socket不仅能实现进程间通信,还可以实现设备间通信

7. 六种方式对比

名称

优点

缺点

适用场景

Bundle

简单易用

只能传输Bundle支持的数据类型

四大组件间的进程通信

文件共享

简单易用

不适合高并发场景,并且无法做到进程间的即时通信

无并发访问庆幸,交换简单的数据实时性不高的场景

AIDL

功能强大,支持一对多并发通信,支持实时通信

使用较复杂,需要处理好线程同步

一对多通信且有RPC需求

Messenger

功能一般,支持一对多串行通信,支持实时通信

不能很好处理高并发庆幸,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型

低并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求

ContentProvider

在数据源访问方面功能强大,支持一对多并发数据共享,可通过call方法扩展其他操作

可以理解为受约束的AIDL,主要提供数据源的CRUD操作

一对多的进程间的数据共享

Socket

功能强大,可以通过网络传输字节流,支持一对多并发实时通信

实现细节稍微有点繁琐,不支持直接的RPC

网络数据交换

四、Binder连接池

待学习