文章目录
- 一、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. 多进程的情况
- 一个应用因为某些原因自身需要采用多进程模式来实现,比如某些模块可能需要运行在单独的进程中,或者需要通过多进程加大内存
- 当前应用需要向其他应用获取数据
假设包名为com.test
- 第一种方式 “:” ,进程属于当前应用私有进程,其他应用的组件不可以和它跑在同一个进程中,最终进程名为"come.test:remote",要在当前进程名前面附加上当前包名
- 第二种方式属于全局进程,是完整的进程名,其他应用通过ShareUID可以和它跑在同一个进程中
- 没有指定此属性的在默认进程中,进程名为包名
【第2章 IPC机制】使用多进程会造成:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效 (不同进程锁的不是同一个对象)
- SharedPreference 的可靠性下降 (SP不支持两个进程同时执行写操作,会导致一定几率的数据丢失,其底层是读写XML文件,并发写有问题)
- Application 会多次创建 (不同进程的组件拥有独立的虚拟机、Application以及内存空间,运行在同一个进程中的组件属于同一个虚拟机和同一个Application,运行在不同进程中的组件属于两个不同的虚拟机和Application)
serialVersionUID作用
序列化的时候系统会把当前类的serialVersionUID写入序列化文件(或其他中介),反序列化时系统会去检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,无法正常反序列化
如果不手动指定serialVersionUID值,系统会根据自动计算当前类的hash值并赋给serialVersionUID,这样当类有所改变时(比如删除了某个成员变量),其serialVersionUID也会被系统自动重新计算赋值,导致反序列化失败;手动指定serialVersionUID的值,可以在当前类发生某些变化后,仍最大限度恢复数据;注意,如果类的结构发生了改变(比如改变类名),尽管serialVersionUID验证通过,反序列化还是会失败
不参与序列化的值
- 静态成员变量属于类不属于对象,不参与序列化过程
- 用transient关键字标记的成员变量不参与序列化过程
一个实现了序列化的类
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 CREATOR = new Parcelable.Creator() {@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
3. Serializable 和 Parcelable 区别
- Serializable是Java中的序列化接口,使用起来开销很大,序列化和反序列化都需要大量的I/O操作。
- Parcelable是Android中的序列化方法,更适用于Android平台,缺点是使用稍麻烦,但是效率很高,Android推荐。
- Parcelable主要用于内存序列化,通过Parcelable将对象序列化到存储设备或者进行网络传输会比较复杂,这两种情况建议使用Serializable。
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 CREATOR = new Parcelable.Creator() {
@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 getBookList();
void addBook(in Book book);
}
注意:尽管Book类和IBookManager在同一个包,在IBookManager中仍要导入Book类
再一个,aidl包是和java包平级的,在src->main下,其他位置无效,这个右键生成AIDL文件,自动就会生成这个文件夹并将生成的.aidl文件放在其下相应目录中了
文章图片
然后学习系统为IBookManager生成的Binder类,在如图位置:
文章图片
先大概了解一下Binder机制流程:
文章图片
看下一这个类:
文章图片
- 这个类继承了IInterface接口,同时自己也是一个接口,所有可以再Binder中传输的接口都需要继承IInterface;
- DESCRIPTOR 是Binder的唯一标识
- 声明了一个内部类Stub,这个是一个Binder类
文章图片
- 声明了两个方法 getBookList 和 addBook,同时声明了两个 id用于标识这两个方法
文章图片
- asInterface:用于将服务端的Binder对象转换成客户端需要的AIDL接口类型的对象,如果客户端和服务端位于同一个进程,那么返回服务端service对象本身,否则返回系统封装后的Stub.proxy代理对象
- asBinder:返回当前的Binder对象
文章图片
- onTransact:这个方法运行在服务端中的BInder线程池中,客户端发起跨进程请求,远程请求会通过系统底层封装后交由此方法处理。首先通过code确定请求目标,接着从data中取出参数,然后执行
文章图片
- Proxy内部类#getBookList#addBook:这两个方法运行在客户端,当客户端调用此方法时,首先创建输入型对象 _data,输出型对象 _reply,返回值对象 _result,接着把参数写入 _data,再调transact发起RPC(远程过程调用)请求,同时当前线程挂起,服务端的onTransact会被调用,直到RPC过程返回,当前线程继续执行,并从 _reply 中取出返回结果
- 客户端发起远程请求时,当前线程会被挂起直至服务端进程返回数据,所以远程方法如果是耗时的,不能再UI进程中发起请求
- 服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方法去实现(这个还不太理解)
注意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的包结构在服务端和客户端要保持一致
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 | 网络数据交换 |