Android IPC基础
引言
IPC(Inter-Process Communication):即进程间通讯。我们都知道平时在开发中经常需要开启一条线程来执行比较耗时的任务,来避免ANR(Application Not Responding / 应用无响应)问题。而线程都是被包含在进程中,这个进程也就是咱的APP,当然APP除了默认的进程,还可以自行创建进程。有时候为了加大APP的可用内存,就需要使用多进程了。(注意:本文代码均为kotlin语言)
Android的多进程模式
1、开启多进程
Android多进程分为:
- 一个应用中存在多个进程;
- 两个应用的多进程;
通常是第一种情况,它的开启方法:
- 在AndroidMenifest.xml文件中为四大组件(Activity、Service、Receiver、ContentProvider)配置android:process属性;
- 通过JNI在native层fork一个进程;
2、多进程运行机制
Android 会使每个进程运行在独自的虚拟机中,因此静态变量是不能在进程间共享的。
使用多进程会造成以下几个问题:
- 静态成员和单例模式 失效;
- 线程同步机制 失效;
- 会导致SharedPrefereces读写 数据丢失;
- Application会多次创建;
对象的两种序列化
当我们通过Intent或Binder传输数据时需要将对象进行序列化,序列化有两种方式。
1、Serializable 接口
Serializable 是Java提供的一个序列化接口。使用比较简单,只要类实现Serializable接口,同时指定一个serialVersionUID值(非必需),即可实现序列化。
import java.io.Serializable
class User : Serializable {
var userName = ""
var userAge = 0
}
注意:对象在序列化和反序列化得到的对象内容一样,但不是同一个对象。
serialVersionUID是序列化类的版本标识,只有对象的serialVersionUID和类的serialVersionUID一致时可以被反序列化。
2、Parcelable 接口
Parcelable是Android提供的新序列化方式。只要实现此接口,即可序列化。
import android.os.Parcel
import android.os.Parcelable
class User() : Parcelable {
var userId = -1
var userName = ""
var userAge = 0
/**
* 从序列化后的对象转为原始对象
*/
constructor(parcel: Parcel) : this() {
userId = parcel.readInt()
userName = parcel.readString()
userAge = parcel.readInt()
}
/**
* 将当前对象写入序列化对象中
* @param flags 1,表示当前对象需要作为返回值返回,不会立即释放资源;0,则相反。一般都是0
*/
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(userId)
parcel.writeString(userName)
parcel.writeInt(userAge)
}
/**
* 返回当前对象的内容描述。如果含有文件描述符,返回1;否则返回0。一般返回0
*/
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<User> {
/**
* 从序列化后的对象转为原始对象
*/
override fun createFromParcel(parcel: Parcel): User {
return User(parcel)
}
/**
* 创建指定长度的原始对象数组
*/
override fun newArray(size: Int): Array<User?> {
return arrayOfNulls(size)
}
}
}
Parcelable中的方法说明都在代码中注释了,稍微看下相信就能明白。相比Serializable 是不是感觉很复杂?!没关系的, 除了下面这些是我们写的,其他的都是开发工具Android Studio 帮我们自动生成的。毫无压力是不是!
class User() : Parcelable {
var userId = -1
var userName = ""
var userAge = 0
}
现在会写了,然而这两种方式的区别是什么呢?
Serializable是Java中的序列化接口,使用简单 开销大,大量的I/O操作。主要用在对象序列化后存储到设备中,或经过网络传输。
Parcelable是Android中的序列化接口,代码复杂,效率很高,Android的推荐方式,主要用在内存序列化。
Binder(粘合剂)的使用
1、什么是Binder?
- 直观来说,Binder是Android中的一个类,实现了IBinder接口;
- 从IPC角度来说,它是Android中的一种跨进程通讯方式,是一种虚拟的物理设备,设备驱动是/dev/binder;
- 从Android Framework角度来说,它是ServiceManager连接其他Manager(如ActivityManager、WindowManager等)和相应ManagerService的桥梁;
- 从应用层来说,它是客户端和服务端进行通讯的媒介。当bindService的时候,服务端会向客户端返回一个Binder对象,客户端就可以通过这个对象来获取服务端的服务(普通服务或AIDL服务)和数据。
2、Binder的工作机制
这里我们通过AIDL(Android 接口定义语言)来分析Binder的工作机制。现在可继续使用通过Parcelable序列化的User类,我们新建aidl包,并将User类放入其中,然后右键User文件,新建AIDL文件夹,并创建 User类所在的相同包名。注意,除了后缀名之外,文件名称要保持一致,即User.aidl
User.aidl 代码
// User.aidl
package com.sange.ipc.aidl;
parcelable User;
另外再新建IUserManager.aidl文件,代码如下:
// IUserManager.aidl
package com.sange.ipc.aidl;
// Declare any non-default types here with import statements
import com.sange.ipc.aidl.User;
interface IUserManager {
// 计算两个数的和 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
// 添加用户
void addUser(in User user);
// 获取所有用户
List<User> getUsers();
}
现在点击工具栏中的 “锤子图标” 让系统为IUserManager.aidl文件生成Binder类,文件位置在IPC\app\build\generated\source\aidl\debug\com\sange\ipc\aidl目录下,接下来我们根据它来分析Binder的工作原理。代码如下:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\\Demo\\IPC\\app\\src\\main\\aidl\\com\\sange\\ipc\\aidl\\IUserManager.aidl
*/
package com.sange.ipc.aidl;
public interface IUserManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.sange.ipc.aidl.IUserManager {
// 描述符,Binder的唯一标识
private static final java.lang.String DESCRIPTOR = "com.sange.ipc.aidl.IUserManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.sange.ipc.aidl.IUserManager interface,
* generating a proxy if needed.
* 将一个Binder对象转成IUserManager接口对象,如果客户端和服务端在同一个进程中 返回的是服务端的stub对象,否则,返回的是系统封装的Stub.Proxy对象
*/
public static com.sange.ipc.aidl.IUserManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.sange.ipc.aidl.IUserManager))) {
return ((com.sange.ipc.aidl.IUserManager) iin);
}
return new com.sange.ipc.aidl.IUserManager.Stub.Proxy(obj);
}
// 返回当前Binder对象
@Override
public android.os.IBinder asBinder() {
return this;
}
/**
* 此方法运行在服务端的Binder线程池中,当客户端发送跨进程请求时,远程请求会通过系统底层封装后,交给此方法来处理。
* 通过参数code可以判断出要执行的方法,执行完 如果需要向客户端返回数据 可以用reply写入数据,然后客户端会收到,读取出来。
* @param code
* @param data
* @param reply 可以写入返回数据
* @param flags
* @return 此方法如果返回false 客户端的请求就会失败,也可以理解为服务端拒绝了客户端请求。
* @throws android.os.RemoteException
*/
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addUser: {
data.enforceInterface(DESCRIPTOR);
com.sange.ipc.aidl.User _arg0;
if ((0 != data.readInt())) {
_arg0 = com.sange.ipc.aidl.User.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addUser(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getUsers: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.sange.ipc.aidl.User> _result = this.getUsers();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.sange.ipc.aidl.IUserManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
// 计算两个数的和 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
// 添加用户
/**
* 此方法过程与getUsers()方法类似。
* @param user
* @throws android.os.RemoteException
*/
@Override
public void addUser(com.sange.ipc.aidl.User user) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((user != null)) {
_data.writeInt(1);
user.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
/**
* 获取所有用户
* 此方法首先创建了两个Parcel对象,输入型对象_data,输出型对象_reply,还有List对象_result,用于存储方法的返回值,
* 如果方法有参数就把它写入到_data对象中,接着调用transact方法 发起RPC(远程过程调用)请求,同时当前线程会被挂起,直到RPC结束后才会继续执行,
* 然后从_reply读取出RPC过程的结果并返回。
* @return
* @throws android.os.RemoteException
*/
@Override
public java.util.List<com.sange.ipc.aidl.User> getUsers() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.sange.ipc.aidl.User> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getUsers, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.sange.ipc.aidl.User.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getUsers = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
// 计算两个数的和 除了基本数据类型,其他类型的参数都需要标上方向类型:in(输入), out(输出), inout(输入输出)
// 添加用户
public void addUser(com.sange.ipc.aidl.User user) throws android.os.RemoteException;
// 获取所有用户
public java.util.List<com.sange.ipc.aidl.User> getUsers() throws android.os.RemoteException;
}
代码原本是没有格式化的,这里为了方便查看,所以格式化了一下。一眼看上去 这个类很复杂,其实挺简单的,我们一步步分析,首先这是个继承android.os.IInterface的接口类。里面声明了两个接口方法(addUser和getUsers),这就是我们在IUserManager.aidl文件定义的那两个方法,还声明了一个抽象的静态内部类Stub,继承了android.os.Binder类,实现了com.sange.ipc.aidl.IUserManager我们定义的接口类。在Stub类中还定义了两个int常量(TRANSACTION_addUser和TRANSACTION_getUsers),这两个参数用于标识在transact过程中客户端请求的到底是哪个方法,名称格式是固定的都式TRANSACTION_方法名,值是IBinder接口的常量android.os.IBinder.FIRST_CALL_TRANSACTION开始依次增加的。注意:只有客户端和服务端位于不同的进程中,才会调用走transact过程。可以看出 这个文件主要的实现逻辑就是Stub类里的内容,对于里面每个方法的含义已在代码写了注释,可以结合着下面的Binder工作机制图来理解。
Binder工作机制图
Binder中的两个重要方法linkToDeath和unlinkToDeath
我们知道,Binder运行在服务端进程,如果服务端发生异常情况Binder终止,这时候客户端到服务端的链接就断开了(也就是Binder死亡),会导致客户端远程调用失败。如果我们不知道Binder是否断开,客户端就是出问题,为了解决这个问题,我们可以给Binder设置一个死亡代理,当Binder死亡时,就会收到通知。
如何设置死亡代理?
先声明一个IBinder.DeathRecipient对象,当死亡时,会调用binderDied()方法,我们断开连接,重新绑定服务。
private val deathRecipient = object : IBinder.DeathRecipient{
override fun binderDied() {
if (userManager != null){
userManager?.asBinder()?.unlinkToDeath(this,0)
userManager = null
// 重新绑定Service
bindService(Intent(this@MainActivity, IRemoteService::class.java), this@MainActivity.serviceConnection, Context.BIND_AUTO_CREATE)
}
}
}
在服务端绑定成功后,给Binder设置死亡代理。
userManager = IUserManager.Stub.asInterface(service)
service?.linkToDeath(this@MainActivity.deathRecipient,0)
这两个方法的第二个参数是标记位,传0即可。另外通过Binder的isBinderAlive方法也可以判断Binder是否死亡。
好了,Binder的简单了解就是这些。
试一试
通过上述的Binder分析过程来看,我们完全可以不提供aidl文件,直接仿照系统生成的Java文件自己写!有兴趣的同学可以试一下。
致谢《Android开发艺术探索》