安卓-IPC

IPC

进程间通信或跨进程通信,是指两个进程之间数据交换的过程。

多进程

一个应用存在多个进程的情况

使用多进程的目的

  1. 自身需要。

  2. 向其他应用获取数据。

安卓开启多进程的方式

给四大组件指定android:process

私有进程

android:process=":remote"(完整写法android:process="com.lwj.ipctest:remote")

可通过修改ShareUID 、签名和它跑在同一进程的全局进程

android:process="com.lwj.ipctest.remote"

多进程造成的影响

  1. 静态成员和单例模式完全失效。(每个进程都会分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间,导致不同虚拟机中访问同一个类对象会产生多份副本。)

  2. 线程同步机制完全失效。(原因同1)

  3. SharedPreferences的可靠性下降。(底层通过读写xml文件实现,,两个线程并发写会导致一定几率的数据丢失。)

  4. Application会多次创建。(当一个组件跑在一个新进程中时,系统要创建新的进程同时分配独立的虚拟机,这个过程其实是启动一个应用的过程,把应用重新启动了一遍,会创建新的application。)

在多进程中,不同进程的组件会拥有独立的虚拟机、application以及内存空间。

IPC基础

Serializable

java提供的一个序列化接口,空接口,为对象提供标准的序列化和反序列化操作

作用

Intent、Binder传输数据,通过对象持久化到存储设备上。

使用方式

  1. implements Serializable

  2. 声明一个serialVersionUID(用于辅助序列化和反序列化,序列化后的数据中的serialVersionUID和当前类的serialVersionUID相同才能正常的被反序列化。该值应手动指定。)

序列化

将实现了Serializable接口的对象通过ObjectOutputStream写进文件中。

反序列化

将文件通过ObjectInputStream读出来。

序列化前的对象和反序列化后的对象内容一样,但不是同一个对象。

注:

  1. 静态成员变量属于类不属于对象,不会参与序列化过程。

  2. 用transient关键字标记的成员变量不参与序列化过程。

  3. 系统默认序列化过程可用通过重写writeObject方法和readObject方法改变。

Parcelable

作用

Intent、Binder传输数据,内存序列化(序列化后存到存储设备中或者通过网络传输实现比较复杂,建议用Serializable)。

使用方式

实现Parcelable接口

可实现功能

序列化、反序列化、内容描述

  1. 序列化由writeToParcel方法完成,最终由write方法完成。

  2. 反序列化由CREATOR完成,最终由read方法完成。

  3. 内容描述由describeContents方法完成,大部分情况下应返回0。

Serializable 与Parcelable 比较

Serializable是java的序列化接口,使用简单但开销大,序列化和反序列化需要大量IO操作。

Parcelable是安卓中的序列化方式,使用于android平台,使用麻烦,效率高。

Binder

一个类,实现IBinder接口,可用于跨进程通信。主要用于Service中,包括AIDL和Messenger(Messager底层是Binder)

变量、方法含义

DESCRIPTOR

Binder唯一标识,一般用当前类目表示,比如"com.lwj.ipctest.IBookManager"。

asInterface(IBinder obj)

用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象。(这种转换区分进程,如果客户端和服务端位于同一进程,方法返回服务端的Stub对象本身,否则返回系统封装后的Stub.proxy对象)

asBinder

返回当前Binder对象。

onTransact

该方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求通过系统底层封装后交由此方法处理。(可以做权限验证,限定能远程调用服务的进程)

Proxy#getBookList

运行再客户端。

Proxy#addBook

运行在客户端。

Binder的工作机制图如下图所示。

图 Binder的工作机制

工作原理

  1. Client 挂起:客户端通过 transact() 把参数写入 data,然后线程挂起等待结果。

  2. 驱动转发:Binder 驱动将请求从客户端进程拷贝到服务端进程。

  3. 服务端执行:服务端线程池里的线程调用 onTransact() 解析参数、执行业务逻辑,结果写入 reply。

  4. 返回并唤醒:驱动把 reply 传回客户端,挂起的客户端线程被唤醒,拿到结果继续执行。

总结:

整个过程对上层是同步调用,但底层是"发送 + 挂起 + 唤醒"的等待机制,由 Binder 驱动完成跨进程的数据中转。

服务端进程由于某种原因异常终止,这个时候服务端的Binder连接断裂(称之为Binder死亡),会导致远程调用失败,客户端功能受到影响。Binder提供方法linkToDeath和unLinkToDeath来解决该问题。

linkToDeath

通过该方法给Binder设置一个死亡代理,当Binder死亡时,就会回调binderDied方法,就可以移出之前绑定的binder代理并重新绑定远程服务。

unLinkToDeath

解除binder绑定的死亡代理。

IPC方式

