inter process communication,进程间通信,两个进程间进行数据交流,进程和线程:线程是cpu调度的最小单元,进程通常是执行单元,一个进程可以包含多个线程,进程在pc和移动设备上通常是一个程序或应用。
IPC的使用场景必须是多进程的,可以是一个程序中多个进程间通信,也可以是多个程序的进程进行通信,安卓中多进程的的通信方法是Binder,此外还有socket,本文详细帮助大家详细理解一下Binder方式的IPC机制。
安卓中多进程的作用
每个进程所能使用的资源是有限,特别是内存,安卓系统对用户进程有严格的内存要求,超过此内存限制时,应用将会发生OOM,此时我们采用多进程,在一个程序中开启多个进程,分担主进程的压力,避免OOM。
安卓中多进程的实现
要在安卓程序中实现多进程,只有在manifest四大组件的注册中,添加process属性,只有这一种方法!其实还有一种方法,那就是通过JNI在native层去fork一个新的进程,怎么样是不是看不懂,反正我也没看懂,书上就是这么写的;
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:process=":remote"
android:name=".SecondActivity" />
<activity
android:process="com.example.demo_ipc.remote"
android:name=".ThirdActivity"/>
我们没有给mainActivity指定process属性,分别给secondActivity指定了":remote",thirdActivity指定了"com.example.demo_ipc.remote"。此时launch三个activity,可以看到三个进程都运行了,mainActivity默认为主进程,secondActivity的 " : "的含义是前面省略了包名,运用此方法命名的进程是当前应用的私有进程,其他应用的组件不可以和他跑在同一进程中,而thirdActivity的进程则为全局进程,其他应用使用ShareUID方式可以和他跑在同意进程。
- 科普一下ShareUID:
系统会为每一个app分配一个Share User ID,具有相同UID的App可以共享数据,比如data目录,组件信息等。
多进程产生的问题
我们知道Android为每个应用分配一个虚拟机,实际上是为每个进程分配一个独立的虚拟机,当一个应用是多进程的时候,这个应用就会有多个虚拟机,导致同一个类也会有多个版本,比如我们在上文的project中新建一个静态类里面放一个静态int i =0,在mainActivity中改变这个int=1,在SecondActivity打印出来,发现这个int还是0,我们有三个进程,就有三个int的副本,在线程一改变线程一里的int副本是不会改变线程二中int的值。
总结一下多进程产生的问题:
- 静态成员和单例模式完全失效
- 线程同步机制完全失效
- SharedPreference的可靠性下降
- Application会多次创建
序列化
主要包括三方面的内容:Serializable接口,Parcelable接口,和Binder。
Serializable和Parcelable用于对象的序列化。
- Serializable接口是java的序列化方法,将需要序列化的类继承Serializable接口,这是一个标志接口,无实现方法。
新建user类实现Serializable接口
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private int age;
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
}
对象序列化
User user = new User(1,"jack",21);
File file = new File("user.tet");
try {
OutputStream outputStream = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(user);
outputStream.close();
objectOutputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
首先创建OutputStream 对象,在将他封装到ObjectOutputStream对象中,然后writeObject,close,就完成了对user对象的序列化。
反序列化
try {
InputStream inputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
User newUser = (User) objectInputStream.readObject();
inputStream.close();
objectInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
步骤和序列化差不多。
注意一下我在user类中指定了一个serialVersionUID ,serialVersionUID的作用是用来辅助序列化和反序列化的,序列化的时候会把UID也序列化到文件中,在反序列化的时候会判断文件和类的UID是否相同,如果相同则可以反序列化,如果我们不指定UID,java会根据类的hash去算这个类的UID,如果这个类的成员发生变化,UID就会发生变化,反序列化就会失败。
- Parcelable是安卓提供的序列化接口:
public class User implements Parcelable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private int age;
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
protected User(Parcel in) {
id = in.readInt();
name = in.readString();
age = in.readInt();
}
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];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(id);
parcel.writeString(name);
parcel.writeInt(age);
}
}
必须实现几个必要的方法:
- describeContents:接口内容的描述,一般返回0
- writeToParcel: 序列化方法,将类的数据写入到Parcel容器中
- 静态的Creator接口,包含两个方法createFormParcel():反序列化方法,将parcel返回成序列化对象,newArray():提供给外部类反序列化这个数组使用。
写完以后就可以直接使用intent或binder传递User类的对象了
二者的比较:Serializable会产生大量的IO,非常的占用内存,Parcelable 的操作则比较繁琐。
进程间通信
安卓中进程间通信的方式对比
通信方式 | Bundle | 文件共享 | Messager | AIDL | ContentProvider | Socket |
---|---|---|---|---|---|---|
特点 | 只能传递Bundle支持的数据类型 | 无法做到进程间即时通信 | 不支持RPC,只能传递Bundle支持的数据类型 | 功能强大,使用复杂,需要处理好线程同步 | 可以理解为受约束的AIDL | 不支持直接的RPC,实现稍微复杂 |
适用场景 | 四大组件之间的进程通信 | 无并发需求,数据实时性不高的场景 | 无RPC需求 | 有RPC需求 | 应用之间数据交换 | 网络进程间数据交换 |
使用AIDL进行进程间通信
1 创建一个子进程的服务
2 创建AIDL文件,在文件中写好业务接口
// IConnectionService.aidl
package com.example.myapplication;
// Declare any non-default types here with import statements
interface IConnectionService {
// 业务接口
void connect();
void disconnect();
boolean isConnect();
}
3 编译后得到自动生成的JAVA文件
4 在子进程服务中创建这个JAVA文件的实例,并通过onBind()返回给主进程
public class RemoteService extends Service {
boolean isConnect = false;
IConnectionService connectionService = new IConnectionService.Stub() {
@Override
public void connect() throws RemoteException {
isConnect = true;
Log.d("RemoteService","connect");
}
@Override
public void disconnect() throws RemoteException {
isConnect = false;
Log.d("RemoteService","disConnect");
}
@Override
public boolean isConnect() throws RemoteException {
Log.d("RemoteService",isConnect+"");
return isConnect;
}
};
@Override
public IBinder onBind(Intent intent) {
return connectionService.asBinder();
}
}
5 在主进程中开启服务,通过这个实例进行通信
IConnectionService connectionServiceMainProcess;
Intent intent = new Intent(this,RemoteService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
connectionServiceMainProcess = IConnectionService.Stub.asInterface(service);
try {
connectionServiceMainProcess.connect();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
浅谈Binder
什么是Binder?
- Binder是安卓特有的进程间通信机制
- Binder是一个类,它实现了IBind接口
- 是ServiceManager连接各种Manager和相应ManagerService的桥梁
传统的进程通信方式将发送方用户空间
的数据先拷贝到内核空间
的内核缓存区
,再从内核缓存区拷贝到接受方的用户空间,因此需要两次拷贝;
传统IPC
Binder将发送方的数据拷贝到内核缓存区,而接受方和内核缓存区都映射在同一块物理地址上,所以省去了一次拷贝的操作;
Binder-
通信双方相对独立,稳定性高
,Binder和Socket一样是基于C/S架构 -
效率高
Binder只需要拷贝一次 -
相对安全
,Binder机制为每一个进程都分配了一个UID,可以有效性验证
AIDL实现原理
首先编写一个AIDL接口文件,写上我们的业务抽象方法,通过编译我们会得到一个自动生成的java接口,这个业务接口继承了iInterface,还有一个内部类Stub,Stub内部类继承了Binder类,实现了业务接口,有三个比较重要的方法:
- asBinder():返回一个Binder对象用于进程间传递
- asInterface():进程间传递后将Binder转化为接口使用,
代理模式
本质是生成一个Stub的Proxy - onTransact():数据的传递
客户端调用bindService()绑定服务端,服务端会通过onBind()将业务接口的内部类Stub对象以Ibinder形式传递给客户端,在客户端的回调中,会调用asInterface()将Binder处理成Stub的代理,通过这个代理进行进程间通信;
网友评论