1.使用Bundle ----> 用于android四大组件间的进程间通讯
android的四大组件均可使用Bundle传递数据 因此若是要实现四大组件间的进程间通讯 彻底可使用Bundle来实现 简单方便
2.使用文件共享 ---->用于单线程读写
这种方式在单线程读写的时候比较好用 若是有多个线程并发读写的话须要限制线程的同步读写
另外 SharePreference是个特例 它底层基于xml实现 可是系统对它的读写会基于缓存,也就是说再多进程模式下就变得不那么可靠了,有很大概率丢失数据
3.使用Messenger ---->用于可存放在message中的数据的传递
使用这个方式能够在不一样进程间传递message对象 这是一种轻量级的IPC方案 当传递的对象能够放入message中时 能够考虑用这种方式 可是msg.object最好不要放
由于不必定能够序列化
使用它的步骤以下:
假设这样一个需求 须要在客户端A发送消息给服务端B接受 而后服务端B再回复给客户端A java
1. 首先是客户端A发送消息给服务端B 因此在客户端A中 声明一个Handler用来接受消息 并建立一个Messenger对象 用Handler做为参数构造 而后onBinder方法返回messenger.getBinder() 便可android
2.在客户端A天然是须要发送消息给服务端B的 因此须要在服务绑定完成以后 获取到binder对象 以后用该对象构造一个Messenger对象 而后用messenger发送
消息给服务端便可 代码以下 :sql
3.因为在服务端接收到了客户端的消息还须要回复 因此在服务端代码中获取 msg中的replyTo对象 用这个对象发送消息给 客户端便可
在客户端须要建立一个handler和Messenger 将发送的msg.replyTo设置成Messenger对象 就可
4.AIDL android 接口定义语言 ---->主要用于调用远程服务的方法的状况 还能够注册接口
使用方法很简单
在服务端定义aidl文件 自动生成java文件 而后在service中实现这个aidl 在onbind中返回这个对象
在客户端把服务端的aidl文件彻底复制过来 包名必须彻底一致 在onServiceConnected方法 中 把 Ibinder对象 用asInterface方法转化成 aidl对象
而后调用方法便可
须要注意的地方:
在aidl文件中并非支持全部类型
仅支持以下6种类型:
基本数据类型---- int long char boolean double
String charSequence
List 只支持ArrayList CopyOnWriteArrayList也能够。。 里面元素也必须被aidl支持
Map 只支持HashMap ConCurrentHashMap也能够 里面元素也必须支持aidl
Parcelable 全部实现了此接口的对象
AIDL 全部的AIDL接口 所以 若是须要使用接口 必须使用AIDL接口
其中自定义的类型和AIDL对象必须显示import进来 不论是不是在一个包中
若是AIDL文件中用到了自定义的Parcelable对象 必须建立同名的AIDL文件 并声明为Parcelable类型
AIDL文件中除了基本数据类型外 其余类型必须标上方向 in out inout
AIDL接口中只支持方法 不支持声明静态常量
在使用aidl时 最好把全部aidl文件都放在一个包中 这样方便复制到客户端
其实全部的跨进程对象传递都是对象的序列化与反序列化 因此必须包名一致
如今加入有这样一个需求 若是服务端是 图书馆添加和查看书的任务 客户端能够查看和添加书 这时候须要添加一个功能 当服务端每添加了一本书
须要通知客户端注册用户 有一本新书上架了 这个功能如何实现?
想一想可知 这是一个观察者模式 若是在同一进程中很容易实现,只须要在服务端中的代码中维护一个集合 里面放的是注册监听的用户 而后用户须要实现一个新书到来的回调接口
当有新书上架时 遍历这个集合 调用每一个注册者的接口方法 便可实现
如今咱们是跨进程通讯 因此天然不能如此简单了 但也不是很复杂 想想 其实就是把以往的接口定义 变成了aidl接口定义 而后其余的同样便可
可是这样仍是存在一个问题 若是注册了listener 咱们又想解除注册 是否是在客户端传入listener对象 在服务端把它移除就能够呢?
实际上是不能够的 由于这是跨进程的 因此对象并非真正的传递 只是在另外一个进程中从新建立了一个同样的对象 内存地址不一样 因此根本不是同一个对象
因此是不能够的 若是要解决这个问题 须要使用RemoteCallbackList 类 不要使用CopyWriteArrayList
在RemoteCallBackList中封装了一个Map 用来保存全部的AIDL回调 key为IBinder value是CallBack 使用IBinder 来区别不一样的对象 ,
由于跨进程传输时会产生不少个不一样的对象 但这些对象的底层的Binder都是同一个对象 因此能够
在使用RemoteCallBackList时 add 变为 register remove 变为 unregister 遍历的时候须要先 beginBroadcast 这个方法同时也获取集合大小
获取集合中对象使用 getBoardCastItem(i) 最后不要忘记finishBoardCast方法
还有一个状况 因为onServiceConnected方法 是在主线程执行的 若是在这里执行服务端的耗时代码 会ANR 因此须要开启一个子线程执行
同理在服务端中 也不能够运行客户端的耗时程序
总结起来就是 在执行其余进程的耗时程序时 都须要开启另外的线程防止阻塞UI线程 若是要访问UI相关的东西 使用handler
为了程序的健壮性 有时候Binder可能意外死亡 这时候须要重连服务 有2种方法:
1.在onServiceDisconnected方法中 重连服务
2. 给Binder注册DeathRecipient监听 当binder死亡时 咱们能够收到回调 这时候咱们能够重连远程服务
最后有时候咱们不想全部的程序均可以访问咱们的远程服务 因此能够给服务设置权限和过滤:
1.咱们在onbind中进行校验 用某种方式 若是验证不经过那么就直接返回null
2.咱们能够在服务端的AndroidMiniFest.xml中 设置所需的权限 <permission android:name="aaaaaa" android:protectionLevel="normal"/>
而后在onbind中 检查是否有这个权限了 若是没有那么直接返回null便可 判断方法以下 :数组
3.能够在onTransact方法中 进行权限验证 若是验证失败直接返回false 能够采用permission方法验证 还能够用Uid和Pid验证 不少方法
其中声明权限与 添加权限的方式 是 在Service所在的AndroidMinifest中 声明权限
好比 <permission android:name="com.yangsheng.ydzd_lb.myaidlpro.book" android:protectionLevel="normal"></permission>
而后在 须要远程调用的 app中添加 这个权限 <uses-permission android:name="com.yangsheng.ydzd_lb.myaidlpro.book"/>
这样 就能够在 onbind中验证权限了缓存
至此 AIDL 大致介绍完了 之后须要在使用中提高了网络
aidl demo 下载 : http://download.csdn.NET/detail/u012760183/9520173多线程
5.ContentProvider方式 实现对另外一个应用进程开放provider数据的查询
此方法使用起来也比较简单 底层是对Binder的封装 使之能够实现进程间通讯 使用方法以下
1. 在须要共享数据的应用进程中创建一个ContentProvider类 重写它的CRUD 和getType方法 在这几个方法中调用对本应用进程数据的调用
而后在AndroidMinifest.xml文件中声明provider 并发
2. 在须要获取共享数据的应用进程中调用getContentResolver().crud方法 便可实现数据的查询
须要注意的问题:
1.关于 sqlite crud的各个参数的意义
query函数 参数
Cursor query(boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
第一个参数 distinct 英语单词意思 独特的 若是true 那么返回的数据都是惟一的 意思就是实现查询数据的去重
第二个参数 table 表名
第三个参数 columns 要查询的行的名字数组 例如 new String[]{"id","name","sex"}
第四个参数 selection 选择语句 sql语句中where后面的语句 值用?代替 例如 "id=? and sex=?"
第五个参数 selectionArgs 对应第四个参数的 ? 例如 new String[]{"1","男"}
第六个参数 groupBy 用于分组
第七个参数 having 筛选分组后的数据
第八个参数 orderby 用于排序 desc/asc 升序和降序 例如 id desc / id asc
最后一个参数 limit 用于限制查询的数据的个数 默认不限制
其余几个函数 根据query函数的参数猜测便可
2.因为每次ipc操做 都是靠uri来区别 想要获取的数据位置 因此provider在调取数据的时候根据uri并不知道要查询的数据是在哪一个位置
因此咱们能够经过 UriMatcher 这个类来给每一个uri标上号 根据编号 对应适当的位置 例如:app
3.另外ContentProvider除了crud四个方法外,还支持自定义调用 经过ContentProvider 和ContentResolver的 call方法 来实现 异步
ContentProviderdemo下载 : http://download.csdn.Net/detail/u012760183/9520175
6.Socket方法实现Ipc 这种方式也能够实现 可是不经常使用
须要权限
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
这种方式须要一个服务端socket 和一个客户端socket 创建链接后 经过流循环获取消息便可
1.在服务端开启一个serverSocket 不断获取客户端链接 注意要在子线程中开启
2.在客户端开启一个线程 使用ip和端口号链接服务端socket 链接成功后 同样 开启子线程 循环获取消息 处理
7.Binder 链接池的使用 很好用
咱们在android中进程间通讯 通常都使用 AIDL实现 由于它强大 可是普通的使用方法每次使用AIDL 都须要开启一个服务 若是有多个AIDL请求 那岂不是要开启不少个服务
这明显是不能够的 好比你让你用户的手机 发现你这一个应用程序绑定了10个服务 那是极差的 因此 咱们在多个AIDL 请求的时候可使用Binder链接池技术
只开启一个服务 根据须要获取的AIDL不一样 转化成须要的AIDL 接口 执行不一样的方法
实现的基本原理 就是在onbind中返回一个BinderPool 接口 这个接口有个方法 能够根据不一样的标志位返回不一样的aidl接口 这样咱们在asInTerface以后调用哪一个方法
传入标志位便可返回须要的aidl接口
提及来简单 让咱们来实现一个试试吧
1.假设原来有2个AIDL接口须要实现(能够扩展成多个) 在服务端创建好AIDL文件 而且创建一个IBinderPool aidl接口 只有一个查询binder的方法 用于查询须要的binder
interface IBinderPool {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
IBinder queryBinder(int code); //此方法返回Ibinder 用于转化成须要的AIDL接口
}
2.在服务端 onbind方法中返回 IBinderPool的实现类 实现query方法 按照传入的code 返回须要的ibinder
@Override
public IBinder onBind(Intent intent) {
return iBinderPool;
}
private Binder iBinderPool = new IBinderPool.Stub() {
@Override
public IBinder queryBinder(int code) throws RemoteException {
switch (code) {
case 1:
return new IBookManger.Stub() {
@Override
public void getBook() throws RemoteException {
System.out.println("--->book");
}
};
case 2:
return new IPersonManager.Stub() {
@Override
public void getPerson() throws RemoteException {
System.out.println("---->person");
}
};
}
return null;
}
};
3.客户端实现一个BinderPool类 这个类主要是封装了 AIDL的一些实现方法 方便调用罢了 其中 涉及到一个能够实现同步机制的类
CountDownLatch 这个类 当他的值 不是0的时候 执行了await方法后会使方法一直停在await处 不进行 直到他的值变成了0 才能够继续执行
也就是说 当执行了await方法 这个线程就会阻塞 等待这个数值变到0后继续执行
而在BinderPool中的应用场景是这样的
private void connectService(){
countDownLatch = new CountDownLatch(1); //实现同步机制
Intent intent = new Intent();
intent.setClass(ctx,MyService.class);
ctx.bindService(intent,connection,Context.BIND_AUTO_CREATE);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
首先为何在这里要使用同步机制 咱们要搞清楚 让咱们看这个方法调用的时机 :
binderPool = BinderPool.getInstance(MainActivity.this); //connectService方法 是在这个方法中调用的
IBinder iBinder = binderPool.queryBinder(2);
iPersonManager = IPersonManager.Stub.asInterface(iBinder);
try {
iPersonManager.getPerson();
} catch (RemoteException e) {
e.printStackTrace();
}
由于咱们最终的目的是在bind服务 链接到远程服务以后获取到 binderPool对象调用它的 binderPool.queryBinder(2) 方法 若是不加同步机制
异步执行 就有可能在 connectService方法 执行完以后 执行IBinder iBinder = binderPool.queryBinder(2);这行代码的时候binderPool对象还
没有被赋值 这样就会产生问题 因此咱们让 connectService方法 阻塞 当BinderPool中的 binderPool对象赋值以后 让CountDownLatch的值countDown到0
这样 connectService方法就会继续执行 而后执行下一行代码了
BinderPool demo下载 : http://download.csdn.net/detail/u012760183/9520188
最后 总结了这么多IPC通讯方式 那咱们该如何选择合适的IPC方式呢 针对这几种IPC通讯方式分析一下优缺点 1.bundle : 简单易用 可是只能传输Bundle支持的对象 经常使用于四大组件间进程间通讯 2.文件共享: 简单易用 但不适合在高并发的状况下 而且读取文件须要时间 不能即时通讯 经常使用于并发程度不高 而且实时性要求不高的状况 3.AIDL : 功能强大 支持一对多并发通讯 支持即时通讯 可是使用起来比其余的复杂 须要处理好多线程的同步问题 经常使用于一对多通讯 且有RPC 需求的场合(服务端和客户端通讯) 4.Messenger : 功能通常 支持一对多串行通讯 支持实时通讯 可是不能很好处理高并发状况 只能传输Bundle支持的类型 经常使用于低并发的无RPC需求一对多的场合 5.ContentProvider : 在数据源访问方面功能强大 支持一对多并发操做 可扩展call方法 能够理解为约束版的AIDL 提供CRUD操做和自定义函数 经常使用于一对多的数据共享场合 6.Socket : 功能强大 能够经过网络传输字节流 支持一对多并发操做 可是实现起来比较麻烦 不支持直接的RPC 经常使用于网络数据交换 总结起来 当仅仅是跨进程的四大组件间的传递数据时 使用Bundle就能够 简单方便 当要共享一个应用程序的内部数据的时候 使用ContentProvider实现比较方便 当并发程度不高 也就是偶尔访问一次那种 进程间通讯 用Messenger就能够 当设计网络数据的共享时 使用socket 当需求比较复杂 高并发 而且还要求实时通讯 并且有RPC需求时 就得使用AIDL了 文件共享的方法用于一些缓存共享 之类的功能