Bundle
  1. 已经实现Parcelabel接口,可以在Bundle中附加需要传输给远程进程的信息并通过Intent出去。

  2. 支持Activity、Service、Receiver三大组件。

  3. 传输数据必现能够被序列化,如:基本数据类型、实现了Parcelabel接口的对象、实现了Serializable接口的对象以及一些Android支持的特殊对象。

文件共享
  1. 两个进程通过读写同一个文件来交换数据。

  2. 数据格式没有要求,可以是文件,也可以是xml。

  3. 需处理好并发读写,适合对数据同步要求不高的进程间进行通信。

  4. SharePreferences是安卓轻量级存储方案,通过键值对的方式来存储数据,底层采用xml文件来存储键值对,目录:/data/data/package_name_shared_prefs。系统对它有缓存策略,多进程模式下,系统对它的读写不可靠,不适用于并发和进程间通信。

Messenger

通过它可以在不同进程中传递Message对象,在Message对象中放入我们需要传递的数据,实现跨进程通信。

Messenger是一种轻量级的IPC方案,底层实现是AIDL。

一次处理一个请求,服务端不存在并发执行的情况。

使用步骤:

服务端

  1. 创建一个Service用来处理客户端的连接请求。

  2. 创建一个Handler、Messager。

  3. 在Servie的onBind中返回这个Messager对象底层的Binder。

客户端

  1. 绑定服务端的Service。

  2. 绑定成功后用服务端返回的IBinder对象创建一个Messenger。

  3. 通过这个Messenger给服务端发送消息,发送消息类型为Message对象。

  4. 如果需要服务端可以回消息给客户端,就需要像服务端一样创建一个Handler和Messenger,把这个Messenger通过Message的replyTo参数传递给服务端,服务端通过replyTo参数就可以回应客户端了。

在Messenger中进行数据传递必须将数据放入Message中,而Messenger和Message都实现了Parcelabel接口,因此可以跨进程传输。Message支持的数据类型就是Messenger所支持的传输类型。Message中能使用的载体有what、arg1、arg2、Bundle以及replyTo。

Messenger的工作原理如下图所示。

图 Messenger的工作原理

缺点:以串行的方式处理客户端发来的消息,如果有大量的消息同时发送到服务端,服务端只能一个一个处理。有时候需要调用服务端的方法,Messenger也无法做到。

AIDL

使用方法

服务端

  1. 创建一个Service来监听客户端的连接请求。

  2. 创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明。

  3. 最后在Service中实现这个接口。

客户端

  1. 绑定服务端的Service。

  2. 将服务端返回的Binder对象转成AIDL接口所属的类型。

  3. 调用AIDL中的方法。

支持的数据类型

  1. 基本数据类型(int、long、char、boolean、double等);

  2. String和CharSequence;

  3. List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;

  4. Map:只支持HashMap,里面每个元素都必须能够被AIDL支持,包括ksy和value;

  5. Parcelable:所有实现了Parcelabel接口的对象;(需显示import)

  6. AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。(需显示import)

注:

  1. AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout。in表示输入型参数,out表示输出型参数,inout表示输入输出型参数。

  2. AIDL接口只支持方法,不支持声明静态常量。

  3. AIDL的包结构在服务端和客户端要保持一致,因为客户端需要反序列化服务端中和AIDL接口相关的类。

  4. AIDL方法是在服务端的Binder线程池中执行的,存在多个服务端同时连接时,会存在多个线程同时访问的情形,需要在AIDL方法中处理线程同步,可以直接使用CopyOnWriteArrayList来进行自动的线程同步。

  5. 对象不能跨进程直接传输。

  6. 使用RemoteCallbackList来删除跨进程listener的接口,RemoteCallbackList是一个泛型,支持管理任意的AIDL接口。

  7. RemoteCallbackList工作原理:内部有一个Map结构专门用来保存所有的AIDL回调,key是IBinder类型,value是Callback类型。Callback类型封装了真正的远程listener。当客户端注册listener时,会把这个listener信息存入mCallbacks中;当客户端解注册时,RemoteCallbackList会帮助我们遍历服务端所有的listener,找出那个和解注册listener和具有相同Binder对象的服务端listener,并把它删除掉。客户端进程终止后,它能够自动移除客户端所注册的listener,RemoteCallbackList内部实现了线程同步功能。

  8. 遍历RemoteCallbackList时,beginBroadcast和finishBroadcast必须要配对使用。

  9. 客户端调用远程服务的方法时,被调用的方法运行在服务端的Binder池中,客户端线程会被挂起,如果服务端方法执行比较耗时,就会导致客户端线程长时间阻塞在这里,如果客户端是UI线程的话,就会导致客户端ANR。如果明确知道某个远程方法比较耗时,那么就要避免在客户端的UI线程中去访问远程方法。

  10. 服务端方法本身就运行在服务端的Binder线程池中,服务端本身就可以执行大量耗时操作,这个时候不要再在服务端方法中开线程去执行异步操作。

