文章目录
如何在一个Android项目中使用多个进程
在AndroidManifest中配置使用android:process属性,因为这个属性只能在AndroidManifest中使用,所以跨进程只能作用于四大组件
这个属性的使用,有以下几种特点
- 如果没有配置 android:process这个属性,默认为项目的包名
- 这个属性可以配置 :remote 或者 包名.remote, 使用冒号这种方式,创建的是这个应用的私有进程,此进程不能与其他 应用 通过ShareId的方式跑在同一个进程中
- 因为使用android:process虽然新开了一个进程,但是还是之前的app,所以UID是一样的
可用 adb shell ps | grep 项目名 看PID和UID
USER PID PPID VSIZE RSS WCHAN PC NAME
u0_a145 10608 134 292960 26816 ffffffff 4019ca70 S com.ex.thre
u0_a146 10755 134 302420 32152 ffffffff 4019ca70 S com.ex.two
IPC(跨进程通信方式)
1. Bundle
通过Intent传递一个带有数据的Bundle来来实现跨进程通信,bundle的本质是一个parcelable对象
Intent intent = new Intent()
intent.putExtra()
2. 使用共享文件
比如A进程将数据存放在一个共有的目录下的文件中,然后让B进程去读,这样就实现了跨进程通信。但是两个进程共同去读写文件,会产生多进程安全问题,所以这种方式最好不要再并发量大的场景下使用。
还需要强调的一点是这个共享的文件不能是SharePrefernces文件,因为SP文件在内存中有一份缓存,所以多进程共同读写非常不可靠,容易造成数据丢失。
3.Messenger
Messenger封装了AIDL,可以让我们很容易的进行进程通信,但是使用Messenger一次只能处理一个请求即串行的处理,所以不用考虑多线程同步问题。
- 当不要求服务端响应时
- 如果需要服务端向客户端的回复一些数据
在这种场景下,需要让客户端也变为一个服务端,即在客户端用Handle新初始化一个Messenger用于处理服务端回复的数据。在发送请求时通过Message的replyTo字段将客户端这边的Messenger传递给服务端。
4. AIDL
AIDL服务端
第一步 创建AIDL文件
要想使用AIDL,首先需要创建AIDL文件,AIDL分为两种,一种是声明AIDAIDL服务端提供给AIDL客户端的接口,我们将它成为接口AIDL文件,另一种是声明接口AIDL文件中使用到的Parcelable对象,即Parcelable对象声明文件
具体的AIDL文件规范如下:
- 并不是所有的类型AIDL文件都支持,AIDL文件只支持如下几种类型
类型 | 是否需要显示import |
---|---|
八大基本类型,比如int,float,long等 | 不需要 |
String或者CharSequence | 不需要 |
List,只支持ArrayList,要求List中的每一个元素都是AIDL文件支持的类型 | 不需要 |
Map,只支持HashMap,要求Map中的每一个元素都是AIDL文件支持的类型 | 不需要 |
实现了Parcelable接口的对象 | 不但需要显示声明,还需要单独的AIDL文件使用parcelable关键字去声明这个对象,要求这个声明文件的全限定名和实际的Java类型保持一致 |
AIDL接口 | 需要显示import |
- AIDL中除了基本数据类型和AIDL类型,一律需要在类型前声明 in, out, 或inout,不能一律的使用inout,因为这个在底层是由开销的
类型前声明 | 意义 |
---|---|
in | 表示数据只能由客户端流向服务端,服务端会获取到客户端完整的数据,但客户端不会同步服务端你对该对象的修改,不写的话,默认的 tag 就是 in |
out | 表示数据只能由服务端流向客户端,从服务端端接受该对象不为空,但字段内容为空,服务端修改对象后,binder 远程调用返回后,客户端会收到修改后的对象 |
inout | 则表示数据可在服务端与客户端之间双向流通 |
- AIDL接口中不像传统接口,可以定义静态场景,它只能定义方法
第二步 写AIDL服务
写一个服务将AIDL文件生成的binder对象, 返回给绑定这个服务的客户端
上一步写的AIDL文件会在编译阶段,自动帮助我们生成一个含有binder的类
自己创建一个Service类将该binder对象返回给客户端即可
AIDL客户端
通过ServiceConnection绑定AIDL服务获得binder对象即可调用AIDL服务端的接口
AIDL安全问题
如果创建了一个AIDL服务,任何进程都可以bind并并且调用AIDL接口是很不安全的,所以要对连接AIDL接口的客户端做限制
常用的显示方式有两种
-
自定义权限
可以在onbind方法进行这种权限判断 -
判断binder的身份
可以在AIDL文件生成的onTransact方法中通过getCallUid方法得知调用者的UID,再通过packageManager.getPackagesForUid()得到调用者的包名再加以判断
onway关键字
我们知道通过binder去跨进程通信,是会阻塞客户端当前线程的,如果在AIDL定义的接口中加了oneway关键字,那么该接口的调用将会变成异步的,不会阻塞当前线程。oneway关键字只适用于无返回值的接口
加了oneway关键字后AIDL文件与之前有什么不同
只是transact方法的flag从0 变为了 1
客户端给服务端注册回调无法注销
一般我们设置回调都会使用一个list去保存回调,注销时再将回调从list中删除,但是AIDL服务端收到的参数是反序列化过来的,所以注册的对象会想要注销的绝对不是一个对象,所以无法注销, 可用RemoteCallBackList解决这个问题
虽然不能以回调对象本身作为注销的依据,但是binder对象是同一个
还有就是虽然它看起来像个List但其实本质并不是,所以遍历时一定要注意要用以下的这种方式
监控AIDL服务端的消失
如果AIDL服务端被杀了,客户端有两种方式可以感应到
- 注册死亡通知
通过IBinder对象注册
当服务端死亡时, binderDied会在binder线程池中被回调 - ServerConnetion的回调
当服务端死亡时, serviceConnection会在onServiceDisconnected方法会在主线程中回调
binder连接池
binder连接池是一种思想,并不像线程池一样有封装好的类,需要自己实现
binder连接池可以解决一个AIDL服务端进程写了十几个Service的问题
大致思想如下图所示
什么是binder
是Android中的一个类,实现了IBinder接口,是Android中的一种跨进程通信的一种方式
下面我们通过AIDL来通知binder是工作过程
首先我们要创建三个文件
- Book.java ------- aidl请求接口中用到的parcelable对象
- IBookInterface.aidl ------- aidl请求接口
- Book.aidl ------- 声明Book.java对象
Book.java
package com.example.studyapplication;
import android.os.Parcel;
import android.os.Parcelable;
public class Book implements Parcelable {
private String name;
private int price;
protected Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
}
IBookInterface.aidl
// IBooklInterface.aidl