在《Android源码设计模式》一书中看到这么一段话:Android的Binder机制是一个庞大的体系模块,平时能接触到的最难懂的莫过与View的运行机制,但Binder比它还要复杂百倍,而且View的底层通信也依赖于Binder。对于开发者来说,这么底层的东西没必要太过于接触,只要学会如何使用它即可。
之前也是一直想搞清楚Binder是怎么实现的,但一直停留在整体架构和流程上的粗浅了解,很难深入下去。当看了这段话我也就不那么执着了,毕竟自己功力也确实还达不到,先学会怎么用,在逐渐加深了解更适合我,那“用”的是什么呢?
下面我们会简单介绍,Binder主要包括四大模块,对于底层的Binder驱动和ServiceManager来说我们没有必要自己去实现,这一部分逻辑最复杂,而且这部分逻辑Android已经为我们封装的很完美了,我们只需要自己实现Binder Client和Binder Server部分就可以了。
其中对于开发者来说,Binder Server必须由自己来实现,但编写这么一个Binder Server的难度也很大,Android就充分考虑到这点,提供了一个简单的方式来生成Android Server端,也就是AIDL(Android Interface Description Language)。Android提供了AIDL工具,可以将AIDL文件编译成Java文件。
Binder简介
核心概念
BInder主要涉及四大模块,这四者之间的关系类似于网络访问。
- Binder驱动:路由器
- ServiceManager:DNS
- Binder Client:客户端
- Binder Server:服务器
通信步骤
1、ServiceManager建立,负责管理所有的进程号
2、各个Server在启动后,在ServiceManager中进行注册
3、Client要与某个Server通信,向ServiceManager进行查询,ServiceManager会返回给Client该Server的相关信息
那Binder具体是如何跨进程通信的呢?先上张图:
假设我们的Client想要调用Server端的,返回值类型为Object的add()方法,这其实就是一个跨进程通信,我们来看下过程是怎么样的:
在Server启动之初,Server会在SM(ServiceManager)中进行注册,并告诉它我这里都有哪些方法,于是SM会将这些方法存放在建立好的一张表中。
接着Client端会向SM发来请求,请求调用Server端的Object类型的add()方法。我们知道,进程间进行通信,它们的数据都是在内核空间里,所以SM并不会返回给Client一个真正的Server端的Object对象,因为这是无法操作的。
实际上返回的是一个代理对象,而这个代理对象里面包含add()方法,但这个add()方法是个空方法,这个代理方法唯一的作用就是当Client端调用add()方法的时候,它会返回给Binder驱动,Binder驱动收到这个代理方法就会明白,它真正要访问的是Server端的add()方法,于是Binder驱动就会通知Server端,调用它的add()方法,将结果返回给SM,SM再返回给Client端。
AIDL
介绍
AIDL是“Android Interface Definition Language”的缩写,翻译过来就是“Android接口定义语言”。从它的名字中我们可以提取这么两个关键词:接口定义和语言。
关于”定义“
所有的AIDL文件大致可以分为两类:一类是用来定义parcelable对象,以供其他AIDL文件使用AIDL中非默认支持的数据类型的。一类是用来定义方法接口,以供系统使用来完成跨进程通信的。可以看到,两类文件都是在“定义”些什么,而不涉及具体的实现,而且定义的都是接口,这就是为什么它叫做“Android接口定义语言”。
关于”语言“
既然是一语言,主要需要学习的就是它的”语法“,AIDL语法包括七大部分:
- import:用于导入一个类,其与Java语言的不同之处在于,即便导入的类与当前AIDL文件在同一目录,也需要显示导入。
- package:用于声明该AIDL生成的Java类文件所在的包名。
- interface:用于声明一个AIDL的服务接口。
- oneway:用于修饰interface和方法,表示CLient不需要等待Server端服务方法返回,oneway interface表示该interface中所有方法都是oneway,通常oneway方法的返回类型都为void。
- 数据类型:AIDL列出的支持的类型可以作为参数或返回值使用。其支持的数据类型主要有:
- Java基础数据类型及其数组:int、long、char、boolean、byte
- String和CharSequence及其数组
- android.os.Ibinde
- List和Map
- 方向标记:用于标记参数的输入/输出类型,方向标记有in、out、inout三种:
- in表示参数由Client端设置并传递到Server端服务方法,服务端方法需要根据该参数做出处理。
- out表示参数由Client端传递到Server端服务方法,服务方法将设置该参数并将其返回Client端。
- inout表示Client端和Server端服务方法操作同一个参数。通常情况下,由Client提供一个参数值,server端服务方法修改该值并返回。
- parcelable:用于声明一个在AIDL中使用的自定义类。
开发步骤
创建“.aidl”文件
鼠标移到app上面去,点击右键,然后 new->AIDL->AIDL File,创建后Android Studio会自动把这个.aidl文件放到一个aidl的目录下,文件初始内容如下:
basicTypes方法中给我们展示了AIDL支持的基本数据类型。
向新建的aidl文件书写代码
|
|
代码写完之后,clean或rebuild一下工程会自动生成对应的.java文件,该Java文件的目录如下:
代码如下:
主要内容包括:
移植相关文件
使用AIDL进行客户端和服务端的通信有一个条件需要满足,那就是服务器端的各个AIDL文件必须要被拷贝到客户端的相同包名下,不然会不成功,所以,需要将 aidl 包复制到另一端的 main 目录。
这一步还有别的坑需要注意,不过因为我们这里的客户端和服务端都写在同一个项目中,所以这一步不需要做,其中的一些问题先暂且不去考虑,等在实践中遇到了再去解决。
编写服务端代码
要编写的服务端代码,就是实现AIDL中定义的方法接口的具体逻辑。
整体的代码结构很清晰,大致可以分为三块:
第一块是初始化:在 onCreate() 方法里面我进行了一些数据的初始化操作。
第二块是重写 IMyAidl.Stub 中的方法:在这里面提供AIDL里面定义的方法接口的具体实现逻辑。
第三块是重写 onBind() 方法:在里面返回写好的 IMyAidl.Stub实例 。
在清单文件中进行注册
|
|
到这里我们的服务端代码就编写完毕了。
编写客户端代码
|
|
代码逻辑同样也很清晰,首先建立连接,然后在 ServiceConnection 里面获取 IMyAidl接口对象,接着通过它来调用服务端的方法。
此时,我们的代码就全部编写完毕了,但运行却报错:Attempt to invoke interface method 'int com.cn.test.IMyAidl.add(int, int)' on a null object reference
,反复看代码逻辑并没有问题,在网上提出这个问题的人也很少,所以这个问题暂时还没有解决,只能靠自己深入学习,找出问题了。如果大家有遇到过这个问题的,欢迎在我的简书下留言,不胜感激。