Binder 意外死亡

服务端进程意外死亡,需要重连服务。有两种方法。

  1. 给Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中可以重连服务。

  2. 在onServiceDisconnected中重连服务。

区别在于onServiceDisconnected在客户端的UI线程中被调用,binderDied在客户端的Binder线程池中被回调。

添加使用权限验证功能

默认情况下,远程服务是任何人都可以连接。我们可以加入权限验证功能,权限验证失败则无法调用服务的方法。

方式

  1. 在onBind中使用permission进行验证。(该方法适用于Messenger)

  2. 在onTranscat中进行验证。

  3. 采用Uid和Pid来验证,通过getCallingUid和getCallingPid可以拿到客户端所属应用的Uid和Pid,通过这两个参数可以做验证工作,如:验证包名。

使用方式

在服务端的onBind方法做权限验证。

在客户端的AndroidManifest声明所需的权限。

ContentProvider

ContentProvider是安卓中提供的专门用于不同应用间进行数据共享的方式。底层实现也是Binder。

自定义ContentProvider 步骤

  1. 继承ContentProvider类并实现六个抽象方法。
  1. onCreate:代表ContentProvider的创建,可以做一些初始化动作。(主线程)

  2. getType:用来返回一个Uri请求所对应的MIME类型(媒体类型),比如:视频、图片。如果不关注这个选项,可以直接返回null或者"*/*"。(Binder线程池)

  3. CRUD。(Binder线程池)

  1. 注册
  1. android:authorities(必须是唯一的,后面通过这个唯一标识来访问该provider)

  2. android:permission(添加android:authorities属性后,外界想要访问该provider的就必须添加该权限)

  3. android:readPermission

  4. android:writePermission

注:

  1. ContentProvider主要以表格形式来组织数据,并且可以包含多个表。每个表格都具有行和列的层次性,行往往对应一条记录,列对应一条记录中的一个字段。

  2. 除了表格形式,ContentProvider还支持文件数据,比如图片、视频等。文件数据和表格数据的结构不同,处理这类数据时可以在ContentProvider中返回文件的句柄给外界让外界来访问ContentProvider中的文件信息。MediaStore就是文件类型的ContentProvider。虽然ContentProvider底层数据看起来很像一个SQLite数据库,但是ContentProvider对底层数据存储方式没有任何要求,我们既可以使用SQLite数据库,也可以使用普通的文件,甚至采用内存中的一个对象来进行数据存储。

  1. 通过SQLiteOpenHelper来管理数据的创建、升级和降级。通过Provider向外界提供上述数据库中的信息。

  2. ContentProvider通过Uri来区分外界要访问的数据集合。为了知道外界要访问的是哪个表,我们需要为它们定义单独的Uri和Uri_Code,使用UriMatcher的addURI方法来关联Uri和Uri_Code。外界访问provider时就可以根据请求的Uri来得到Uri_Code,有了Uri_Code就知道访问的是哪张表了。

  3. CRUD存在多线程并发,方法内部需要处理好线程同步。采用SQLite且只有一个SQLiteDatabase的连接,可以正确应对多线程的情况,因为SQLiteDatabase内部对数据的操作有同步处理,如果是多个SQLiteDatabase对象来操作数据库就无法保证线程同步。

Socket

Socket也称"套接字",分为流式套接字和用户数据报套接字两种,分别对应网络的传输控制层中的TCP和UDP协议。Socket本身支持传输任意字节流。

表. TCP、UDP对比

|-------------------------------------|----------------------|
| TCP | UDP |
| 面向连接的协议 | 无连接 |
| 提供稳定的双向通信功能 | 提供不稳定的单向通信功能(也可双向通信) |
| 连接需要经过"三次握手" | 效率高 |
| 为了提供稳定的数据传输功能,本身提供超时重传机制,因此具有很高的稳定性 | 不保证数据一定能够正确传输 |

  1. 使用Socket通信需要声明权限。
  2. 不能在主线程中访问网络。(网络操作可能是耗时的,放在主线程会影响程序响应效率)

使用方式

声明权限-->远程Service建立一个TCP服务-->连接TCP服务-->客户端连接生成Socket-->服务通过Socket与客户端通信-->客户端断开连接-->服务端关闭Socket并结束通话进程。

共享内存

SharedMemory

使用方式

使用步骤:

服务端

创建一个Binder服务 -》新建一个SharedMemory -》通过服务的Binder将SharedMemory的文件描述符传递出去

客户端

通过Service获得Binder -》通过Binder获得ParcelFileDescriptor,将其转化为FileDescriptor -》 将该文件描述符映射到内存中 -》 读取共享内存中的数据

