一、Android IPC简介
IPC,Inter-Process Comminication的缩写,含义为进程间通信或者跨进程通信,即两个进程之间进行数据交换的过程。首先,什么是进程?一般是指一个执行单元,在PC和移动设备上指一个程序或一个应用。而线程则是CPU调度的最小单元,同时也是有限的系统资源,它是进程的一部分。一个进度可以只有一个线程,即主线程,在Android的世界里又名UI线程,只有在UI线程里才可以去操作界面元素(不是一定的,具体原因可以自己了解一下)。在很多时候,如果在主线程中执行大量耗时的任务,就会造成界面无法响应,在Android里面叫做ANR(Application Not Responding),要想解决这个问题,就需要把这些任务放到别的线程中。
二、Andorid中开启多进程模式
如果想要在一个应用里面开启多个进程,只需要给四大组件在AndroidMenifest指定android:process属性即可,也可以通过JNI在native层fork一个新的进程,这种方法会在以后的JNI开发中演示,目前不做太多说明。
在开启多进程之后,会面临如下问题
- 静态成员和单例模式完全失效。
- 线程同步机制失效
- SharedPreferences的可靠性下降
- Application会多次创建
第一个和第二个问题的原因从本质上来说是一样的,每开启一个进程Android都会为其分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致了不同的虚拟机访问同一个对象会产生多份副本。
第三个问题是因为SharedPreferences不支持两个进程同时去执行写操作,否则可能会导致数据丢失。Shareferences底层是通过读写XML文件实现的,并发读写会出现问题。
第四个问题很显而易见,当一个组件跑在一个新的进程中,由于系统创建新的进程会分配独立的虚拟机,所以这个过程就是重启一个应用的过程,自然会去创建新的Application.
三、IPC基础概念
在通过Intent和Binder传输数据时,需要将数据进行序列化操作才可以进行传输,可以通过Serializable和Parcelable来完成对象的持久化。
3.1 Serializable
Serializable是Java提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化很简单,只需要将这个类实现Serializable接口并声明一个SerialVersionUID即可,甚至不声明这个SerialVersionUID也是可以的,可以实现序列化但是会对反序列化产生影响,只有序列化的数据中的serialVersionUID和当前类的serialVersionUID相同时才能被正常反序列化。另外,系统默认的序列化过程也是可以改变的,通过重写系统默认的writeObject和readObject方法就可以实现。
public class User implements Serializable {
private static final long serialVersionUID = 124332435687634567L;
private String name;
private boolean sex;
}
//序列化
User user = new User("kris",true);
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.txt"));
out.writeObject(user);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
//反序列化
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.txt"));
User newUser = (User) in.readObject();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
需要注意的是,静态变量属于类不属于对象,所以不会参与序列化过程。其次用transient关键字标记的成员变量也不会参与序列化。
3.2 Parcelable接口
Parcelable是Android特有的一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。常用写法如下:
public class User implements Parcelable {
private String name;
private boolean sex;
//从序列化后的对象中创建原始对象
protected User(Parcel in) {
name = in.readString();
sex = in.readByte() != 0;
}
//反序列化功能由CREATOR来完成,其内部表明了如何创建序列化对象和数组,
//并通过Parcel的一系列read方法来完成反序列化过程
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];
}
};
//内容描述功能,几乎在所有情况下都应该返回0,
//仅当当前对象中存在文件描述符时,此方法返回1
@Override
public int describeContents() {
return 0;
}
//序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法
//来完成。将当前对象写入序列化结构中,flags为1时标识当前对象需要作为返回值返回,
//不能立即释放当前资源,几乎所有情况都返回0
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeByte((byte) (sex ? 1 : 0));
}
}
既然Parcelable和Serializable都能实现序列化并且都能用于Intent间的数据传递,那么二者该如何抉择呢?Serializable使用起来简单但是开销很大,序列化和反序列化过程都需要大量的I/O操作。而Parcelable虽然使用起来麻烦点,但是传输效率很高,因此首选还是Parcelable。Parcleable主要是用在内存序列化上,通过Parcelable讲对象序列化到存储设备或者在网络中传输会稍显复杂,因此这两种情况还是推荐使用Serializable。
3.3 Binder
Binder是一个很深入的话题,本章节只会简单介绍一下。从直观上来说,Binder是Android中的一个类,它继承了IBinder接口。从IPC角度来讲,Binder是Android中的一种跨进程通信方式,Binder可以理解为一种虚拟的物理设备,它的驱动是/dev/binder,在Linux里面没有;从Android framework角度来说,Binder是ServerManager连接各种Manager(ActivityManager、WindowManager等等)和ManagerService之间的桥梁;从应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据(包括普通服务和AIDL服务)。下面会通过AIDL来讲解Binder:(具体demo请在AIDL章节中查看)
在项目中构建了AIDL代码后,会在app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out目录下找到对应的XXX.java类。这个类是一个接口并且继承了IInterface接口,虽然看起有点混乱,但是实际上还是很清晰的。
首先声明了4个方法getBookList、addBook、registerListener、unregisterListener,这些就是我们在AIDL文件中声明的方法了。接着声明了一个抽象内部类Stub,这个Stub就是一个Binder类,内部声明了4个整形的id来标识transact过程中客户端请求的到底是哪个方法,当客户端和服务端位于同一个进程时,方法不会走跨进程的transact过程,而位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。
DESCRIPTOR
Binder的唯一标识符,一般用当前Binder的类名表示
asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转换成客户端需要的AIDL接口类型的对象,这种转换是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub本身,否则返回的就是系统封装后的Stub.proxy对象。
asBinder
返回当前的Binder对象
onTransact
这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法处理。该方法的原型为public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端可以通过code来确定客户端所请求的方法是什么,接着从data中取出目标方法所需要的参数(如果存在参数的话),然后执行目标方法。当目标方法执行结束后,就向reply中写入返回值(如果存在返回值的话)。需要注意的是,如果此方法返回false,那么客户端的请求就会失败,通过这个特性我们可以用它来做权限验证,来避免其他进程随意调用服务端服务。
proxy#getBookList
这些方法是运行在客户端的,每当其被调用,首先会创建该方法所需要的输入性Parcel对象_data、输出型Parcel对象_reply和返回值对象_result;然后把该方法的参数信息写入_data中(如果存在参数);接着调用transact方法发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_result中的数据。
@Override public java.util.List<com.wtwd.aidl.Book> getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.wtwd.aidl.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.wtwd.aidl.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
通过上面的分析,可以初步的了解Binder的工作机制,可以看出当客户端发起远程请求时,当前的线程会被挂起直至服务器进程返回数据,如果这个远程方法是耗时的,就不能在UI线程中发起此次远程请求;其次,由于服务端的Binder方法运行在Binder线程池中,所以此Binder方法不管是否耗时都应该采取同步的方式去实现,因为它已经运行在一个线程中了,下图可以很形象地描述Binder的工作机制。
接下来会介绍Binder的两个很重要的方法linkToDeath和unlinkToDeath。Binder是运行在服务端进程的,如果服务端进程由于某种原因异常终止,这时候客户端到服务端的Binder会连接断裂(即是Binder死亡),如果客户端不知道断裂的话,其功能就会收到影响。为了解决这个问题,Binder提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,客户端可以接收到通知,这是可以进行重连操作从而恢复连接。
//首先声明DeathRecipient 对象
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
//当Binder死亡的时候,系统就会回调binserDied方法,我们就可以移除之前绑定的binder代理并重新绑定远程服务
@Override
public void binderDied() {
if (bookManager == null) {
return;
}
bookManager.asBinder().unlinkToDeath(deathRecipient,0);
bookManager = null;
//重新绑定service
Intent intent = new Intent("com.wtwd.aidl.aidlService");
intent.setPackage("com.wtwd.aidl");
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}
};
其次,在客户端绑定远程服务成功后,给binder设置死亡代理
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bookManager = IBookManager.Stub.asInterface(service);
try {
service.linkToDeath(deathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bookManager = null;
}
};