欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Binder机制原理、源码、AIDL,IBinder,Binder,IInterface,BinderDriver,需要的都在这里了

程序员文章站 2022-06-06 15:30:15
...

导读:

本文分为三个阶段,

  • 第一阶段,原理概述,力争说人话的基础上,讲明白Binder机制在搞什么,为什么这样搞,以及具体是怎么搞的。
  • 第二阶段,代码层面描述,主要描述了,AIDL、IBinder、Binder、IInterface、Binder Driver是如何设计和实现第一阶段的构想。
  • 第三阶段,实例操作,不使用半自动的AIDL工具,纯手动利用Binder实现一把进程间通信。

相信这一套军体拳下来,应该能薛伟地掌握Binder的相关原理。

P.S. 本文code部分使用了伪代码,包含但不限于kotlin、java和汉字。。。不过应该都能看懂

Binder是Android系统用来实现高效IPC(进程间通信)所搭建的框架。在Android系统中的作用和他的名字一样,是像胶水一样,把系统各个服务进程粘合到了一起。

那么Android系统为什么要重新构建一个IPC框架呢?而不是直接使用Linux提供的IPC方案,类似管道,信号,socket等,首先是安全性的考量,这些Linux 原生IPC方案,都不能直接携带进程的信息,只能依赖上层的协议,比如socket是通过ip和端口来区分不同的进程,让伪造访问的难度降低,而Binder 的设计里是带有uid和pid来标识进程身份的,所以安全性会高很多;其次Binder机制的性能会非常的高,他只需要一次内存的拷贝即可, 而Android系统有很多个重要的服务进程,例如 AMS,PMS,SMgr,进程之间的交互非常的频繁,对IPC的性能有较高的要求, 最简单的例子,启动一个Activity也是需要IPC的,启动过程不再本文的描述范围内,有兴趣的话可以自行查阅资料。

首先为什么要IPC框架:

Binder机制原理、源码、AIDL,IBinder,Binder,IInterface,BinderDriver,需要的都在这里了
Android系统是基于linux的, 所以存在一个进程隔离的概念, 每个进程都由于虚拟地址的技术,认为自己独享整个系统, 所以并不能直接访问对方的内存地址,导致进程隔离。

那么物理概念的内存肯定只是大家共用的, 一定是可以访问的, 那么如果想完成这种操作就需要进入到内核空间来完成,此时沉入内核空间的进程被称为进入内核态, 而在用户空间则是用户态
在内核空间可以获得高级别权限,例如访问所有的内存地址。
那么显然我们打通User Space 中的两个进程,肯定需要一个内核模块来完成,所以我们需要一个Binder Driver,作为桥梁来互相访问各自进程的内存。
Binder机制原理、源码、AIDL,IBinder,Binder,IInterface,BinderDriver,需要的都在这里了

那么为什么Binder Driver这个外来物可以做为内核空间的驱动而存在呢,这是因为LKM机制,可以让一个可编译的模块作为内核模块进入到内核空间提供服务,所以Android 系统构建了一个Binder Driver 在内核态提供支持,讲了这么多,好像想做什么大概是明白了,但具体是怎么做的呢,还是不清楚,面对这种复杂的问题,就是拆分,把他拆分成一个一个问题逐一解决,Android团队设计的时候估计也是这么想的。
假设有一个服务进程S,一个客户端进程C(Binder机制是个C/S架构,你们应该已经发现了)首先一个问题就扑面而来,客户端C如何调用服务端S提供的服务呢,或者说我作为C如何能执行到S的函数,在说白了,需要个协议之类的东西,Binder Driver 对C提供了一个代理,一个IBinder对象,这个东西在C看来就是S,里面有相同的函数可以提供调用, 当C调用了代理IBinder提供的函数时候,Binder Driver就会让C的线程暂时挂起, 然后他去S那执行真正的函数,执行完毕,就把结果返回给C,唤醒C的线程。感觉好像C调用了S一样。
那么第二个问题就来了,我怎么找到C说的这个S,此时需要一个ServiceManager进程登场了, Server进程会通过Binder Driver向ServiceManager注册自己,好比加入通讯录的感觉, 此时能叫上来名字的话就可以找到本人的电话了。
Binder机制原理、源码、AIDL,IBinder,Binder,IInterface,BinderDriver,需要的都在这里了
所以大致想实现的设计是这样的:

首先定义一个接口 IDoSomeThing, 这个接口代表了S能干什么的能力,所以S需要实现这个接口 IDoSomeThingImpl , 并且给出这个实现的实例, 供别人调用。
但Binder Driver也要去S实现另一个接口,代表你要通过驱动来提供给其他进程使用,这个接口叫刚才提到的IBinder,所以这个IDoSomeThing还需要实现IBinder,至此Server进程的工作算是做完了。
那么C进程呢, C进程也得有这个IDoSomeThing的接口, 而且要和S的定义一模一样,虽然没有实现,但是起码知道对方S有什么能力, 当我想调用的时候, 我需要和BinderDriver要一个代理对象,代表S,对吧,这个代理对象的类型,也是IBinder, 这个IBinder 就被设计成代表一种可以使用BinderDriver的支持的能力, 至于是提供S的实现,还是提供给C的代理对象,那么就交给S的实现类和BinderDriver来处理。

