Android 知识点<6> IPC机制

1. 背景介绍

1.1 进程 : 进程是指操作系统的一个执行单元,在Android系统中可以理解为一个应用或者程序

1.2 进程和线程的区别

(1)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元
(2)同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进行至少包括一个线程。
(3)进程的创建调用fork或者vfork,而线程的创建调用pthread_create,进程结束后它拥有的所有线程都将销毁,而线程的结束不会影响同个进程中的其他线程的结束
(4)线程是轻两级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的
(5)线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源
(6)线程有自己的私有属性TCB,线程id,寄存器、硬件上下文,而进程也有自己的私有属性进程控制块PCB,这些私有属性是不被共享的,用来标示一个进程或一个线程的标志


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

@Override
public 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();
}
}
2.在客户端A自然是需要发送消息给服务端B的  所以需要在服务绑定完成之后  获取到binder对象  之后用该对象构造一个Messenger对象  然后用messenger发送

消息给服务端即可  代码如下  :

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();
                }


            }
3.由于在服务端接收到了客户端的消息还需要回复  所以在服务端代码中获取 msg中的replyTo对象  用这个对象发送消息给 客户端即可 

 在客户端需要创建一个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即可  

  1. int check = checkCallingOrSelfPermission("aaa");  
  2.             if(check== PackageManager.PERMISSION_DENIED){  
  3.                 return null
  4.             }  

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值