写点什么

Android 跨进程通信导论,2021 年 Android 面试心得

作者:嘟嘟侠客
  • 2021 年 11 月 27 日
  • 本文字数:4737 字

    阅读完需:约 16 分钟

try?{double?result?=?mathAidl.add(1,?1);Toast.makeText(MainActivity.this,?"计算结果为:"?+?result,?Toast.LENGTH_SHORT).show();mathAidl.play("请观看服务端播放:'西京一村夫'SINITEK 冲刺之路.mp4");}?catch?(RemoteException?e)?{e.printStackTrace();}}};}


以上代码放在:


https://github.com/BuilderPattern/AidlTransactionServer.git


https://github.com/BuilderPattern/AidlTransactionClient.git


(2)如果你是一个比较有求知欲的键盘侠,你通过一顿操作在 IMathAidl.java 中就会发现,该 AIDL 会生成 java 类,实际的交互都这里完成。问题来了,是不是可以不用定义 aidl 接口,直接采用 IMathAidl.java 类中的方式去实现呢?答案是:肯定可以!因为,本质上就是 aidl 通过构建生成对应的 IMathAidl.java 文件来实现具体操作,如下:1.Server 端创建一个 Service 并注册:


public?class?NoAidlService?extends?Service?{


public?IBinder?onBind(Intent?t)?{return?mBinder;}


private?NormalBinder?mBinder?=?new?NormalBinder();


private?class?NormalBinder?extends?Binder?{@Overrideprotected?boolean?onTransact(int?code,?Parcel?data,?Parcel?reply,?int?flags)?throws?RemoteException?{//0 加,1 乘 switch?(code)?{case?0:?{data.enforceInterface("NoAidlService");//检测标识 int?_arg0?=?data.readInt();int?_arg1?=?data.readInt();int?_result?=?_arg0?+?_arg1;reply.writeNoException();reply.writeInt(_result);return?true;}case?1:?{data.enforceInterface("NoAidlService");int?_arg0?=?data.readInt();int?_arg1?=?data.readInt();int?_result?=?_arg0?*?_arg1;reply.writeNoException();reply.writeInt(_result);return?true;}}return?super.onTransact(code,?data,?reply,?flags);}}


}


<service?android:name=".NoAidlService"><intent-filter><action?android:name="com.sinitek.noaidl.myservice"?/><category?android:name="android.intent.category.DEFAULT"?/></intent-filter></service>


2.Client 端连接 Server 端,发送收据/等待回复:


public?class?MainActivity?extends?AppCompatActivity?{


IBinder?mBinder;ServiceConnection?mServiceConnection?=?new?ServiceConnection()?{@Overridepublic?void?onServiceConnected(ComponentName?name,?IBinder?service)?{mBinder?=?service;}


@Overridepublic?void?onServiceDisconnected(ComponentName?name)?{


}};


TextView?mTextView;


@Overrideprotected?void?onCreate(Bundle?savedInstanceState)?{super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mTextView?=?findViewById(R.id.activity_main_operate_tv);Intent?intent?=?new?Intent("com.sinitek.noaidl.myservice");intent.setPackage("com.sinitek.transactionservernoaidl");bindService(intent,?mServiceConnection,?Context.BIND_AUTO_CREATE);initEvent();}


private?void?initEvent()?{mTextView.setOnClickListener(new?View.OnClickListener()?{@Overridepublic?void?onClick(View?v)?{mRcvSnd();}});}


public?void?mRcvSnd()?{


if?(mBinder?==?null)?{return;}Parcel?_data?=?Parcel.obtain();Parcel?_reply?=?Parcel.obtain();int?_code?=?(int)?(Math.random()?*?6)?%?2;int?_result;try?{_data.writeInterfaceToken("NoAidlService");//客户端标识_data.writeInt(6);_data.writeInt(6);mBinder.transact(_code,?_data,?_reply,?0);_reply.readException();_result?=?_reply.readInt();Toast.makeText(MainActivity.this,?"收到回复:"?+?_result,?Toast.LENGTH_SHORT).show();


}?catch?(RemoteException?e)?{e.printStackTrace();}?finally?{_reply.recycle();_data.recycle();}}


}


有关该交互过程比较容易理解,依次:创建 Server》Client 连接 Service》Client 向 Service 发送数据》Service 收到 Client 数据进行一系列分析操作,做出回应》Client 拿到 Service 的响应结果,如下图所示:



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


(一)进程间通信有多种方式,这里主要介绍 Android 进程间通信 Binder 特点及实现过程:在 Android 中,一个进程通常无法访问另一个进程的内存。为了进行通信,需将其对象分解成可供操作系统理解的原语,并将其编组为可供开发人员操作的对象。如果开发人员直接编写执行该操作的代码较为繁琐,因此 Android 引入 AIDL 实现进程通信。aidl(Android Interface Definition Language)接口定义语言,可以定义客户端与服务均认可的编程接口,实现进程通信。学习 Binder 前,强烈建议先了解 aidl 的实现过程,通过学习 aidl 的工作过程,我们会对进程通信的流程有大致的概念,有利于底层执行的命令、调用接口和执行上层写入/读取相对应的理解,后面学习底层的过程就好比验证每个过程的更深层实现。比如:C/S 端通信 transact/onTransact 方法内实现数据交互,消息源筛选、数据类型及数据的读写,特点一目了然;鉴于篇幅,有关 aidl 的实现过程请自行学习,尽量熟练掌握(可以学习 Android 开发艺术探索和张鸿洋的博客)。特点:安全、传输性能高和操作相对简单;安全性,对比 socket/管道通信方式,Binder 通信过程中,系统每创建一个进程会分配对应的 PID 和 UID(此特点还可以用于多进程项目的某些方向区分优化),进程之间通过进程 id 匹配确认消息来源;传输性能高,传输数据不需要 tcp 协议的三次握手,传输过程只需一次拷贝,操作简单;共享内存虽然不需要拷贝,但实现起来要复杂很多。


(二)了解下内存映射函数 mmap 和读写数据接口 ioctl:mmap 用于建立用户空间和内核空间的映射关系,实现映射关系内存空间共享数据;建立映射关系的不同内存空间的数据操作,会映射到建立关系的其他内存空间,对应关系如下图:



ioctl 是底层和应用层之间定义的接口协议(用于操作写入/读取数据),不提供单独的 read/write 接口,一次调用实现写入读取数据,满足数据交互同步。假设需要进行写入/读取数据(write_size 和 read_size 都大于 0),在操作写入和读取的过程,先将 write_buffer 里面的数据写入 Binder;然后同步等待数据返回,从 Binder 中读取数据存入 read_buffer。因为 BINDER_WRITE_READ 命令包含写入和读取数据两步操作,且是否进行写入和读取操作都是根据 Binder 的 write_size 和 read_size 确定的。假如只写入,设置 Binder 的 write_size 大于 0,read_size 等于 0 即可;其他情况同理。


(三)接下来对进程通信 C/S 端采用 Binder 通信过程的 write


《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享


/read 及数据传递进行介绍:在 aidl 操作的过程,有单独的 write 和 read 方法,在底层命令是一次执行,写入/读取根据顺序判断是否需要执行(write_size/read_size)对应操作。比如 Server/Client 端执行 write/read 完成后,进入等待接收数据状态,接收到数据之后做相应操作并回应 reply。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 创建,如下图:



通信主要由四部分组成: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 接收数据并执行注册过程,如下图:



实际上要完成上面的过程,首先 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 对象进行写入/读取,如下图:



所以,进程间通信就可以分为:SMgr 和其他进程,Server、Client 和 SMgr 两种。本质上都是用对应进程的 Binder 对象实现写入/读取等操作。区别在于进程对应的 Binder 对象:前者系统注册的时候就会定为 0 号引用;后者需要 SMgr(Binder 对象管理中心)实现注册,后续在注册表中根据定义的 key 取出对应进程的 Binder 引用执行操作。


本文参考资料:


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

最后

分享一份 NDK 基础开发资料



分享内容包括不限于高级 UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter 等全方面的 Android 进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!


本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

用户头像

嘟嘟侠客

关注

还未添加个人签名 2021.03.19 加入

还未添加个人简介

评论

发布
暂无评论
Android跨进程通信导论,2021年Android面试心得