Android开发经验谈Android技术知识Android开发

Android 开发艺术探索学习笔记(二)

2019-12-13  本文已影响0人  aJIEw
Part 2

结合 官方文档 阅读《Android 开发艺术探索》时所做的学习笔记。本篇记录第二章:IPC 机制。

What? How? Why?

什么是 IPC?

所谓的 IPC 是指进程间通信(Interprocess communication 的缩写),我们知道 Linux 里可以通过管道、signal 等进行重定向、跨进程通信等,Android 中同样如此。一般情况下只要应用中使用了多个进程,那么就会涉及到进程间通信的问题。另外,由于每一个 Android 应用都是一个沙箱,独享一个进程,所以应用间数据共享同样也需要通过 IPC。

有哪些 IPC 的方式?

Android 中进程间通信的方式有很多,比如最常见的通过 Intent.putExtra 以及 Content Provider,通过文件共享(需要注意并发读/写问题),通过 Socket,以及最重要的一种 IPC 方式,通过 绑定服务,其中最重要的部分是 Binder,MessagerAIDL 底层实现也是通过 Binder。

为什么需要 IPC

首先,当应用 A 启动的时候,Android 系统会为它单独指定一个进程,分配一个 User ID,于是它只能在这个进程里执行自己的代码,访问自己的数据空间。但是如果我们为应用 A 指定了多个进程,并且想要调用另一个进程中的代码,那么我们是无法直接访问另一个进程中的内存空间的,所以就需要某种方式或者说通过某个桥梁,建立起进程间的连接。这个方式可以称为 remote procedure calls (RPCs),而借用的桥梁就是 Binder

简单来说就是将方法调用以及数据,分解成操作系统能够读取的程度,然后把它从当前进程和地址空间中传送到目标进程和地址空间,重新组合起来之后再进行调用,最后将结果用同样的方式返回给请求发生的进程。

More details

IPC 基础概念

序列化

Serializable 接口

Parcelable 接口

Binder

Binder 实现了 IBinder 接口,在跨进程通信中,它是客户端与服务端通信的媒介,当绑定服务时,服务端会返回一个 Binder 对象,而客户端正是通过这个 Binder 对象来获取服务端的数据以及进行远程方法调用。

一般的 Service 如果不涉及进程间通信,那么只要实现 IBinder 接口就可以了,所以我们以最基本的 AIDL 作为例子来解释 Binder 的工作原理。AIDL 实际上是 Android 为我们封装好的接口定义语言,它自动为我们生成 Binder 的实现代码。

具体而言,一个 Binder 的实现通常需要有以下部分:

以上最关键的部分是最后的 Proxy 类以及 onTransact() 方法。

关于 AIDL 的具体使用方式请看下面的 AIDL 部分。

IPC 方式介绍

Bundle

Intent.putExtra(Bundle),数据必须是可以序列化的,比如原始数据类型,实现 Parcelable 接口或者 Serializable 接口的对象,基本数据类型或者实现了 Parcelable 的 ArrayList/SparceArray 等。

文件共享

对数据格式没有要求,但是需要注意并发读写的问题。

SharedPreferences 是个特例,它会在内存中保存一份拷贝,使用 apply() 时进行异步提交(先保存到内存然后异步提交保存到文件),而使用 commit() 时会阻塞并返回结果,它是单进程模式下的单例,所以多进程明显就变得非常不可靠。

Messager

轻量级的 IPC,底层使用的是 AIDL。

AIDL

Messager 适合跨进程传递消息,如果有大量请求或者需要跨进程方法调用,那么可能就需要使用到 AIDL 了。

具体步骤如下:

  1. 创建 .aidl 接口

与创建普通 Java 接口类似,只不过只支持下面一些数据类型:

另外,使用 AIDL 传递 Parcelable 对象时,还需要我们为该 Parcelable 对象创建一个 .aidl 文件,如下:

package your.package.name