至此,技术上的设计完成了,感觉上是可行的,那么我们来通过源码来看Android是怎么把这一套玩转跑通的。

从AIDL开始吧,aidl第一部就是使用aidl的语句定义一个接口,例如IDoSomeThing,里面有一个方法doSomeThing(), 你要把它定义为.aidl文件, 编译一下 gradle会帮你生成这个你定义的java版本 的接口:

interface  IDoSomeThing  : android.os.IInterface{
		doSomeThing() : Unit;
        // android.os.IInterface 定义的方法
		//@Override asBinder(): Binder
}

这个IInterface意思就是你是一个Binder官方认证的接口,你需要实现一个asBinder,来提供一个Binder对象。
但还没完,他还会在你的接口里生成一个内部抽象类Stub,而这个内部抽象类是无比的重要。

interface  IDoSomeThing  : IInterface{
	class Stub extends Binder : IDoSomeThing  {
		public fun asInterface ( ibinder:IBinder ): IDoSomeThing  {
		if(调用我的是自己进程内)
			//返回自己的实现
			return ibinder as IDoSomeThing  
		else 证明是其他的进程来调用
			返回代理对象
			return new Stub.Proxy(ibinder);
		}
		
		@Override
		fun asBinder:IBinder =  this;
		
		fun onTransact(){
		    调用我的doSomeThing函数
			读一下有没有参数传过来
			val result = this.doSomeThing()
			把result结果写回去。
		}
	}
	.
	.
	.
}

这个抽象类,有啥用,首先他也是一个IDoSomeThing ,所以在我们S进程里面,提供的实现类其实是这个Stub 的实现类。

class MyServiceProcess: Service{
	class MyBinder : Stub{
		doSomeThing(){
			//实现
			doSomeRealThing...
		}
	}
	@Override
	fun onBind(intent: Intent):IBinder = new MyBinder();

}

这里通过Service 的方法onBind,就把我们的实例提供给了BinderDriver.所有的跨进程通讯Server端都需要一个Service来提供给BinderDriver吗?大家可以扒一扒 ActivityManagerServer、IActivityManager、ActivityManagerNative、ActivityManagerProxy、ActivityManager这几个类的源码,看看AMS的IPC过程。
之前说过了, C和S必须都有接口,所以aidl生成的类,必须在两个进程都有才可以,两个APP的话就复制过去。
Server端先看到这里, 我们再看一下Client端,客户端绑定 远程服务的时候,是调用bindService方法(aidl的具体使用方式不再本文描述内,可以查看官方文档,写的特别好).

bindService(Intent(this, RemoteService.class){ service: IBinder->
	//onServiceConnected的回调内
	我的server进程服务代理对象 mService = Stub.asInterface(service);
}

欧,看到了asInterface, 这个方法,特别的简单,如果没有跨进程,就是直接返回sub, 如果跨了进程就返回代理对象,而Proxy这个类,又是stub的子类。

class Stub...{
	class Proxy:IDoSomeThing {
		private mRemote: IBinder;
			constuct(remote : IBinder){
			mRemote = remote;
		}
		
		@Override
		fun asBinder = mRemote;
		
		fun doSomeThing(){
			写参数
			....
			val result  =  mRemote.transact(参数);
			....
			读结果
		}
	}
}

马上可以看到的是,她和stub不同,stub is 一个IBinder, 而Proxy只是持有了IBinder的引用,一个是策略模式,一个是代理模式。
可以看到C端通过bind到远程服务S端,会获得一个IBinder对象,我们用asInterface把这个IBinder传过去,换成S端的代理对象Proxy, Proxy是一个IDoSomeThing,所以当我们调用方法doSomeThing的时候, Proxy使用了Binder的一个方法transact,这就通知BinderDriver来处理一下, BInderDriver会调用server中的stub的实现类中的 onTransact()方法,我们在翻上去看一下, 完整的流程就无比清晰了:

C端进程的一个线程tc1,调用Proxy.doSomeThing, Proxy调用transact通知驱动, tc1挂起,
S端进程的一个线程ts1, 会被驱动回调onTransact,执行doSomeThing的真正实现 doRealSomeThing完成后返回给驱动,驱动在唤醒tc1线程,把结果返回,至此结束。

当然,一个S端可能对应着多个C端, S端是一个线程池维护的,给每个C端分别分配线程, 所以,onTransact可能会被多个线程回调,就会ts1,ts2,ts3等等,如果涉及到共享数据,做好多线程控制吧!

至此Binder机制 Java层的代码基本分析完毕,放一张剽窃来的画的贼屌的图吧,接口可能定义的不一样不过不妨碍感受一下。
Binder机制原理、源码、AIDL,IBinder,Binder,IInterface,BinderDriver,需要的都在这里了

终于到最后一步,实战阶段了,我们的目标是: 不使用aidl工具生成, 我们手写代码完成功能:
同学们自己去写吧。。反正我写了这个博客,基本上都是手撸了一遍, 还需要掌握一下Parcel这个类,毕竟跨进程涉及到把对象序列化和反序列化,相信你们也可以的。