25.IPC跨进程通信实践
目录介绍
- 01.整体概述
- 1.1 项目背景
- 1.2 遇到问题
- 1.3 基础概念
- 1.4 设计目标
- 02.IPC通信方式
- 2.1 使用Intent
- 2.2 使用文件共享
- 2.3 使用Messenger
- 2.4 使用AIDL
- 2.5 使用ContentProvider
- 2.6 使用Socket
- 03.跨进程要点
- 3.1 如何开启多进程
- 3.2 数据序列化
- 3.7 方案的对比选择
- 05.方案基础设计
- 5.1 整体架构图
- 5.2 UML设计图
- 5.3 关键流程图
- 5.4 接口设计图
- 5.5 模块间依赖关系
- 06.其他设计说明
- 6.1 性能设计
- 6.2 稳定性设计
- 6.3 灰度设计
- 6.4 降级设计
- 6.5 异常设计
01.整体概述
1.1 项目背景
- 进程之间如何通信
1.2 遇到问题
1.3 基础概念
- IPC(Inter-Process Communication)
- 含义即为进程间通信或者翻译为跨进程通信,是指两个进程之间进行数据交换的过程。
- 一般情况下,在 Android系统中一个应用就只享有一个进程
- 在最简单的情况下一个进程可以只包含有一个线程(当然,一般情况下是不可能的),即主线程,也称为 UI 线程
- 有时候应用因为某些原因需要采用多进程模式
- 此时如果要在应用内的不同进程间进行通信,就需要使用到 IPC 机制。或者是两个不同的应用需要进行数据交换,此时也一样需要依靠 Android 系统提供的 IPC 方案。
1.4 设计目标
02.IPC通信方式
- 使用Intent
- Activity,Service,Receiver 都支持在 Intent 中传递 Bundle 数据,而 Bundle 实现了 Parcelable 接口,可以在不同的进程间进行传输。
- 使用文件共享
- 可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(注意:并不是同一个对象,只是内容相同。)
- 使用Messenger
- Messenger 是一种轻量级的 IPC 方案,它的底层实现是 AIDL ,可以在不同进程中传递 Message 对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。
- 使用AIDL
- 服务端,创建Service用于监听客户端的连接请求,然后创建一个AIDL接口文件,里面是将要实现的方法,注意这个方法是暴露给客户端的的,在Service中实现这个AIDL接口即可。
- 客户端,先绑定服务端Service,绑定成功后,将服务端返回的Binder对象转换成AIDL接口所属的类型,最后调用AIDL的方法就可以了。
- 使用ContentProvider
- 许多如通讯录、公共相册、日程表,需要跨进程访问。使用方法:继承 ContentProvider 类实现抽象方法,除 onCreate 运行在主线程里,其他五个方法均由外界回调运行在 Binder 线程池中。
- ContentProvider 的底层数据,可以是 SQLite 数据库,可以是文件,也可以是内存中的数据。
2.1 使用Intent
- 使用介绍
- Activity,Service,Receiver 都支持在 Intent 中传递 Bundle 数据,而 Bundle 实现了 Parcelable 接口,可以在不同的进程间进行传输。
- 举个使用场景例子
- App通过intent调用打电话;App获取手机存储中图片资源;App监听手机屏幕亮屏和灭屏事件;App分享文件到QQ;等等,都是属于跨进程通信。
2.2 使用文件共享
- 使用介绍
- Windows 上,一个文件如果被加了排斥锁会导致其他线程无法对其进行访问,包括读和写;而 Android 系统基于 Linux ,使得其并发读取文件没有限制地进行,甚至允许两个线程同时对一个文件进行读写操作,尽管这样可能会出问题。
- 可以在一个进程中序列化一个对象到文件系统中,在另一个进程中反序列化恢复这个对象(注意:并不是同一个对象,只是内容相同)。SharedPreferences 是个特例,系统对它的读 / 写有一定的缓存策略,即内存中会有一份 ShardPreferences 文件的缓存,系统对他的读 / 写就变得不可靠,当面对高并发的读写访问,SharedPreferences 有很多大的几率丢失数据。因此,IPC 不建议采用 SharedPreferences。
- 举个使用场景例子
- 待完善
2.3 使用Messenger
- Messenger 是一种轻量级的 IPC 方案
- 它的底层实现是 AIDL ,可以在不同进程中传递 Message 对象,它一次只处理一个请求,在服务端不需要考虑线程同步的问题,服务端不存在并发执行的情形。
- 服务端进程:
- 服务端创建一个 Service 来处理客户端请求,同时通过一个 Handler 对象来实例化一个 Messenger 对象,然后在 Service 的 onBind 中返回这个 Messenger 对象底层的 Binder 即可。
- 客户端进程:
- 首先绑定服务端 Service ,绑定成功之后用服务端的 IBinder 对象创建一个 Messenger ,通过这个 Messenger 就可以向服务端发送消息了,消息类型是 Message 。如果需要服务端响应,则需要创建一个 Handler 并通过它来创建一个 Messenger(和服务端一样),并通过 Message 的 replyTo 参数传递给服务端。服务端通过 Message 的 replyTo 参数就可以回应客户端了。
- 注意: 客户端和服务端是通过拿到对方的 Messenger 来发送 Message 的。
- 客户端通过 bindService onServiceConnected 而服务端通过 message.replyTo 来获得对方的 Messenger 。
- Messenger中有一个Handler以串行的方式处理队列中的消息。不存在并发执行,因此我们不用考虑线程同步的问题。
2.4 使用AIDL
- AIDL 是什么
- AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务端和客户端通信接口的一种描述语言,可以拿来生成用于 IPC 的代码。从某种意义上说 AIDL 其实是一个模板,因为在使用过程中,实际起作用的并不是 AIDL 文件,而是据此而生成的一个 IInterface 的实例代码,AIDL 其实是为了避免我们重复编写代码而出现的一个模板
- Messenger 是以串行的方式处理客户端发来的消息,如果大量消息同时发送到服务端,服务端只能一个一个处理,所以大量并发请求就不适合用Messenger,而且Messenger只适合传递消息,不能跨进程调用服务端的方法。
- AIDL 可以解决并发和跨进程调用方法的问题,要知道Messenger本质上也是AIDL,只不过系统做了封装方便上层的调用而已。
- AIDL文件支持的数据类型
- 基本数据类型,比如int,long,double;String 和 CharSequence
- ArrayList ,里面的元素必须能够被 AIDL 支持;HashMap ,里面的元素必须能够被 AIDL 支持;
- Parcelable ,实现 Parcelable 接口的对象,注意:如果 AIDL 文件中用到了自定义的 Parcelable 对象,必须新建一个和它同名的 AIDL 文件。
- AIDL ,AIDL 接口本身也可以在 AIDL 文件中使用。
- 如何使用
- 服务端创建一个 Service 用来监听客户端的连接请求,然后创建一个 AIDL 文件,将暴露给客户端的接口在这个 AIDL 文件中声明,最后在 Service 中实现这个 AIDL 接口即可。
- 绑定服务端的 Service ,绑定成功后,将服务端返回的 Binder 对象转成 AIDL 接口所属的类型,然后就可以调用 AIDL 中的方法了。
- 户端调用远程服务的方法,被调用的方法运行在服务端的 Binder 线程池中,同时客户端的线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞,导致 ANR 。
- 客户端的 onServiceConnected 和 onServiceDisconnected 方法都在 UI 线程中。
- 服务端
- 步骤1:新建定义AIDL文件【特别注意包名保持一致】,并声明该服务需要向客户端提供的接口。补充,如果aidl中有对象,则需要创建对象,并且实现Parcelable
- 步骤2:在Service子类中实现AIDL中定义的接口方法,并定义生命周期的方法
- 步骤3:在AndroidManifest.xml中注册服务 & 声明为远程服务
- 客户端
- 步骤1:拷贝服务端的AIDL文件到目录下【需要把service端所有的aidl都拷贝到client来】
- 步骤2:使用Stub.asInterface接口获取服务器的Binder,根据需要调用服务提供的接口方法
- 步骤3:通过Intent指定服务端的服务名称和所在包,绑定远程Service
- 遇到一些问题
- aidl接口是如何定义?
- 定义后aidl接口如何编译生成.java文件?
- aidl通信是单向还是双向?单向通信,客户端调
- 其他注意点:注意的是需要先启动service端的apk,在启动client端的apk
- 不同的场景
- 场景一、service 和 client 在同一个 project 中。service项目可以直接依赖client项目
- 场景二、service 和 client 分属于不同的 project 中。将service项目中的 aidl 文件夹原封不动的拷贝到client项目中的对应位置
2.5 使用ContentProvider
- 用于不同应用间数据共享,和 Messenger 底层实现同样是 Binder 和 AIDL,系统做了封装,使用简单。
- 系统预置了许多 ContentProvider ,如通讯录、日程表,图片等,需要跨进程访问。
- 使用方法:继承 ContentProvider 类实现 6 个抽象方法,这六个方法均运行在 ContentProvider 进程中,除 onCreate 运行在主线程里,其他五个方法均由外界回调运行在 Binder 线程池中。ContentProvider 的底层数据,可以是 SQLite 数据库,可以是文件,也可以是内存中的数据。
2.6 使用Socket
- Socket起源于 Unix,而 Unix 基本哲学之一就是“一切皆文件”
- 都可以用“打开 open –读写 write/read –关闭 close ”模式来操作。Socket 就是该模式的一个实现,网络的 Socket 数据传输是一种特殊的 I/O,Socket 也是一种文件描述符。Socket 也具有一个类似于打开文件的函数调用: Socket(),该函数返回一个整型的Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。
- 常用的 Socket 类型有两种:
- 流式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DRAM)。流式是一种面向连接的 Socket,针对于面向连接的 TCP 服务应用;数据报式 Socket 是一种无连接的 Socket ,对应于无连接的 UDP 服务应用。Socket 本身可以传输任意字节流。
- Socket 是连接应用层与传输层之间接口(API)。
03.跨进程要点
3.1 如何开启多进程
- 为一个 Android 应用开启多进程模式的方法有两种。
- 第一种方法是在 AndroidManifest 中为四大组件指定 android:process 属性,为其声明要在哪个进程名下运行,即可开启多进程。
- 第二种方法是通过 JNI 在 native 层中 fork 一个新的进程。这里只讨论第一种方法。
- Android 应用默认在命名为包名的进程下运行
- 除非你为其指定了 android:process 属性
- 虽然开启多进程的方法并不算麻烦,但当应用开启了多进程后,其实会对来很多的负面影响,主要有以下几个:
- 静态成员与单例模式失效
- 线程同步机制失效
- SharedPreferences 可靠性下降
- Application 被创建多次
- 为了解决多进程带来的问题
- 系统也为开发者提供了很多的跨进程通信方式,比如文件共享、ContentProvider、Messenger、AIDL、Socket 等
3.2 数据序列化
- 必须对数据序列化
- 跨进程通信的目的就是为了进行数据交换,但并不是所有的数据类型都能被传递,除了基本数据类型外,还必须是实现了序列化和反序列化的数据类型才可以,即实现了 Serializable 接口或 Parcelable 接口的数据类型
07.进程间通信问题
- 程序世界中,存在着大量的
通信
场景。搜索我们的知识,解决进程间通信
问题有以下几种方式: - 管道
- 普通管道pipe:一种
半双工
的通信方式,数据只能单向流动
,而且只能在具有亲缘关系
的进程间使用。 - 命令流管道s_pipe:
全双工
,可以同时双向传输 - 命名管道FIFO:
半双工
的通信方式,允许
在无亲缘关系
的进程间通信。
- 普通管道pipe:一种
- 消息队列 MessageQueue:
消息的链表
,存放在内核
中 并由消息队列标识符
标识。- 消息队列克服了
信号传递信息少
、管道
只能承载无格式字节流
以及缓冲区大小受限
等缺点。
- 共享存储 SharedMemory:
- 映射一段
能被其他进程所访问
的内存,这段共享内存由一个进程创建
,但多个进程都可以访问
。 - 共享内存是
最快的 IPC 方式
,它是针对其他
进程间通信方式运行效率低
而专门设计的。 - 往往与其他通信机制一同使用,如
信号量
配合使用,来实现进程间的同步和通信。
- 映射一段
- 信号量 Semaphore:
- 是一个
计数器
,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制
,防止某进程正在访问共享资源时, - 其他进程也访问该资源,实现
资源的进程独占
。因此,主要作为进程间
以及同一进程内线程间
的同步手段。
- 是一个
- 套接字 Socket:
- 与其他通信机制不同的是,它可以
通过网络
,在不同机器之间
进行进程通信。
- 与其他通信机制不同的是,它可以
- 信号 signal:
- 用于通知接收进程
某事件已发生
。机制比较复杂。
- 用于通知接收进程
其他介绍
01.关于博客汇总链接
02.关于我的博客
- github:https://github.com/yangchong211
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 开源中国:https://my.oschina.net/zbj1618/blog
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e