Android 知识点<6> IPC机制
1. 背景介绍
1.1 进程 : 进程是指操作系统的一个执行单元,在Android系统中可以理解为一个应用或者程序
1.2 进程和线程的区别
1.3 Android 的多进程
1.3.1 因为Android对单个应用的大小是有控制的,所以可以采用多进程的方式进行避免。
1.3.2 通过android:process 属性,来启动Android的多进程。其中 android:process= ": remote" .":"开头代表是私有进程
1.3.3 不同进程,可以通过ShareUID方式,在同一个进程中运行(ShareUID相同,签名相同,在Manifest文件设置)
1.3.4 多进程运行的缺点:
1. 静态成员和单例失效
2. 线程同步机制失效
3. Sharepreference 失效
4. Application会多次创建
2. IPC 的一些基本概念
2.1 序列化 - Serializable 接口
2.1.1 Serializable的一些说明:
- 对象的序列化处理非常简单,只需对象实现了Serializable 接口即可(该接口仅是一个标记,没有方法)
- 序列化的对象包括基本数据类型,所有集合类以及其他许多东西,还有Class 对象
- 对象序列化不仅保存了对象的“全景图”,而且能追踪对象内包含的所有句柄并保存那些对象;接着又能对每个对象内包含的句柄进行追踪
- 使用transient关键字修饰的的变量,在序列化对象的过程中,该属性不会被序列化。
2.1.2 序列化的步骤:
- 首先要创建某些OutputStream对象:OutputStream outputStream = new FileOutputStream("output.txt")
- 将其封装到ObjectOutputStream对象内:ObjectOutputStream OutputStream = new ObjectOutputStream(outputStream);
- 此后只需调用writeObject()即可完成对象的序列化,并将其发送给OutputStream:objectOutputStream.writeObject(Object);
- 最后不要忘记关闭资源:objectOutputStream.close(), outputStream .close();
2.1.3 反序列化的步骤:
- 首先要创建某些OutputStream对象:InputStream inputStream= new FileInputStream("output.txt")
- 将其封装到ObjectInputStream对象内:ObjectInputStream objectInputStream= new ObjectInputStream(inputStream);
- 此后只需调用writeObject()即可完成对象的反序列化:objectInputStream.readObject();
- 最后不要忘记关闭资源:objectInputStream.close(),inputStream.close();
2.1.4 serialversionid 的作用,用于反序列化时候的验证,如果相同,则可以进行反序列化,如果不同会报异常
2.2 Android 序列化 - Parcelable
2.2.1 describeContents就是负责文件描述,首先看一下源码的解读
2.2.2 序列化 : 通过writeToParcel方法实现序列化,writeToParcel返回了Parcel,所以我们可以直接调用Parcel中的write方法,基本的write方法都有,对象和集合比较特殊下面单独讲,基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储
2.2.3 反序列化 :反序列化需要定义一个CREATOR的变量,上面也说了具体的做法,这里可以直接复制Android给的例子中的,也可以自己定义一个(名字千万不能改),通过匿名内部类实现Parcelable中的Creator的接口
2.3.4 parcelable 和 serializable 的区别 :
Parcelable和Serializable都是实现序列化并且都可以用于Intent间传递数据,Serializable是Java的实现方式,可能会频繁的IO操作,所以消耗比较大,但是实现方式简单 Parcelable是Android提供的方式,效率比较高,但是实现起来复杂一些 , 二者的选取规则是:内存序列化上选择Parcelable, 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)
可以序列化的对象 : 基本数据类型 +
3. Binder
3.1 Binder基于Client-Server通信模式,传输过程只需一次拷贝,为发送发添加UID/PID身份,支持实名Binder和匿名Binder,安全性高。
3.2 Binder 通信模型
Binder框架定义了四个角色:Server,Client,ServiceManager(以后简称SMgr)以及Binder驱动。其中Server,Client,SMgr运行于用户空间,驱动运行于内核空间。这四个角色的关系和互联网类似:Server是服务器,Client是客户终端,SMgr是域名服务器(DNS),驱动是路由器。
3.3 Binder 通信流程
3.4 Binder的两个重要方法,处理binder断开的情况 .linkToDeath ,unlinkToDeath .
声明DeathRecipient ,然后重写binderDied(),处理断开的情况
在客户端bind成功后,给binder 设置死亡代理 : binder.linkToDeath(mDeathRecipient , 0) . 这样当binder断开的时候我们将收到通知。
也可以使用binder.isBinderAlive 进行判断
-------------------------------分割线,下面进入重点内容 -》 android IPC 的几种方式 ----------------------------
方式1 : 使用Bundle
android的四大组件都可使用Bundle传递数据,用intent携带过去。 所以如果要实现四大组件间的进程间通信 完全可以使用Bundle来实现 简单方便 。
bundle目前支持的数据格式 :基本数据类型,String,数组,Arraylist
对于不支持的类型,可以在后台计算之后进行传输。
方式2 :使用文件共享
这种方式在单线程读写的时候比较好用 如果有多个线程并发读写的话需要限制线程的同步读写
另外 SharePreference是个特例 它底层基于xml实现 但是系统对它的读写会基于缓存,也就是说再多进程模式下就变得不那么可靠,有很大几率丢失数据
方式3 :Messager
使用这个方式可以在不同进程间传递message对象 这是一种轻量级的IPC方案 当传递的对象可以放入message中时 可以考虑用这种方式 但是msg.object最好不要放,因为不一定可以序列化
使用它的步骤如下:
假设这样一个需求 需要在客户端A发送消息给服务端B接受 然后服务端B再回复给客户端A
1. 首先是客户端A发送消息给服务端B 所以在客户端A中 声明一个Handler用来接受消息 并创建一个Messenger对象 用Handler作为参数构造 然后onBinder方法返回messenger.getBinder() 即可
public class MyServiceService extends Service { private class MessageHandler extends Handler{ //创建的接受消息的handler @Overridepublic void handleMessage(Message msg) { switch (msg.what){ case 1: Bundle bundle = msg.getData(); String str = bundle.getString("aaa"); System.out.println("----"+str); Messenger replyTo = msg.replyTo; //此处往下是用来回复消息给客户端的 Message replyMsg = Message.obtain(null,2); Bundle bundle1 = new Bundle(); bundle1.putString("bbb","remote222给主进程回复消息啦"); replyMsg.setData(bundle1); try { replyTo.send(replyMsg); } catch (RemoteException e) { e.printStackTrace(); } break; } super.handleMessage(msg); } } Messenger messenger = new Messenger(new MessageHandler()); public MyServiceA() { } public IBinder onBind(Intent intent) { return messenger.getBinder(); } } |
消息给服务端即可 代码如下 :
public void onServiceConnected(ComponentName name, IBinder service) { Messenger messenger = new Messenger(service); Message msg = Message.obtain(null,1); Bundle bundle = new Bundle(); bundle.putString("aaa", "主进程给remote22进程发消息啦"); msg.setData(bundle); msg.replyTo = mmessenger; //这行代码用于客户端A接收服务端请求 设置的消息接收者 try { messenger.send(msg); } catch (RemoteException e) { e.printStackTrace(); } } |
在客户端需要创建一个handler和Messenger 将发送的msg.replyTo设置成Messenger对象 就可
方式4 : AIDL
实现方式
在服务端定义aidl文件 自动生成java文件 然后在service中实现这个aidl 在onbind中返回这个对象
在客户端把服务端的aidl文件完全复制过来 包名必须完全一致 在onServiceConnected方法 中 把 Ibinder对象 用asInterface方法转化成 aidl对象
然后调用方法即可
支持类型:
基本数据类型 ---- int long char boolean double
String charSequence
List 只支持ArrayList CopyOnWriteArrayList也可以。。 里面元素也必须被aidl支持
Map 只支持HashMap ConCurrentHashMap也可以 里面元素也必须支持aidl
Parcelable 所有实现了此接口的对象
AIDL 所有的AIDL接口 因此 如果需要使用接口 必须使用AIDL接口
为了程序的健壮性 有时候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验证
方式5 : ContentProvider
此方法使用起来也比较简单 底层是对Binder的封装 使之可以实现进程间通信 使用方法如下
1. 在需要共享数据的应用进程中建立一个ContentProvider类 重写它的CRUD 和getType方法 在这几个方法中调用对本应用进程数据的调用
然后在AndroidMinifest.xml文件中声明provider
<provider android:authorities="com.yangsheng.book" //这个是用来标识provider的唯一标识 路径uri也是这个 android:name=".BookProdiver" android:process=":remote_provider"/> //此句为了创建多进程 正常不需要使用 |
2. 在需要获取共享数据的应用进程中调用getContentResolver().crud方法 即可实现数据的查询
3. 由于每次ipc操作 都是靠uri来区别 想要获取的数据位置 所以provider在调取数据的时候根据uri并不知道要查询的数据是在哪个位置
所以我们可以通过 UriMatcher 这个类来给每个uri标上号 根据编号 对应适当的位置
方式6 ; socket 通信
关于IPC通信方式的对比和选取 :
最后 总结了这么多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了
文件共享的方法用于一些缓存共享 之类的功能
参考 :
1. 主要根据 Android 开发艺术探索
2. 参考 http://blog.csdn.net/u012760183/article/details/51397014