前言
本系列主要介绍Android的IPC机制,Android中多进程的概念以及多进程开发的注意事项,进程间通信的方式等。
1.IPC简介
IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,指两个进程间进行数据交换的过程。Android中一般指两个APP间的数据传递。
提到进程,首先要理解什么是进程,什么是线程:
- 线程: CPU调度的最小单元,有限的系统资源
- 进程: 一个执行单元
关系图:(手动画的有点丑,见谅)
IPC使用场景:多进程,多进程情况分为以下两种
- 一个应用本身因为某些原因需要采用多进程来实现(某些模块需要单独运行在单独的进程、Android中对单个应用使用的最大内存做了限制)
- 当前应用需要向其他应用获取数据
3.Android多进程模式
- 开启多进程模式(单个应用下)
给四大组件(Activity;Service;Receiver;ContentProvider)在AndroidMenifest.xml中添加process属性
<activity
android:name=".Main2Activity"
android:launchMode="singleTask"
android:process=":remote"
android:taskAffinity="com.wdl.test" />
<activity android:name=".Main3Activity"
android:process="com.wdl.kt1.remote"
/>
上面的案例表示当Main2Activity启动时,系统新增一个om.wdl.kt1:remote的进程,默认进程进程名是包名。
案例中android:process分别为“:remote”,“com.wdl.kt1.remote”,两者区别:
值 | |
---|---|
: | 指在当前进程名前面加上应用包名;属于当前应用的私有进程,其他应用组件不可与其跑在同一进程中 |
完整形式 | 完整的命名方式,不会附加包名信息;属于全局进程,其他应用通过ShareUID可与其在同一进程中运行 |
Android为每个应用都分配一个UID,具有相同UID的应用才能共享数据。两个应用具有相同的ShareUID并且签名相同,才能互相访问对方的私有数据。(data目录;组件信息等)
4.多进程的运行机制
Android为每个进程都分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,导致访问同一个类的对象时会产生多个副本。
例:新建类,声明静态成员变量(静态变量在所有的地方共享),在第一个页面设置为2,跳转至第二个页面。
object UserManager {
var sUserId = 1
}
第一个页面
UserManager.sUserId = 2
Log.e("wdl", "--onCreate-- ${UserManager.sUserId}")
第二个页面
Log.e(TAG, "--onCreate-- ${UserManager.sUserId}")
日志
2018-10-15 19:45:23.779 22832-22832/com.wdl.kt1 E/wdl: --onCreate-- 2
2018-10-15 19:45:45.658 22870-22870/? E/wdl: --onCreate-- 1
观察上面发现,位于不同进程访问同一个类会产生副本。
使用多进程造成的影响:
- 静态成员和单例模式失效
- 线程同步机制失效
- SharePreference可靠性降低(两个进程同时去执行写操作会造成数据丢失)
- Application多次创建
5.IPC基础概念
1. Serializable接口以及Parcelable接口实现数据序列化
- Binder
简单介绍
新建AIDL示例:
- 新建aidl包,新建User.java、User.aidl、IUserManager.aidl
package com.wdl.kt1.aidl
import android.os.Parcel
import android.os.Parcelable
import com.wdl.kt1.Book
import java.io.Serializable
/**
* author: wdl
* time: 2018/10/12 18:05
* des: TODO
*/
class User : Parcelable,Serializable {
private val serialVersionUID = 519067123721295773L
private var age: Int? = null
private var name: String? = null
private var book: Book? = null
constructor(age: Int?, name: String, book: Book) {
this.age = age
this.name = name
this.book = book
}
/**
* 从序列化后的对象中创建原始对象
* 因为Book是另一个可序列化对象
* 所以它的反序列化过程需要传递当前线程的上下文类加载器
*
* @param in Parcel
*/
private constructor(`in`: Parcel) {
age = `in`.readInt()
name = `in`.readString()
book = `in`.readParcelable(Thread.currentThread().contextClassLoader)
}
/**
* 返回当前对象的内容描述
*
* @return 1:含描述符
* 基本为0
*/
override fun describeContents(): Int {
return 0
}
/**
* 将当前对象写入序列化结构中
*
* @param dest Parcel
* @param flags 1:标识当前对象需要作为返回值返回不能立即释放资源
* 基本为0
*/
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(age!!)
dest.writeString(name)
dest.writeParcelable(book, 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)
}
}
override fun toString(): String {
return "User{" +
"age=" + age +
", name='" + name + '\''.toString() +
", book=" + book +
'}'.toString()
}
}
右键new,AIDL file
IUserManager.aidl
// IUserManager.aidl
package com.wdl.kt1.aidl;
//尽管User与IUserManager同一个包,但是不导入会报错
import com.wdl.kt1.aidl.User;
// Declare any non-default types here with import statements
interface IUserManager {
List<User> getUsers();
void addUser(in User user);
}
User.aidl
// User.aidl
package com.wdl.kt1.aidl;
// Declare any non-default types here with import statements
parcelable User;
重新build项目:
Process 'command 'D:\SDK\build-tools\27.0.3\aidl.exe'' finished with non-zero exit value 1
1.包名问题
2.AIDL不支持同名不同参函数
3.使用自定义类型时需要在AIDL中声明
会报上面的错误,那是因为虽然User与IUserManager同一个包,但是不导入会报错,正确代码如上。
如图可以找到系统自动生成的IUserManager.java类
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: G:\\android_work\\Kt1\\app\\src\\main\\aidl\\com\\wdl\\kt1\\aidl\\IUserManager.aidl
*/
package com.wdl.kt1.aidl;
// Declare any non-default types here with import statements
public interface IUserManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.wdl.kt1.aidl.IUserManager {
private static final java.lang.String DESCRIPTOR = "com.wdl.kt1.aidl.IUserManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.wdl.kt1.aidl.IUserManager interface,
* generating a proxy if needed.
*/
public static com.wdl.kt1.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.wdl.kt1.aidl.IUserManager))) {
return ((com.wdl.kt1.aidl.IUserManager) iin);
}
return new com.wdl.kt1.aidl.IUserManager.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@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_getUsers: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.wdl.kt1.aidl.User> _result = this.getUsers();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addUser: {
data.enforceInterface(DESCRIPTOR);
com.wdl.kt1.aidl.User _arg0;
if ((0 != data.readInt())) {
_arg0 = com.wdl.kt1.aidl.User.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addUser(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.wdl.kt1.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;
}
@Override
public java.util.List<com.wdl.kt1.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.wdl.kt1.aidl.User> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getUsers, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.wdl.kt1.aidl.User.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addUser(com.wdl.kt1.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();
}
}
}
static final int TRANSACTION_getUsers = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List<com.wdl.kt1.aidl.User> getUsers() throws android.os.RemoteException;
public void addUser(com.wdl.kt1.aidl.User user) throws android.os.RemoteException;
}
观察上述代码,可以发现它继承了IInterface,自己也是个接口。所有可以在Binder中传输的接口都要继承IInterface。
- 声明了在IUserManager.aidl中所声明的方法getUsers()和addUser(User user)
- 声明两个整型ID用来标识这两个方法,transcat过程中客户端请求的是哪个方法
- 声明Stub内部类,是一个Binder类,当客服端与服务端处于同一进程,不走跨进程的transcat,不同进程,走transcat,由Stub.Proxy完成
- DESCRIPTOR:Binder唯一标识,当前类名表示
- asInterface(android.os.IBinder obj)
用于将服务端的Binder对象转为客户端所需的AIDL接口类型的对象,同一进程返回服务端Stub本身,否则返回Stub.Proxy
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.wdl.kt1.aidl.IUserManager))) {
return ((com.wdl.kt1.aidl.IUserManager) iin);
}
return new com.wdl.kt1.aidl.IUserManager.Stub.Proxy(obj);
-
asBinder()
返回当前Binder对象 -
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
此方法运行在服务端的线程池中,当客户端发起跨进程请求时,交给此方法处理。服务端通过code判断请求目标方法是什么,通过data取出目标方法所需参数,执行完成后向reply写入返回值;如果返回false,客户端请求失败。 -
Proxy#getUsers()
此方法运行在客户端,创建该方法所需输入型Parcel对象_data,输出型Parcel对象_reply,返回值List对象_result。将该方法参数信息写入_data中,接着调用transcat方法完成RPC(远程过程调用),当前线程挂起,服务端的transcat会被调用直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC返回结果;最后返回_reply数据 -
Proxy#addUser(User user)
同上
Binder工作机制:
?:当客户端发起请求时,当前线程会被挂起,所以如果这个方法很耗时,则都应该采用同步的方式去实现。
Binder运行在服务端进程,由于服务端异常终止,服务端的连接断裂(Binder死亡),如果客户端不知道连接断裂,就会影响客户端的使用,所以Binder提供了如下两个方法判断连接是否断裂:
- linkToDeath:设置一个死亡代理,断裂时收到通知
- unlinkToDeath