当前位置: 首页 > 科技观察

干货-吃透ANDROIDBINDER的通信架构(下篇)

时间:2023-03-21 19:50:44 科技观察

一、简介1.1Binder架构思考Android内核基于Linux系统,Linux有多种进程间IPC方式:管道、消息队列、共享内存、套接字、信号量、信号。为什么Android非要用Binder进行进程间通信呢?在讲Binder架构之前,先简单说一下大家熟悉的TCP/IP五层通信架构:应用层:直接为用户提供Service;传输层:传输的是消息(TCP数据)或用户数据报(UDP数据)网络层:传输的是数据包(Packet),比如路由器数据链路层:传输的是帧(Frame),比如Ethernet网络交换机物理层:比特在相邻节点之间传输,如集线器、双绞线等,这是一个经典的五层TPC/IP协议体系。这种分层的设计思想使得每个子问题都可以设计成一个独立的协议,这个协议的设计/分析/实现/测试变得更加容易:层是独立的,例如应用层可以使用传输层提供的功能不知道其实现原理;如果接口定义好,即使层中的方法发生变化,只要接口不变,对系统是没有影响的;结构的解耦让每一层都可以使用更合适的技术方案和更合适的语言;方便维护,可以分层调试定位问题;Binder架构也是分层架构设计的,每一层都有自己不同的功能:Java应用层:对于上层应用,通过调用AMP.startService,不需要关心底层。经过层层调用,最后必然会调用AMS.startService。JavaIPC层:Binder通信采用C/S架构,Android系统的基础架构在Java框架层已经设计了Binder客户端类BinderProxy和服务类Binder;NativeIPC层:对于Native层,如果需要直接使用Binder(比如媒体相关),可以直接使用BpBinder和BBinder(当然也有JavaBBinder)。JavaIPC在上层的通信也是基于这一层。Kernel物理层:这里是BinderDriver,前三层都是运行在用户空间,用户空间的内存资源是不共享的。每个Android进程只能运行在自己进程拥有的虚拟地址空间中,而内核空间是可以共享的。真正通信的核心环节还是在BinderDriver。1.2分析起点Binder在Android系统中被广泛使用,几乎是整个Android架构的中坚力量。Binder系统如此庞大,所以这里我们需要找个起点穿针引线,看看Binder的全貌。那么本文将从一个新的角度出发,以startService流程分析为例来讲解Binder的作用。首先在initiator进程中调用AMP.startService,驱动binder,最后调用系统进程AMS.startService,如下图所示:AMP和AMN都实现了IActivityManager接口,AMS继承自AMN。其中AMP作为Binder的客户端,运行在各个app的进程中,AMN(或AMS)运行在系统进程system_server中。1.3BinderIPC原理Binder通信采用C/S架构,从组件的角度来看,包括Client、Server、ServiceManager、binder驱动,其中ServiceManager用于管理系统中的各种服务。下面说一下startService过程中涉及到的Binder对象的架构图:可以看出,无论是注册服务还是获取服务的过程,都需要ServiceManager。注意这里的ServiceManager指的是Native层的ServiceManager(C++),不是framework层的ServiceManager(Java)。ServiceManager是整个Binder通信机制的大管家。它是Android进程间通信机制Binder的守护进程。客户端与服务端进行通信时,需要先获取ServiceManager接口,然后才能启动通信服务。当然,可以缓存目标信息。您不需要每次都请求ServiceManager。图中Client/Server/ServiceManage之间的相互通信是基于Binder机制实现的。由于通信是基于Binder机制,所以也是C/S架构,图中三大步骤都有对应的Client和Server端。注册服务:首先,AMS向ServiceManager注册。进程:AMS(system_server)进程为客户端,ServiceManager为服务端。获取服务:客户端进程在使用AMS之前,首先要从ServiceManager中获取AMS的代理类AMP。进程:AMP所在进程(app进程)为客户端,ServiceManager为服务端。使用服务:app进程可以根据获取到的代理类AMP直接与AMS所在进程进行交互。进程:AMP进程(app进程)为client,AMS进程(system_server)为server。图中Client、Server、ServiceManager之间的交互用虚线表示,因为它们之间并不直接交互,都是通过BinderDriver进行交互,实现IPC通信方式。其中Binder驱动位于内核空间,Client、Server、ServiceManager位于用户空间。Binderdriver和ServiceManager可以看作是Android平台的基础设施,而Client和Server则是Android的应用层。这三大进程中的每一个都是一个完整的BinderIPC进程。接下来从源码的角度,只介绍第三部分。流程使用服务,即扩展了AMP.startService如何调用AMS.startService的流程。Tips:如果你只想了解大概的流程,不打算细说源码,那你可以跳过通信流程的源码分析,直接看这篇文章***段和***段也能看懂关于活页夹。2.通信过程2.1AMP.startService[→ActivityManagerNative.java::ActivityManagerProxy]主要功能:获取或创建两个Parcel对象,data用于发送数据,reply用于接收响应数据。将startService相关数据封装成Parcel对象数据,其中descriptor="android.app.IActivityManager";通过Binder传递数据,将响应消息写入reply;读取reply响应消息和Component对象的异常;2.2Parcel.obtain[→Parcel.java]sOwnedPool是一个缓冲池,大小为6,用于存储parcel对象。这样设计的目的是省去每次创建Parcel对象的开销。obtain()方法的作用:首先尝试从缓存池sOwnedPool中查询是否有缓存的Parcel对象,存在则直接返回该对象;如果没有可用的Parcel对象,则直接创建Parcel对象。2.2.1newParcel[→Parcel.java]nativeCreate这是一个native方法,通过JNI进入native层,调用android_os_Parcel_create()方法。2.2.2android_os_Parcel_create[→android_os_Parcel.cpp]在C++层创建一个Parcel对象,对象指针强制转换为long类型保存到Java层的mNativePtr对象中。创建Parcel对象后,使用Parcel对象写入数据。下面以writeString为例。回收再利用。当缓存池满了,就不再加入缓存池。这里有两个Parcel线程池,由mOwnsNativeParcelObject变量决定:mOwnsNativeParcelObject=true,即调用不带参数的obtain()方法得到的对象在回收时会放入sOwnedPool对象池;mOwnsNativeParcelObject=false,即调用带nativePtr参数的obtain(long)方法获取的对象,在回收时会放入sHolderPool对象池;2.3writeString[→Parcel.java]2.3.1nativeWriteString[→android_os_Parcel.cpp]2.3.2writeString16[→Parcel.cpp]提示:除了writeString(),Parcel.java中大量的native方法都会调用相应的方法android_os_Parcel.cpp,然后调用Parcel.cpp中对应的方法。调用过程:Parcel.java–>android_os_Parcel.cpp–>Parcel。cpp.2.4什么是mRemote?mRemote的诞生是从ActivityManagerProxy对象(简称AMP)的创建开始的。AMP是通过ActivityManagerNative.getDefault()获取的。2.4.1AMN.getDefault[→ActivityManagerNative.java]gDefault数据类型为Singleton,为单例模式。接下来我们看一下Singleto.get()的过程。2.4.2调用时需要创建gDefault.get***,创建后保存在mInstance对象中。可以直接使用.2.4.3gDefault.create文章Binder系列7—框架层分析。可知ServiceManager.getService("activity")返回的是指向目标服务AMS的代理对象BinderProxy。2.4.4AMN.asInterface[→ActivityManagerNative.java]此时obj是一个BinderProxy对象,记录了远程进程system_server中AMS服务的binder线程的句柄。2.4.5queryLocalInterface[Binder.java]对于BinderIPC的进程,同一个进程的调用会是asInterface()方法返回的本地Binder对象;不同进程的调用都会是远程代理对象BinderProxy。2.4.6CreateAMP[→ActivityManagerNative.java::AMP]可见mRemote是指向AMS服务的BinderProxy对象2.5mRemote.transact[→Binder。java::BinderProxy]mRemote.transact()方法code=START_SERVICE_TRANSACTION,data保存descriptor、caller、intent、resolvedType、callingPackage、userId这6项信息。transactNative是native方法,通过jni调用android_os_BinderProxy_transact方法。2.6android_os_BinderProxy_transact[→android_util_Binder.cpp]gBinderProxyOffsets.mObject保存BpBinder对象,这是Zygote启动时调用AndroidRuntime::startReg方法完成jni方法的注册。register_android_os_Binder()过程有一个初始化和注册的BinderProxy操作,完成gBinderProxyOffsets的赋值过程。接下来,进入这个方法。2.7BpBinder.transact[→BpBinder.cpp]IPCThreadState::self()采用单例模式保证每个线程只有一个实例对象。2.8IPC.transact[→IPCThreadState.cpp]transact主流程:先执行writeTransactionData()向Parcel数据类型的mOut写入数据,此时mIn没有数据;然后执行waitForResponse()方法,循环执行,直到收到Response消息。调用talkWithDriver()与驱动程序交互。收到响应报文后,会写入mIn,根据收到的响应不同,进行相应的操作。这里根据是否有TF_ONE_WAY标志调用waitForResponse:设置oneway时,调用waitForResponse(NULL,NULL);oneway未设置时,调用waitForResponse(reply)或waitForResponse(&fakeReply)2.9IPC.writeTransactionData[→IPCThreadState.cpp]【本文为“小米开放平台”专栏原创文章,“小米开放平台”微信公众号小米开发者]