Android 开发之详解 IPC 进程通信

原出处:微信公众号——携宁技术
原作者:西京一村夫
原文链接:https://mp.weixin.qq.com/s/bjaC0tSB0R87kFZSe_G8sA

本文已获原作者授权发表,如需转载请联系作者获得授权。

前 言

应用层开发者想要在Android端更上一层, Android 基础知识、启动过程、编译打包 apk 过程、安卓 framework 层等实现原理都是一定要掌握的,熟悉安卓四大组件的深层通信过程及原理和 Android 构建工具 gradle 的实现原理也都逐渐成为必要技能,这就是好比步入应用层中高级工程师的一道门,这道门对于应用层初学者来说却无比坚实。

本篇旨在为我们学习Android运行机制奠定基础,因为包括启动流程(了解之后可以做启动优化)、四大组件的交互、最常见的Hanlder都会关联到。

通过这些学习,你的应用可以实现最直观的启动优化。

例如: 很多应用后续使用过程,除了默认进程外会再启动一些服务类进程(有三方的类似推送,也有可能是我们自己创建的其他服务类进程),当应用从launch桌面启动(AS性能检测可以看到,或者自己debug),系统创建应用默认进程(与applicationId保持一致),然后在应用进程中做一些必要的初始化工作,大部分需要初始化的工作,都是默认进程所必须的,非默认进程只需要针对自身做必要的部分初始化工作即可。

也就是说,当我们做好所有的单进程(ApplicationId同名进程)初始化等工作,安全又快,也就实现了应用从最初的4-5s左右到后来的2-3s,纯原生鱼雷基本不到2s(自动登陆接口响应需要1s多),如果再配合一些其他的优化手段,画面太好。

什么?你觉得提升2s没啥用?

Android 开发之详解 IPC 进程通信_第1张图片

那你是没见过做启动页开屏广告十几家广告一起来,至于显示我们不关心,但是你肯定是都要初始化的,要是代码比较任性,超10s那也常见(初始化必要工作太多的不在讨论范围),正常到3s这个差别还是很大的。

本来只在默认进行做的工作,你每个进程都做;而且多进程和多线程一样,操作数据也要考虑到安全性,这些问题在后面会讲到。

下面就正式开始对Binder通信机制和过程涉及的相关内容进行阐述(如本文有理解误区,万望读者不吝赐教):

一、进程间通信

进程间通信有多种方式,这里主要介绍Android进程间通信Binder特点及实现过程

在 Android 中,一个进程通常无法访问另一个进程的内存。

为了进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供开发人员操作的对象。如果开发人员直接编写执行该操作的代码较较为繁琐,因此Android引入AIDL为您处理此问题。

首先,大致介绍下aidl(Android Interface Definition Language)接口定义语言。可以利用它定义客户端与服务均认可的编程接口,以便二者使用进程间通信 (IPC) 进行相互通信。

学习Binder通信之前,强烈建议先了解aidl的实现过程,通过学习aidl的整个工作过程,我们对进程通信的流程会有大致的概念,有利于对底层执行的命令、调用接口和执行上层写入/读取相对应的理解,后面学习底层的过程就好比验证每个过程的更深层实现。

比如: C/S端通信transact/onTransact方法内实现数据交互,消息源筛选、数据类型及数据的读写,特点一目了然;鉴于篇幅,有关aidl的实现过程请自行学习,尽量做到了如指掌(可以看看Android开发艺术探索和张鸿洋的博客)。

特点:

  1. 安全性,对比socket/管道通信方式,Binder通信过程中,系统每创建一个进程会分配对应的PID和UID(此特点还可以用于多进程项目的某些方向区分优化),进程之间通过进程id匹配确认消息来源;
  2. 传输性能高,传输数据不需要tcp协议的三次握手,传输过程只需一次拷贝;
  3. 操作简单,共享内存虽然不需要拷贝,但实现相对复杂很多。

二、了解内存映射函数mmap和读写数据接口ioctl

mmap用于建立用户空间和内核空间的映射关系,实现映射关系内存空间共享数据;建立映射关系的不同内存空间的数据操作,会映射到建立关系的其他内存空间,映射的内存空间即可直接使用数据。

对应关系如下图:

Android 开发之详解 IPC 进程通信_第2张图片

ioctl是底层和应用层之间定义的接口协议(用于操作写入/读取数据),不提供单独的read/write接口,一次调用实现写入读取数据,满足数据交互同步。

假设需要进行写入/读取数据(write_size和read_size都大于0),在操作写入和读取的过程,先将write_buffer里面的数据写入Binder;然后同步等待数据返回,从Binder中读取数据存入read_buffer,这里的write/read都是以Binder为操作对象。

因为BINDER_WRITE_READ命令包含想写入和读取数据两步操作,且是否进行写入和读取操作都是根据Binder的write_size和read_size确定的。假如只需写入,设置Binder的write_size大于0,read_size等于0即可;只需读取的情况同理。

三、接下来对进程通信C/S端采用Binder通信过程的write/read及传递数据进行介绍

在aidl操作的过程,我们是有分别的write和read方法的,但是更底层的命令是直接一次执行,写入和读取是根据顺序判断是否需要执行(write_size/read_size)进行对应操作的。比如Server/Client端执行write/read完成后,进入等待接收数据状态,接收到数据之后做相应操作并回应reply。

写入/读取数据:

调用TRANSITION命令执行Client端向Server端发送数据请求,S从数据体data中拿出数据,再调用REPLY做出相应的回应(回应的数据也可以根据是否对应进程ID做相应回复)。

