Intent
共享文件
binder
contentprovider
网络通信:socket
bundle
Bundle实现了Parcelable接口,activity,service,receiver三大组件可以跨进程传输基础类型,序列化过的对象,及一些android支持的特殊对象
共享文件
android 基于linux,对并发读写没有限制,譬如两个线程对统一文件同时写操作都是允许的。所以,文件共享用于简单的文本信息传输,还可以通过序列化,在进程A序列化对象,在进程B反序列化得到对象来实现进程间通信
文件并发读写限制:试用于对数据同步要求不高的进程间通信,处理好并发读写的问题
sp:轻量级缓存方案,键值对的方式存储,底层实现是通过xml存储键值对,一般位于:/data/data/package name/shared_prefs/下。。sp属于文件的一种,但是系统对sp的读写有一定的缓存策略,即在内存中有一份sp文件的缓存,因此多进程下,系统对它的读写变的不可靠,高并发读写时候,数据很可能就丢失了,因此,一般进程间通信不用sp
Messenger
demo: https://github.com/SpikeKing/wcl-messenger-demo
https://www.jianshu.com/p/56ce3d9fc00d
Messenger基于AIDL实现, 顺序执行, 不支持并发
略像客户端原生和js交互,双向通信,需要双向注册信息回调
service
client
AIDL
demo:https://github.com/lijiacn/TestAIDL/tree/master
messenger:
1:串行的,对于并发不适用
2:通信,无法调用另一进程的方法
使用AIDL实现跨进程的方法调用
所有的AIDL文件大致可以分为两类。一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”
service:
aidl定义接口,service实现接口,clien调用接口,是现在service中,达到client调用service方法
AIDL 文件支持数据类型:
- 基本数据类型(int,long, char, boolean, double 等)
- String, CharSequence
- List: 只支持ArrayList, 里面每个元素都必须能够被AIDL支持
- Map: 只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
- Parcelable:所有实现Parcelable接口的对象
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
自定义对象
1:实现Parcelable接口
2:新建同名aidl文件。添加: parcelable 对象class
注意
1:aidl中除了基本数据类型,其余都需要标明:in,out,inout
2:aidl中只能声明方法,区别于传统接口
3:service端中的list使用CopyOnWriteArrayList:支持并发读写,aidl方法是在服务端的binder线程池中执行,多个client连接时候,需要在aidl方法中处理线程同步问题,CopyOnWriteArrayList进行自动线程同步。为啥aidl中能用ArrayList之外的list类型?aidl中所支持的是抽象的list,list只是一个接口,服务端返回的是CopyOnWriteArrayList,但是binder中会按照list的规范去访问数据,最终形成一个新的ArrayList传递给客户端,类比ConcurrentHashMap也一样
Book.aidl与Book.java的包名应当是一样的。这似乎理所当然的意味着这两个文件应当是在同一个包里面的——事实上,很多比较老的文章里就是这样说的,他们说最好都在 aidl 包里同一个包下,方便移植——然而在 Android Studio 里并不是这样。如果这样做的话,系统根本就找不到 Book.java 文件,从而在其他的AIDL文件里面使用 Book 对象的时候会报 Symbol not found 的错误。为什么会这样呢?因为 Gradle 。大家都知道,Android Studio 是默认使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度——问题就出在这里。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么理所当然系统是找不到这个 java 文件的。那应该怎么办呢?
又要 java文件和 aidl 文件的包名是一样的,又要能找到这个 java 文件——那么仔细想一下的话,其实解决方法是很显而易见的。首先我们可以把问题转化成:如何在保证两个文件包名一样的情况下,让系统能够找到我们的 java 文件?这样一来思路就很明确了:要么让系统来 aidl 包里面来找 java 文件,要么把 java 文件放到系统能找到的地方去,也即放到 java 包里面去。接下来我详细的讲一下这两种方式具体应该怎么做:
1:新建aidl文件,添加方法或变量,编译,同名接口内出现对应方法
2:新建service。binder桥接,使用接口.stub 实例
1:修改 build.gradle 文件:在 android{} 中间加上下面的内容:
sourceSets {
main {
java.srcDirs = ['src/main/java', 'src/main/aidl']
}
}
2:把 java 文件放到 java 包下去:把 Book.java 放到 java 包里任意一个包下,保持其包名不变,与 Book.aidl 一致。只要它的包名不变,Book.aidl 就能找到 Book.java ,而只要 Book.java 在 java 包下,那么系统也是能找到它的。但是这样做的话也有一个问题,就是在移植相关 .aidl 文件和 .java 文件的时候没那么方便,不能直接把整个 aidl 文件夹拿过去完事儿了,还要单独将 .java 文件放到 java 文件夹里去。
client:
1:connection
ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IMyAidlInterface mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
Log.i("TAG", "onServiceConnected: " + mIMyAidlInterface);
try {
mIMyAidlInterface.hello("World!!!");
} catch (RemoteException e) { e.printStackTrace(); } }
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i("TAG", "onServiceDisconnected: " + name);
}
};
2:action
Intent intent = new Intent();
//你定义的service的action
intent.setAction("example.admin.com.testaidl.MyService");
//这里你需要设置你应用的包名
intent.setPackage("example.admin.com.testaidl");
bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
场景:service数据变动,通知client。观察者模式
1:新建client端用于回调的AIDL接口:onNewBookArrived
2:新建服务端使用的AIDL文件:addBook,getBook,register(上面接口实例),unRegister
3:新建service,建list维护client回调接口,在service收到新书时候,通过onNewBookArrived逐个回调给client
难点
上面的场景,在进程unRegister监听时候,跟注册传的同一个对象,缺反注册失败,为什么?跨进层,是没法传输对象的,内存空间都不一样,传到service的对象,都生成了新的对象。。这时候就要用到系统专门提供的RemoteCallBackList:专门用于删除跨进层listener的接口。它是个泛型,支持管理任意的AIDL接口,因为所有的AIDL都继承自IInterFace,看下面就明白了:
public class RemoteCallbackList<E extends IInterface>
RemoteCallbackList
工作原理:
内部有个Map结构专门用来保存所有的AIDL回调,这个map的key是IBinder类型,value是CallBack类型,其中CallBack中封装了真正的listener,当客户端注册listener时候,它会把这个listener的信息存入mCallBack中,其中key和value获取方式如下:
IBinder key = listener.asBinder();
Callback value = new Callback(listener, cookie);
所以,虽说到service都是新对象,但是他们底层Binder对象是同一个,所以,解注册时候,遍历service所有的listener,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删除即可。
优势:
1:客户端进程终止后,它能自动移除客户端所注册的listener
2:RemoteCallbackList内部自动实现线程同步工作。
注意:
1:不能像list一样去操作它,它并不是一个list
2:遍历RemoteCallbackList,必须要成对出现:beginBroadcast(),finishBroadcast()。哪怕仅仅是获取其中元素个数
3:service方法本身是在binder线程池中执行,所以,没有特殊必要不用再service开辟线程执行任务
4:client端,connection方法都是主线程,也不要直接调用service端耗时方法
解决4.5ANR问题: client端调用service方法放到非ui线程即可
new Thread(new Runnable() {
@Override
public void run() {
//todo service方法调用
}
});
5:service调用client接口也是运行在binder线程池中,所以,涉及到client中操作ui的。需要注意
6:binder可能会意外死亡,大多是应为service进程意外停止
service端:
1:维护client监听的数组CopyOnWriteArrayList替换为RemoteCallBackList
2:数组操作修改一下:add 》 register,remvove 》unregister
3:通知监听者(client)。
interface CustomListener extends android.os.IInterface {
void method();
}
private RemoteCallbackList<CustomListener> callbackList = new RemoteCallbackList<>();
private void method() {
final int n = callbackList.beginBroadcast();
for (int i = 0; i < n; i++) {
CustomListener listener = callbackList.getBroadcastItem(i);
if (null != listener) {
try {
listener.method();
} catch (Exception e) {
e.printStackTrace();
}
}
}
callbackList.finishBroadcast();
}
binder意外死亡,重连服务
1:给Binder设置DeathRecipient监听,当Binder死亡时候,我们会收到binderDied方法的回调,在binderDied中我们可以重新连接远程服务
2:onServiceDisconnected中重连
区别
onServiceDisconnected在UI线程被回调,binderDied在客户端的Binder线程池中被回调
如何在AIDL中使用权限验证功能
service默认情况下,都是可以连接的。所以,需要在service添加权限验证,来过滤能访问的client
onBind中验证,验证不通过就返回null,这样client就没法绑定服务
验证方式:
- permission:onBind中checkCallingOrSelfPermission。。这种方式也适用于Messenger
1:声明所需权限:?
2:client申请权限 ?
3:service 验证权限
服务端的onTransact方法中进行权限验证,如果验证失败,就返回false,这样服务端就会终止AIDL中的方法从而达到保护服务端的效果
验证方式:?????????????
- permission:
- Uid和Pid验证
Binder连接池
ContentProvider
ContentProvider 系统提供的专门用于不同应用间进行数据共享的方式,这一点,天生适合进程间通信。
和Messenger一样,底层实现也是Binder
虽然系统已经封装的很好,使用起来相对简单,但是还是有一些注意问题:
1:CRUD
2:防止SQL注入
3:权限控制
自定义ContentProvider:
1:继承自ContentProvider
2:实现6个抽象函数
oncreate:创建,一些初始化工作
getType:返回一个Uri请求对应的MIME类型,比如图片,视频等,如果不关注这个类型,直接返回null或“/”
剩下4个方法,对应CRUD
根据binder运行原理:6个方法都运行在ContentProvider进程中,oncreate由系统回调并运行在主线程中,其他5个有外界回调并运行在binder线程池中
ContentProvider主要以表格的形式来组织数据,并且可以包含多个表,表具有行和列,行代表一条记录,列对应一条记录中的一个字段。类比db很像。。。ContentProvider还支持文件数据,如图片,视频等。android 系统的MediaStore就是文件类型的ContentProvider。ContentProvider对底层的数据存储方式没有要求,可以使用sqlite数据库,可以是普通文件,也可以是内存中的一个对象来进行数据存储
Socket
套接字,是网络通信中的概念,:流式套接字,用户数据报套接字。分别对应于网络的传输控制层中的TCP和UDP。
TCP是面向连接的协议,提供稳定的双向通信功能,TCP的连接需要经过三次握手才能完成,为了稳定的数据传输功能,其本身提供了超时重传机制,因此具有很高的稳定性。
UDP是无连接的,提供不稳定的单向通信功能,当然UDP也能实现双向通信功能。性能上,UDP具有更好的效率,其缺点是不保证数据一定能够正确传输,尤其是在网络拥塞的时候。
Socket本身可以支持传输任意字节流
注意点:
1:声明权限
2:不要放在主线程访问网络。1):程序无法在4.0及以上中运行,会抛出异常:android.os.NetworkOnMainThreadException。2):网络请求很可能是耗时的,影响效率
选择合适的IPC方式
名称 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程通信 |
文件共享 | 简单易用 | 不适合高并发场景,并且无法做到进程间的及时通信 | 无法并发访问,交换简单的数据实时性不高的场景 |
AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍微复杂,需要处理好线程同步 | 一对多通信且有RPC需求 |
Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好处理高并发情形,不支持RPC,数据通过Message进行传输,因此只能传输Bundle支持的数据类型 | 低并发的一对多即时通信,无RPC需求,或者无须要返回结果的RPC需求 |
ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法扩展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作 | 一对多的进程间数据共享 |
Socket | 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 | 实现细节稍微有点繁琐,不支持直接的RPC | 网络数据交换 |
网友评论