优点:高效,直接操作内存中的数据。

缺点:需自行同步控制,多进程访问同一份内存可能发生冲突。

注意事项

1、共享内存中的数据不进行自动同步,需要手动进行同步控制。

2、使用共享内存时需要定义好数据结构,以便在各个进程中能够准确地解析数据。

3、共享内存大小需要提前定义好,一旦创建后不能扩展,否则可能破坏数据结构

Binder连接池

AIDL衍生通信方式。

作用

将每个业务模块的Binder请求统一转发到远程Service中去执行,避免重复创建Service的过程。

解决问题

当多个业务模块都需要AIDL通信时,需要建立多个AIDL和Service,应用会很重量级,浪费系统资源。使用Binder连接池可以对Service进行复用,减少Service数量,将AIDL放在同一个Service中去管理。

工作机制

  1. 每个业务模块创建自己的AIDL接口并实现此接口,不同业务模块之间不能有耦合,所有实现细节要单独开。
  2. 向服务端提供自己唯一标识和其对应的Binder对象。
  3. 服务端提供一个queryBinder接口,这个接口根据业务模块特征来返回相应的Binder对象给客户端
  4. 不同业务模块拿到所需 的Binder对象就可以进行远程方法调用。

工作原理如下图所示:

图 Binder连接池的工作原理

选取IPC方式

|-----------------|------------------------------------------|-----------------------------------------------------------|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 名称 | 优点 | 缺点 | 使用场景 | demo |
| Bundle | 简单易用 | 只能传输Bundle支持的数据类型 | 四大组件间的进程通信 | |
| 文件共享 | 简单易用 | 不适合高并发场景,且无法做到进程间实时通信 | 无并发访问情形,交换简单的数据是实时性不高的场景 | 代码1 代码2 |
| AIDL | 功能强大,支持一对多并发通信,支持实时通信 | 使用稍复杂,需处理好线程同步 | 一对多通信且有RPC需求 | 代码1 代码2 |
| Messenger | 功能一般,支持一对多串行通信,支持实时通信 | 不能很好处理高并发情形,不支持RPC,数据只能通过Message进行传输,因此只能传输Bundle支持的数据类型。 | 低并发的一对多即使通信,无RPC需求,或者无需要返回结果的RPC需求 | https://gitee.com/flying-guy/ds/blob/master/HelloWorldDemo/IPCTest/src/main/java/com/lwj/ipctest/MessengerActivity.java |
| ContentProvider | 在数据源访问方面功能强大,支持一对多并发数据共享,可通过Call方法拓展其他操作 | 可以理解为受约束的AIDL,主要提供数据源的CRUD操作。 | 一对多的进程间的数据共享 | https://gitee.com/flying-guy/ds/blob/master/HelloWorldDemo/IPCTest/src/main/java/com/lwj/ipctest/ProviderActivity.java |
| Socket | 功能强大,可以通过网络传输字节流,一对多并发实时通信 | 实现细节稍微繁琐,不支持直接的RPC | 网络数据交换 | https://gitee.com/flying-guy/ds/blob/master/HelloWorldDemo/IPCTest/src/main/java/com/lwj/ipctest/TCPClientActivity.java |
| Binder连接池 | 与AIDL类似,性能更好 | 实现有点复杂,需处理好线程关系 | 多个业务模块都需要使用AIDL,秀技术专享hhh | 代码1 代码2 https://gitee.com/flying-guy/ds/blob/master/HelloWorldDemo/IPCTest/src/main/java/com/lwj/ipctest/TCPClientActivity.java |

相关推荐
沙粒01 小时前
Mac 使用 scrcpy 局域网无线投屏指南
android
过期动态2 小时前
MySQL中的约束
android·java·数据库·spring boot·mysql
牛蛙点点申请出战3 小时前
IconFontViewer -- 一个可以在 Android Studio 中实时预览 IconFont 的插件
android·前端·intellij idea
努力努力再努力wz4 小时前
【MySQL 进阶系列】拒绝滥用root:从 mysql.user 到权限校验,带你彻底理解用户管理与授权机制!
android·c语言·开发语言·数据结构·数据库·c++·mysql
HaiXCoder4 小时前
AndroidAutoSize 框架原理分析与核心问题
android
fengci.5 小时前
CTF+随机困难题目
android·开发语言·前端·学习·php
Le_ee5 小时前
SWPUCTF 2025 秋季新生赛wp2
android
pengyu6 小时前
【Kotlin 协程修仙录 · 金丹境 · 初阶】 | 并发艺术:async/await 与并发组合的优雅之道
android·kotlin
沐言人生7 小时前
ReactNative 源码分析3——ReactActivity之初始化RN应用
android·react native