parcelable YourParcelableClass;

再之,AIDL 接口方法的参数必须标明方向 in out inoutin 表示输入型参数,out 表示输出型参数,inout 两者皆可,基本数据类型默认为 in。不同参数类型的 Binder 在再编码marshall,不知道如何翻译,将数据序列化、传输、接收、解序列化的过程)时所需的步骤不同,标明类型后,Binder 可以跳过某些步骤从而达到节省开销的目的。具体解释见:“In/out/inout” in a AIDL interface parameter value?

创建好 AIDL 接口后,gradle sync 后 IDE 会为我们生成对应的 Java 文件。

  1. 实现 AIDL 接口(其实是接口中的 Stub 类,即 Binder 真正的实现类)

在服务端 Service 中,我们需要实现接口中的 Stub 类,然后在 onBind() 方法中返回供客户端调用。需要注意的是:

  1. 客户端的实现

客户端通过 bindService() 与服务端建立连接后,通过 onServiceConnected() 回调中获取 binder 实例,然后再通过 YourAIDLInterface.Stub.asInterface()binder 实例类型转换为所需要的 AIDL 接口(如果客户端位于不同的应用中,则必须也要保留一份 .aidl 文件,以便生成 AIDL 接口)。

需要注意的是,如果我们使用了回调的话,会遇到无法解注册的问题,本质是因为回调接口在从客户端传递到服务端的时候,已经不是同一个对象了,想要成功解注册我们需要使用 RemoteCallbackList,它是线程安全的(原理:通过遍历所有 Binder 对象--我们的 listener 也是 Binder 对象--然后找到对应的 listener 再删除)。

另外还要注意 Binder 意外死亡(DeathRecipient 或 onServiceDisconnected(),前者在 Binder 线程池中被调用,后者在 UI 线程中调用)以及权限验证(onBind() 或 onTrasact() 中做处理)的问题。具体请查看例子:Chapter_2/aidl

ContentProvider

参考文档

Socket

分为 TCP 套接字(Java 中对应的实现使用 ServerSocket)和 UDP 套接字(Java 中对应的实现使用 DatagramSocket),具体例子见:Chapter_2/socket

Binder 连接池

Binder 连接池的主要作用是将每个业务模块的 Binder 请求统一转发到远程 Service 中去执行,从而避免重复创建 Service,节省系统资源。

工作机制:每个模块创建并实现自己的 AIDL 接口,然后向服务端提供自己的唯一标识和对应的 Binder 对象,服务端只是需要一个 Service,然后提供一个 queryBinder 接口,根据业务模块的标识来返回相应的 Binder 对象,然后再由客户端发起远程方法调用。

具体例子见:Chapter_2/binderpool

优缺点对比

名称 优点 缺点 适用场景
Bundle 简单易用 只能传输 Bundle 支持的数据类型 四大组件间的进程间通信
文件共享 简单易用 不适合高并发场景,并且无无法做到进程间的即时通信 无并发访问情形,交换简单的数据,实时性不高的场景
Messenger 功能一般,支持一对多串行通信,支持实时通信 不能很好处理高并发情形,不支持 RPC,数据通过 Message 进行传输,因此只能传输 Bundle 支持的数据类型 低并发的一对多即时通信,无 RPC 需求,或者不需要返回结果的 RPC 需求
AIDL 功能强大,支持一对多并发通信,支持实时通信 使用稍复杂,需要处理好线程同步 一对多通信且有 RPC 需求
Content Provider 在数据源访问方面功能强大,支持一对多并发数据共享,可通过 Call 方法扩展其他操作 可以理解为受约束的 AIDL,主要提供数据源的 CRUD 操作 一对多的进程间的数据共享
Socket 功能强大,可以通过网络传输字节流,支持一对多并发实时通信 实现细节稍微有点烦琐,不支持直接的 RPC 网络数据交换
上一篇 下一篇

猜你喜欢

热点阅读