前言
Android 系统的运行由大量相互独立的进程相互协助来完成的, 所以 Android 进程间通信问题, 是做好 Android 开发高级工程师必须要跨过的一道坎, 也是面试时经常被问及到的知识点. 但是, 我们是否真的清楚, Android 中都有哪些方式实现跨进程通信呢? 这些方式都有哪些优缺点? 如何选择这些通信方式? Binder 是什么? 为什么要引入 Binder?Binder 是这么样实现跨进程通信的? AIDL 是什么? AIDL 和 Binder 又有什么关系呢?......
一, 基础知识简介
在介绍 Android 跨进程通信之前, 笔者先简单啰嗦一下进程隔离, 跨进程通信.
1, 进程隔离
在操作系统中, 进程与进程间的内存和数据都是不共享的. 两个进程就好像大海中相互独立的两个岛屿, 各自生活在互相平行的两个世界中, 互不干扰, 各自为政. 这样做的目的, 是为了避免进程间相互操作数据的现象发生, 从而引起各自的安全问题. 为了实现进程隔离, 采用了虚拟地址空间, 两个进程各自的虚拟地址不同, 从逻辑上来实现彼此间的隔离.
2, 跨进程通信
马克思主义哲学说, 人是一切社会关系的总和. 任何一个个体都不可能完全隔离于外界, 都不可避免地与外界 "互通有无". 进程也一样, 每一个进程完成的功能有限, 就像现在的生成线一样, 往往就是只完成某一类功能, 而不是把所有事情都给做了, 就这样, 每个进程就时不时需要与其他进程之间通信了. 两个进程之间要进行通信, 就需要采用特殊的通信机制: 进程间通信 (IPC:Inter-Process Communication, 即进程间通信或跨进程通信, 后文以 IPC 替代, 在此声明).
二, Linux 跨进程通信方式
我们知道, Android 系统就是基于 Linux 内核实现的, 咱们先简单了解一下 Linux 系统的 IPC 方式. 虽然不同的资料对各种方式的名称和种类说法不完全相同, 但是主要来说有如下 6 种方式:(1) 管道 Pipe;(2) 信号 Signal;(3) 信号量 Semaphore;(4) 消息队列 Message Queue;(5) 共享内存 Shared Memmory;(6) 套接字 Socket. 读者若想深入了解这些方式, 可以自行查阅, 这里不展开介绍, 只需要知道有这六种方式即可.
三, Android 跨进程通信
Android IPC 的方式在不同的资料中有不同的说法, 但从大方向上看, 可以归纳为如下四种 (这里仅对各种方式做简单介绍和优劣对比, 对具体如何使用, 不做讲解):
1,Activity 方式
Activity 是四大组件中使用最频繁的, 咱们先从它说起. 使用 Activity 方式实现, 就是使用 startActivity() 来启动另外一个进程的 Activity.
(1) 场景
我们在使用 App 的使用, 往往会遇到如下几种情形:(1) 浏览器中看到一篇比较不错的文章, 分享到微信朋友圈或者微博;(2) 在某个 App 中点击某个网址, 然后界面跳转到浏览器中进行阅读;(3) 使用美团外卖 App, 看到店家的电话, 点击联系商家时, 跳转到了电话拨打界面...... 这样的操作再频繁不过了. 这些就是通过 startActivity 的方式从一个 App, 跳转到了另外一个 App 的 Activity, 从而实现了跨进程通信.
(2) 使用
我们知道, 在调用 startActivity(Intent intent) 的时候, intent 有两个类型: 显式 Intent 和隐式 Intent.
1) 显式 Intent 的使用方式如下, 用于进程内组件间通信:
- Intent intent = new Intent(this,OtherActivity.class);
- startActivity(intent);
这种方式显式地指定了要跳转的 Activtiy 的 class 名称, 不知道是不是因为这个原因而被称为显式 intent 的, 笔者没有查证. 这种方式用于进程内 Activity 的跳转, 是跨模块间通信, 而不是跨进程间通信.
2) 隐式 intent 的使用方式如下, 用于 IPC:
Intent intent = new Intent(); intent.setAction(Intent.ACTION_CALL); startActivity(intent);//startActivityForResult() 同样, 这里不赘述
Intent.ACTION_CALL 就是字符串常量 "android.intent.action.CALL", 这种方式通过 setAction 的方式来启动目标 App 的 Activity, 上述代码就是启动电话 App 的拨号界面, 有时候还可以带上电话号码等参数.
由上可知, Activity 实现跨进程通信的方式, 适合于不同 App 之间功能界面的跳转.
2,Content provider(后面简称 CP) 方式
(1) 场景
当我们开发 App 需要用到联系人, 多媒体信息等数据的时候, 往往会通过系统提供 Uri, 采用 CP 的方式去获取. Android 系统中, 数据主要存储在自带的 SqlLite 数据库中. 应用要共享 SqlLite 中的数据给其他 App 操作 (增, 删, 改, 查), 就要用到 CP, 也就是说, CP 主要用于跨进程数据库共享. Android 系统提供了很多的 CP 来供其它 App 使用, 如多媒体信息, 联系人, 日历等. 如下图显示了 Android 系统提供的 CP, 包名都是以 "com.android.providers" 开头的:
这些用于共享的数据其实都是存储在系统数据库中的, 如下显示了 meida CP 中的数据库:
App 开发者也可以自定义 CP, 把自己的数据提供给其它 App 使用, 也可以自己定义操作权限, 如只允许其它 App 读取自己的数据, 而不允许修改等.
(2) 特点
1)CP 的使用场景, 是提供数据共享.
2)CP 本质上还是在操作数据库, 数据存储在 sdcard 中, 所以建立连接和操作数据都是耗时操作, 所以注意开辟子线程去操作.
3) 当数据库中数据有变化时, Content Observer 监听到数据库变化也是有一定的滞后.
3,Broadcase 方式
Broadcast 使用非常简单, 注册好广播, 添加上 action, 就可以等着接收其他进程发出的广播. 发送和接收广播时, 还可以借助 Intent 来携带数据. 但是广播的使用存在很多问题, 被很多程序员吐槽, 甚至鄙夷, 所以选择用广播进行跨进程通信, 是下下策. 下面盘点一下 Broadcast 的槽点:
(1)Broadcast 是一种单向的通信方式. 当一个程序发送广播后, 其他应用只能被动地接收, 无法向发送者反馈.
(2)Broadcast 非常消耗系统资源, 会导致系统性能下降.
(3) 速度慢, 容易造成系统 ANR. 且除了 Parall Broadcast 外, 无法保证接收到的时间, 甚至不一定能收得到.
(4) 如果使用 Ordered Broadcast, 一个 Receiver 执行时间过长, 会影响后面接收者的接收时间, 甚至还有可能被中间某个 Receiver 拦截, 导致后面 Receiver 无法接收到.
(5) 发送者无法确定谁会接收该广播, 而接收者也无发确认是谁发来的广播.
(6) 如果是静态注册的广播, 一个没有开启的进程, 都有可能被该广播激活.
......
总而言之, 言而总之, 使用 Broadcast 来实现跨进程通信, 是下下之策!
4,Service 方式
启动 Service 的方式有多种, 有的用于跨进程通信, 有的用于进程内部模块之间的通信, 下面仅简单介绍一下跨进程通信的方式.
(1)startService() 方式
Intent startIntent = new Intent (); ComponentName componentName = new ComponentName(string packageName,string serviceClassName); startIntent.setComponent(componentName ); startService( startIntent) ;
该方式启动远程 Service 实现跨进程通信, 耦合度比较低, 功能及代码结构清晰, 但是存在以下缺点:
1) 没有好的机制立即返回执行结果, 往往 Service 完成任务后, 还需要其他方式向 Client 端反馈.
2)Service 端无法识别 Client 端是谁, 只知道有启动命令, 但无法知道是谁下的命令.
3) 在已经创建好 Service 情况下, 每次调用 startService, 就会执行 onStartCommand() 生命周期方法, 相比于 bindService, 效率低下.
4) 如果 Client 端忘记调用 stopService() 了, 那么该 Service 会一直运行下去, 这也是一个隐患.
所以, 针对上述缺点, 往往建议 startService() 方式用于同一个 App 内, 跨进程的方式用后面将要讲到的 AIDL 来实现.
(2)bindService,Handler,Messager 结合
这种方式也可以实现跨进程通信, 据说和 AIDL 具有相同的功效, 但比 AIDL 使用简单, 详细可以阅读博文 [Android 总结篇系列: Android Service] . 但是从笔者工作这些年来看, 从没见过谁使用过这种方式, 可能是笔者太孤陋寡闻了, 这里就不多介绍了.
(3)AIDL
这种方式也是 bindService() 启动方式的一种使用情况, 也是广受程序员们推崇的方式. 前面说 startService() 和 Broadcast 如何不好, 就是为了衬托 AIDL 如何的好 ! 这是本文的主角, 本文后面会专门详细讲解, 这里不赘述.
值得注意的是, 从 Android L 开始, 系统对 Service 的隐式启动做了限制, 需要至少包含包名或类名, 具体可以查看博文 [Android 5.0 之后隐式声明 Intent 启动 Service 引发的问题] , 所以隐式启动 Service 时需要多注意这一点.
5, 总结
从如上的介绍来看, 其实 Android 中跨进程通信的实现, 就是利用四大组件来实现的. 对方式的选择, 我们总结一下:
(1) 如果跨进程需要界面上的交互操作, 用隐式 startActivity() 方式实现.
(2) 如果需要共享数据, 用 Content Provider 方式实现.
(3) 排除前两种情形, 就用 AIDL.
(4) 仅仅为了完成功能, 又确实不会用 AIDL 的, 就用 Broadcast 吧!!! 虽然很 low, 但比实现不了功能还是强多了
四, Android 组件间通信
前面介绍了几种跨进程通信的方式, 这里顺便介绍一下进程内组件之间的通信方式, 也就是各模块之间的通信.
(1) 显示调用 startActivity(). 可以用 Intent 携带数据, 而且如果用 startActivityForResult(), 还可以得到目标 Activity 完成任务后的反馈.
(2)startService/bindService. 用于与 service 通信, 其中 Intent, 回调方法等会携带数据或者对象, 选择哪一个需要视情况而定.
(3)Boradcast. 前面已经 diss 过很多了, 这里不多说了, 能不用则不用.
(4)Handler 方式. 这种方式在子线程和主线程通信中用得很普遍, 跨模块使用也常见到, 使用方便. 需要注意的是 Looper 和线程问题.
(5) 回调. 最普遍的方式了, 使用也很方便. 一般会结合面向接口编程来实现, 如果调用的层次比较深, 同一个接口注册的地方太多, 可读性会比较差, 经常看得头晕. 甚至有时候这些注册回调的地方会相互干扰. 使用的时候要注意退出该模块的时候, 把注册的回调变量置空销毁.
(6)EventBus. 使用简单, 耦合度低, 代码可读性比较好, 也受到很多开发者的喜爱. 但也有一些缺点: 1) 需要导入第三方库. 2) 发送消息出去后, 被注册的地方都会收到该消息, 如果处理不当, 开发者甚至不容易意识到哪些地方收到了消息并做了处理, 带来不必要的混乱. 3) 当注册了 EventBus 的模块退出后, 容易忘记反注册.
(7)SharePreference(简称 SP) 等数据存储方式. 这个就是数据的持久化和模块间共享数据了, 除了 SP, 还有网络存储, Sqilite 数据库, 文件存储等多种方式, 功能可以类比跨进程通信的 ContentProvider.
和跨进程通行方式选择一样, 组件间的通信方式也要根据具体需要来确定了. 从完成功能上讲, 前面讲到的跨进程通信的方式, 在进程内组件间通信也可以用, 但是这样就像用牛刀杀鸡, 大炮打蚊子了. 出于性能方面的考虑, 就不要将跨进程的方式用于进程内模块间通信了.
五, Binder 概述
Binder 是 Android 框架中非常重要的一个机制, 在 Framework 中被广泛使用, 理解 Binder 对阅读 Framework 源码有着很大的帮助. 当然, 在应用层等层面的跨进程通信中, 也被广泛使用. 有人说, 理解 Binder, 是跨入高级 Android 工程师行列的第一步, 先不管是不是夸大其词了, 但足以说明 Binder 对于 Android 有多么重要, 也说明理解 Binder 有一定的难度.
1,Binder 是什么
Binder 英文单词的意思就是 "粘合剂", 把两个物体粘合在一起. 说起 Binder 是什么, 在 IPC 过程中, 从不同的角度来看, 它可以被定义为一种机制, 或者实体类, 或者远程代理, 甚至是传输对象, 咱们这里说的 Binder 是从宏观定义来看的, 是说的 Binder 机制. 在 Android 中, 它是一种高效的 IPC 方式, 是 Android 所特有的机制. 它采用 C/S 架构模式, 基于内存映射 (mmap()), 在系统内核空间将 Client 端和 Service 端两个用户空间的进程联系在一起.
2,Binder 的由来
Binder 前身是某公司开发的 OpenBinder 项目, 后来该项目的作者加入了 Google, 同时把该项目带进了 Android. 从此以后, Binder 就成为了 Android 中主要的跨进程通信机制.
3,Android 为什么要引入 Binder
前面第二点提到, Linux 已经具备了这么多的 IPC 方式, 为什么 Android 中还要引入 Binder 呢? 原因是从性能, 稳定性和安全性三方面考虑, Linux 本身的那些方式, 往往只能具备一部分因素, 无法兼顾. 但是 Binder 却不然, 从性能上看, 只需要一次数据拷贝, 性能上仅次于共享内存; 从稳定性上看, 基于 C/S 架构, 职责明确, 架构清晰, 稳定性好; 从安全性上看, 它为每个 App 分配 UID, 而进程的 UID 是鉴别进程身份的重要标志. 对于更详细的对比, 推荐阅读 [Android Bander 设计与实现 - 设计篇的 "引言" 部分] , 本文重点不是探索 Linux, 就不赘述了, 咱们只需要知道 Binder 比 Linux 其它 IPC 方式更给力就够了.
4,Binder 机制原理
Binder 机制相关知识点和源码非常庞杂, 很多技术书籍往往要花很大篇幅来阐述, 比如罗升阳的《Android 系统源码情景分析》就花了 100 多页来剖析它. 俗话说, 要想倒出一碗水, 你得先有一桶水. 很不好意思, 笔者现在一碗水都不到, 这里就不献丑了, 推荐一篇讲得比较好的博文:[写给 Android 应用工程师的 Binder 原理剖析 https://www.jianshu.com/p/429a1ff3560c ] , 看完后收获挺大的.
六, AIDL 基本使用
前面我们讲到, 为了克服 Linux 中 IPC 各种方式的缺点, 在 Android 中引入了 Binder 机制. 但是当说起 Binder 在 Android 中的使用时, 几乎所有的资料都是在说 AIDL 的使用. AIDL 的全称是 Android Interface Definition Language, 即 Android 接口定义语言, 是 Binder 机制实现 Android IPC 时使用比较广泛的工具. 本节将以一个 Demo 演示一下 AIDL 的基本实现步骤.
如下两个图展示了该 Demo 的结构图和 AIDL 关键文件:
图 6.1 图 6.2
1, 建立两个 App, 分别为 Client 端和 Server 端.
这个比较 好理解, Server 端就是包含了 Service 真正干活的那一端; Client 端就是通过远程操控指挥的那一端, 分别在不同的 App 中. 如下图所示:
2, 在 Server 端 main 目录下建立 aidl 文件夹以及. aidl 文件, 并 copy 一份到 Client 端, 如图 6.1 中2处所示结构. 注意, Client 端和 Server 端2处是一模一样的. 另外, AS 中提供了快捷方式创建 aidl 文件, 在 main 处点击右键 > New> AIDL> AIDL File 文件, 按照提示给 aidl 文件命名即可自动创建完成, 可以看到文件路径也是该项目的包名.
这里给 aidl 命名为 IDemoService.aidl, 这里需要注意的是命名规范, 一般都是以 "I" 开头, 表示是一个接口, 其内容如下:
// IDemoService.aidl package com.songwei.aidldemoserver; // Declare any non-default types here with import statements interface IDemoService { void setName(String name); String getName(); }
3,Server 端创建 Service 文件 AidlService.java, 如图 6.1 中3处所示, 代码如下:
View Code
为了下文分析流程及生命周期, 在其中各个方法中都添加了 Log.
同时, 在 Server 端的 AndroidManifest.xml 文件中添加该 Service 的注册信息.
<service Android:name=".AidlService" Android:exported="true"> <intent-filter> <action Android:name="com.songwei.aidl" /> </intent-filter> </service>
这里有几点需要注意:
(1)exported 属性值, 如果有 "intent-filter", 则默认值为 true, 否则为 false. 所以这里其实可以去掉, 因为有 "intent-filter", 其默认值就是 true.
(2) 由于笔者在后面启动该 service 的时候用的 action 的方式, 所以这里就有了 "intent-filter" 里面的 action. 如果用其他方式启动, 这个 service 的注册信息就需要相应的改动了, 有一定开发经验的读者应该都知道, 就不展开讲了, 主要是怕读者容易忽略这里, 所以特别提醒一下.
4, 编译 Sever 端和 Client 端 App, 生成 IDemoService.java 文件.
当编译的时候, AS 会自动为我们生成 IDemoService.java 文件, 如图 6.1 和图 6.2 中4处所示. 当你打开该文件的时候, 是不是看到了如下场景?
惊不惊喜? 意不意外? 是不是一脸懵逼, 大惊, 卧槽, 这尼玛啥玩意啊?
AIDL 是 Android 接口定义语言, IDemoService.java 是一个 java 中的 interface(接口), 现在是不是若有所思了呢? AIDL 正是定义了 IDemoService.java 这个接口!!! 这个接口文件就是 AIDL 帮助咱们生成的 Binder 相关代码, 这些代码就是用来帮助实现 Client 端和 Server 端通信的. 前面第 2 步中提到的 IDemoService.aidl 文件, 其作用就是作为原料通过 AIDL 来生成这些你貌似看不懂的代码的, 第 3 步中的 AidlService.java 和后续在 Client 端 App 连接 Server 端 App 的时候, 其实这个 aidl 文件就从来没有出现过, 也就是说, 它已经没有什么价值了. 所以说, AIDL 的作用就是用来自动生成 Binder 相关接口代码的, 而不需要开发者手动编写. 有些教程中说, 可以不使用 AIDL 而手动编写这份 Binder 代码, AIDL 不是 Binder 实现通信所必需的, 笔者也没有尝试过手动编写, 如果读者您想挑战, 可以尝试一下!
咱们继续!
打开 IDemoService.java 文件后, 点击主菜单兰 Code> Reformat Code (或 Ctrl + Alt +L 快捷健), 你会发现画面变成了下面这个样子:
View Code
惊不惊喜? 意不意外? 这下是不是有种似曾相识的赶脚? 这就是一个很普通的 java 中的接口文件而已, 结构也非常简单.
神秘的面纱揭开一层了吧! 后面在讲完 Client 端和 Server 端的连接及通信后, 还会继续深入剖析这个文件.
5,Client 端 ClientActivity 连接 Server 端 AidlService 并通信
ClientActivity.java 的内容如下, 布局文件在此省略, 比较简单, 就两个按钮, 一个用于绑定, 一个用于解绑, 看 Button 命名也很容易分辨.
View Code
代码中对一些关键和容易忽略的地方做了注释, 可以结合起来进行理解.
6, 运行
运行的时候, 需要先启动 Service 端进程, 才能在 Client 端中点击 "绑定" 的时候绑定成功. 完成一次 "绑定" 和 "解绑", 得到的 log 如下所示:
01-08 15:29:43.109 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onCreate] 01-08 15:29:43.110 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onBind] 01-08 15:29:43.113 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]componentName=ComponentInfo{com.songwei.aidldemoserver/com.songwei.aidldemoserver.AidlService} 01-08 15:29:43.114 13532-13547/com.songwei.aidldemoserver I/aidlDemo: server:[setName] 01-08 15:29:43.114 13532-13546/com.songwei.aidldemoserver I/aidlDemo: server:[getName] 01-08 15:29:43.114 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]myName=Andy Song 01-08 15:36:07.570 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceDisconnected]
可以结合前面的 ClientActivity.java 和 AidlService.java 代码中的添加的 log, 来理解一下这个流程. 当然, 最好是能够按照上面的步骤, 亲自动手实现一遍, 比看 10 遍更有效果.
本节对介绍了 AIDL 最基本的知识, 出于项目性质的原因, 可能有些 Android 开发人员工作了好几年都不一定需要完整写一个 AIDL 实现两个 App 通信的功能. 笔者就是这样, 在前几年的工作中, 虽然很早就知道 AIDL 这个东西, 但确实是项目中就没有写过这个功能, 直到最近两年. 所以即便是读者您有不少开发经验了, 也可能和笔者一样是个 AIDL 的初级开发者, 这也是我花这么长的篇幅写这个基础内容的原因.
另外, 有需要源码可以下载这个 demo, 网盘地址: https://pan.baidu.com/s/1CyE_8-T9TDQLVQ1TDAEX2A 提取码: 4auk .
七, AIDL 的深入使用和理解
前面一节讲了 AIDL 最进本的知识, 这一节中将会结合更复杂的场景, 更深入地介绍 AIDL.
1,Client 端是如何实现调用 Server 端方法的
2,AIDL 支持的数据类型
3,AIDL 数据类序列化问题
4,AIDL 回调的使用
结语
本文主要是笔者用来整理跨进程通信的知识点, 以及复习近两年才真正会用的 AIDL, 所以详略上是前半部分略, 后半部分详, 完全是按照笔者对知识点掌握程度来行文的. 读者在阅读中, 如果有些部分因为写得太简略而看得不过瘾, 只能自己去查更详细的资料了; 如果有些部分因为写得太详细而嫌啰嗦, 完全可以跳着看; 如果有些知识点因为笔者经验和水平问题写得有误或者阐述欠妥, 请不吝赐教, 万分感激!!!
来源: https://www.cnblogs.com/fivestudy/p/10253454.html