在上一篇文章Binder总结篇1-Binder原理中,我们大概理解了Binder的运行原理,那么我们在什么样子的应用场景下会使用到Binder呢?
就我个人而言,是在IM系统开发当中使用到多进程开发,也就需要Binder来进行通信了,本文是编写实际的例子的,涉及到的点有:
1.AIDL
包括支持的数据类型,定义以及使用等
2.Service
包括一些启动,数据获取以及数据回传等。
本文的demo地址是:AndroidBinderSample
AIDL
关于AIDL的详细描述,可以看官网Android 接口定义语言 (AIDL)
他是Android的接口定义语言,用来具体实现Binder通信过程的数据传递,格式跟java的接口代码的编写差不多。
他是使用.aidl文件结尾,存放在man文件夹下的aidl文件夹下,当然你可以通过在gradle中配置aidl.srcDirs
来指定。
AIDL支持的数据类型
1.Java的基本类型,也包括String类型和CharSequence类型
2.List 和Map,其中List和Map中的元素必须是AIDL支持的数据类型,而且在Server端必须使用ArrayList或者是HashMap来接收。
3.其他的AIDL生成的接口
4.实现了Parcelable接口的实体,可以看详细介绍Android中Parcelable的原理和使用方法
AIDL文件,总得来说,AIDL分为两类文件,一种是接口类型,就是需要被调用被实现的。一种是声明Parcelable数据,作用就是把对应的Java实现了Parcelable接口的类映射到AIDL中,然后被AIDL的接口文件引用,需要注意的是这个AIDL文件的包名需要与Java实现Parcelable文件对应的包名一致。
例如:
我们声明了一个JavaDomain,在包app.androidbinder.domain
下,大致如下
1 | package app.androidbinder.domain; |
然后在AIDL文件下也需要建立对应的包名,然后编写UserInfo.aidl,如下:
1 | package app.androidbinder.domain; |
这样子,在别的AIDL中就可以引用这个UserInfo
了。
定义接口类型的AIDL如下:
1 | // UserService.aidl |
这就是两种AIDL文件类型以及对应的大致编写。
in、out、inout
在官方文档中支出,所有的非原语参数需要指示数据的方向标记,可以是in、out、inout。默认的原语是in,不能是其他流向。
这里指定的非原语是指:除了Java的基本类型外的其他参数,也就是对象。我们在AIDL使用的时候需要知道这个参数的流向。
那什么是数据的方向标记呢?
首先,数据的方向标记是针对客户端中的那个传入的方法参数而言。数据流向的标识符不能使用在返回参数上,只能使用在方法参数上面。
1.in:他表示的是这个参数只能从客户端流向服务端,比如客户端传递了一个User对象给服务端,服务端会收到一个完整的User对象,然后假如在服务端对这个对象进行操作,那么这个改变是不会反映到客户端的,这个流向也就是只能从客户端到服务端。
2.out:他表示,当客户端传递参数到服务端的时候,服务端将会收到一个空的对象,假如服务端对该对象进行操作,将会反映到客户端。比如,客户端传递一个User对象到服务端,服务端接收到的是一个空的User对象(不是null,只是有点像new一个User对象)。当服务端对这个User对象进行改变的时候,他的值变化将会反映到客户端。
3.inout,它具有这二者的功能,也就是客户端传递对象到服务端,可以接收到完整的对象,同时服务端改变对象,也会反映到客户端。
总结来说,in类似于传值,out类似于传引用,只是out的引用值到了服务端为空,inout则具有二者的功能,默认的是in。
Service
多进程之间的通信离不开的是Service,Android四大组件之一,这里不过多的赘述,只是需要知道我们在多进程通信当中,服务端最少提供一个Service
来,在onBind()
方法中返回实现AIDL接口的IBinder
对象。然后客户端通过bindService()
来连接,通过在ServiceConnection
的onServiceConnected
方法,通过调用AIDL 生成的文件中的Stub
的asInterface()
来在客户端获取服务实例,继而调用服务端的方法。需要注意,在跨app的进程调用中,对外暴露的Service
需要在清单文件中把android:exported
设置为true
。一般而言,我们还会配置一些fillter来进行过滤。
关于如何使用Service以及他的一些生命周期,一些方法区别(比如startService和bindService)请自己另外查阅文档,这里就不描述了。以下是本文Demo中的实例,使用的还是上面的AIDL,使用之前确保成功编译出对应的aidl生成文件:
服务端的Service
1 | package app.androidbinder2.services; |
清单文件的配置是:
1 | <service |
客户端
主要的代码如下:
1 | //...省略代码 |
AIDL的实际使用
我们就以上面定义的AIDL文件作为我们的例子。我们需要实现的一个需求是两个App之间通过AIDL实现数据互通,至于一个App之间多进程也是类似,后面再做例子。
其实上面已经是把例子的代码罗列的差不多了。这里列一下跨APP进程调用demo一般的步骤:
1.假如有需要自定义的数据需要通过实现Parcelable传递,那么先编写这种java文件
2.在客户单编写一份引入上面编写的Parcelable实体的AIDL文件,再编写一份AIDL接口(一般而言会把所有跨进程的接口编写在一起)
3.通过编译,当没有问题之后,需要把上面的需要跨进程的Parcelable的Java文件以及AIDL文件复制到Server端,包名需要一致,然后编译。
4.在Server端,新建继承Service的服务实现,然后在onBinder()中,返回实现我们编译生成的AIDL接口文件的Stub子类。
5.在Client端,通过ServiceConnection获取到Server端的Binder实例,进行调用。这里主要用到的是Stub的静态方法asInterface()。客户端就可以通过这个实例进行跨进程调用了。
同一个APP中跨进程通信
我们其实在实际中,使用最多的还是这一种情况,目前我所接触到的就是自己设计的IM系统中使用到。
他其实跟跨APP没有什么区别,同一个APP中跨进程,他只是需要一份AIDL文件即可。因为系统分配给一个进程的内存是有限的,而且默认的主进程处理的事务较多,在保活方面,在数据接收处理方面,使用多进程是有优势的。
Android的APP启用多进程方式是在清单文件中添加android:process
,设置该组件所处的进程名称既可。
比如
1 | android:process=":local"//他设置所处的进程名称是包名+local,他表示的是该组件处于自己的私有进程,其他进程的组件不可以跑在同一个进程中。 |
以上就是AIDL的基本使用,当然我们在实际使用中,会更加的复杂,比如我们会在IM进程中开启多个线程来处理消息,接受消息,轮训消息等,而且需要通过回调的方式主动通知主进程,一般需要IM端一个Service,主进程端一个Service等,这些我们后面在做讲述。
本文如有错误或侵权还请指出或联系删除,谢谢。