读取数据过程涉及到SPAWN_LOOPER,用于Binder读取数据端的线程池管理;当所有线程都有任务执行且没有超过该SET_MAX_THREADS最大线程数,要求接收方创建更多线程等待任务(looper此处也印证了为什么先有这篇,然后再写handler相关)。

Binder写入/读取的数据结构体都是binder_transaction_data,该数据体包含了上述,比如:进程id、用户id用于接收方确认发送方,data_size缓存区存放的数据长度(由发送方设置,用于接收方确定接收到的数据大小)。

系统为每个应用分配一定的内存(虽然应用可以自行配置,但是不能超过系统可分配的最大值),在每次处理完之后会执行BUFFER_FREE命令释放。

上面我们提到mmap()映射函数,在Binder通信的写入/读取过程中,采用的是接收数据缓存的动态分配和释放。程序处理完某缓存区数据之后,底层会调用命令释放缓存区,否则会导致缓存区耗尽而无法接收数据。

关于Binder的C/S端通信,总结性描述:

Client端将函数参数打包,在transact()中通过服务端Binder向Server发送数据包请求/等待数据返回,Server端在onTransact()中通过客户端Binder获取binder_transaction_data数据,取出进程id及其他数据做相应处理,应答Client端。

很多讲解这块儿内容的时候,都提到用对方Client/Server的Binder进行数据操作,实现数据交互。在底层本质上是,进程通过ServiceManager(简称SMgr下面会详细解释)注册,用的时候直接在注册表查找,就可以实现两端都持有彼此的引用(可以暂时理解为对象的引用遍布各处),由于应用层实现不需要修改底层代码,所以这也是便于理解的一种说法。

在Client和Server通信的过程,SMgr类似于域名服务器,当客户端向服务端发起请求的时候,通过SMgr找到初始进程对应Binder引用(类比在域名服务器中找到域名对应的主机)进行写入/读取。

SMgr进程的转化及其Binder创建,如下图:

Android 开发之详解 IPC 进程通信_第3张图片

通信主要由四部分组成:Server、Client、SMgr和Binder驱动

前两个是需要实现通信的两端,驱动负责把Server端Binder相关信息通过数据包形式发送到SMgr,SMgr收到数据包之后解析数据注册Server端的Binder引用,后续SMgr还会负责在Binder的登记表里查找每个进程对应的Binder引用。

大致过程为:Server端创建Binder实体之后,通过驱动以transaction_data数据包的形式发送到SMgr,通过SMgr将对应引用注册到内核Binder注册表。

以上发送及注册引用的具体过程为:Binder实体创建之后,驱动会在内核创建其对应的实体节点和用于传递给SMgr的Binder引用,然后把实体信息和刚刚创建的Binder内核引用等信息传递给SMgr,SMgr进程解析数据将引用插入Binder注册表,Client端就可以在表中查找该引用执行write_buffer/read_buffer等操作。

其他进程发送数据,SMgr接收数据并执行注册过程,如下图:

Android 开发之详解 IPC 进程通信_第4张图片

实际上要完成上面的过程,首先Server和SMgr也涉及到进程间通信,SMgr可以理解成系统默认进程,其他进程和SMgr通信的时候,SMgr都是作为Server端;那么SMgr和Server端通信时,SMgr中的Binder引用到底是谁传过来的,都要它注册,那他自己的Binder谁注册?

比方SMgr进程是项目经理,其他进程是项目组织成员,项目经理给每个人的权力(Binder)去完成工作,那么项目经理分配权限,项目经理的权力哪里来?肯定需要管理层赋予他职责。

接下来解释下SMgr的Binder引用哪里来,当某个进程执行BINDER_SET_CONTEXT_MGR命令将自身注册成SMgr进程时,驱动会为该进程创建好Binder实体(0号引用),并且其他所有进程都能获取到该引用,系统只能有一个SMgr进程。

后续任何进程向SMgr注册Binder时,都要通过该引用(SMgr的Binder实体引用)和SMgr进程进行通信。进程注册及进程间通信整个过程,大致理解为SMgr作为主导注册/查找对应Binder对象进行写入/读取,如下图:

Android 开发之详解 IPC 进程通信_第5张图片

所以,进程间通信就可以分为:SMgr和其他进程,Server、Client和SMgr两种。

本质上都是用对应进程的Binder对象实现写入/读取等操作。

区别在于进程对应的Binder对象:前者系统注册的时候就会定为0号引用;后者需要SMgr(Binder对象管理中心)实现注册,后续在注册表中根据定义的key取出对应进程的Binder引用执行操作。

此处,我们延申一下通信过程中线程的交互(后面写关于Handler的内容会更具体解释),如下图:

Android 开发之详解 IPC 进程通信_第6张图片

本文参考资料:

https://blog.csdn.net/augfun/article/details/82343249
https://baike.baidu.com/item/mmap/1322217?fr=aladdin
https://baike.baidu.com/item/ioctl/6392403?fr=aladdin
https://developer.android.google.cn/guide/components/aidl.html?hl=zh-cn
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0319/2619.html
https://www.zhihu.com/question/20122137/answer/14049112

读者福利

最后,感谢大家的支持,特别给大家准备了一些珍藏资料,包括:

  1. Android学习PDF+架构视频+面试文档+源码笔记
  2. 高级架构技术进阶脑图
  3. Android开发面试专题资料
  4. 高级进阶架构资料

如果你有需要的话,可以前往GitHub免费获取!

Android 开发之详解 IPC 进程通信_第7张图片

你可能感兴趣的:(移动开发,Android,android,ipc,进程通信,移动开发,Android开发)