在一些中小型app中主动使用多进程的概率较低,但是也不代表小型app只有一个进程,比如集成了推送一般第三方就是重开的进程,只是我们没有注意到而已。但是大型app中多进程是非常常见的,就以微信为例,我们可以查看一下微信的进程,发现有很多个,那么既然多进程是一个较复杂的概念,为什么微信会使用这么多进程呢?
一、为什么要使用多进程?
1、突破虚拟机分配进程的运行内存限制;
在Android中,虚拟机分配给各个进程的运行内存是有限制值的(根据设备:32M,48M,64M等)随着项目不断增大,app在运行时内存消耗也在不断增加,甚至系统bug导致的内存泄漏,最终结果就是OOM。
2、提高各个进程的稳定性,单一进程崩溃后不影响整个程序;
小程序进程崩溃,不影响其他进程,不会导致闪退
3、对内存更可控,通过主动释放进程,减小系统压力,提高系统的流畅性;
在接收到系统发出的 trimMemory(int level) 中主动释放重要级低的进程
二、如何使用多进程?
使用多进程很简单,只需要清单文件中使用process进行标注即可
三、多进程如何通信?
binder是Android跨进程的底层机制,跨线程之所以不推荐Socket、管道(pipe)来实现,是因为binder的性能更好,安全性更高。当然也有广播、ContentProvicer也有各自的应用场景,只是不推荐使用,比如广播有溯源难的问题。
我们知道,在进程A中不能直接调用到B中的方法,因为不同进程是加载在不同的内存中,不能直接进行通信。内存分为用户空间和内核空间,用户空间供我们使用,内核空间是公用。我们app中的数据都是保存在用户空间,想要实现进程中通信,就必须将数据从用户空间拷贝到内核空间,然后再从内核空间拷贝到用户空间,这样进行交互。所以就必须依靠内核空间进行通信,其他的通信方式比如管道也是如此,但是binder是通过映射的方式,只需要赋值一次就可以,所以效率比管道高。
四、什么是AIDL
Android Interface Definition Language,接口定义语言。
Binder是Android系统中进程间通讯的一种方式,也是Android系统中最重要的特性之一。 AIDL就是定义自动生成Binder相关代码的语言。
我们已经知道了进程间通信最好的方式是使用binder,那么我们应该如何告知系统我们想做什么事情呢,最简单使用最多的方式就是AIDL语言了,我们只需要通过AIDL语言进行编程,接下来AIDL会自动帮我们生成binder文件,节省我们的时间。接下来我们通过一个demo来演示一下客户端、服务端是如何通过AIDL进行交互的,我们让客户端、服务端分别作为一个独立app可能会更加清晰明了。
步骤一 在服务端app中新建aidl文件
选中包名,右键new->aidl->aidl file,就会弹出给aidl取名的弹框,在里面输入名字确定,系统就会自动在和java同级目录下新建aidl文件夹,并且新建好了aidl文件。内容很简单,首先导入了我们自定义的实体类,然后写好了添加和获取列表的2个方法供客户端来调用。
步骤二 服务端新建一个数据实体类Person作为待传输数据
需要注意的是,这里也要先新建AIDL文件,然后再新建Person文件。并且需要特别注意的是,这个AIDL文件的名字一定要和javaBean名字一样,即文件名必须叫Person.aidl。不然是会报错的,第一次发现的时候调好久,血的教训。。。新建好Person类以后需要实现Parcelable接口,代表可以传输数据,这次我在Person类中定义了2个属性name和grade。
步骤三 服务端创建服务service
这里面功能很简单,相当于是一个仓库,保管着所有的实体类对象。最后需要在清单文件中注册service,并且加上android:exported='true'代表当前服务是公开出去,允许其他进程调用。
步骤四 服务端中启动service
在服务端的某个地方启动service,这里我们在打开服务端app的首页时直接启动。
步骤五 客户端配置AIDL和javabean
aidl:只需要将服务端新建的2个aidl文件全部复制到客户端即可,注意这里一定要保证一样才能进行通信,所以一般都是直接复制过去就好。
javabean:这里需要注意,必须新建一个和服务端一模一样的包,然后将服务端的Person复制过来,保证在客户端也能调用到该类。
步骤六 客户端使用服务端提供的接口进行通信
我在客户端这边写了一个按钮,每次点击以后调用服务端的addPerson方法添加一个用户,然后将用户信息打印出来。这里需要注意的是,需要绑定好服务端的服务,具体代码如下:
步骤七 运行代码查看结果
这里先运行服务端,然后运行客户端,运行后在客户端的logcat成功打印出日志,说明跨进程调用成功。
binder使用总结:这里我们使用了aidl语言来操作binder实现了跨进程的调用,而实际上aidl的作用仅仅是用来生成binder文件而已,只不过是这个生成的文件比较复杂,可读性低,一般不自己写。如果我们能自己写出该binder文件的话完全可以不用aidl了,该文件可以在build中查看到,而且客户端、服务端中都有并且内容一模一样。当然aidl也有缺陷,就是编写aidl文件的时候没有报错提示,所以调试起来比较麻烦。以上我们简单使用了binder实现进程间通信,接下来我们再一起分析一下binder核心原理。
五、binder源码分析
我们可以从aidl生成的binder代码来入手分析,继承自IInterface.
接下来点击AS左侧的选项查看该类的结构图,在核心的Stub中有2个成员,一个是Proxy,另一个是Stub(),其中proxy是发送数据,stub是接收数据。
服务端可能会有很多的aidl文件,具体调用哪个是通过attachInterface进行绑定的。
接下来我们来看看asInterface方法,该方法在客户端调用,然后在服务端实现。客户端传入了binder,然后返回了一个aidl对象。调用之后首先搜寻本地的aidl接口,排除不跨进程的情况,然后再调用proxy()将客户端的引用传给binder,binder拿到了引用以后就可以操作客户端了。
接下来再看看核心的proxy内部类里有什么,是不是似曾相识,这里面addPerson、getPersonList方法均为服务端定义的供客户端使用的方法。其中_data是客户端传给服务端的数据,而_replay是服务端处理完返回给客户端的结果。最后通过binder对象的transact将当前进程挂起,然后调用服务端中的方法。这里的Stub.TRANSACTION_addPerson是谷歌官方考虑到传递方法名字符串比较耗性能,然后服务端和客户端的方法和顺序是一模一样的,所以这里就直接用数字来代替,代表第几个方法。这也从侧面说明了客户端和服务端定义的aidl文件为何要一致了,不一致的话这里是会找不到指定方法的。注意其中的flags如果为0代表双向,如果为1代表单向。
在调用完transact方法后就会调用服务端的onTransact(),我们找到服务端的onTransact方法一看究竟。这里就很清晰明了了,判断刚刚传过来的方法下标,然后进行指定的处理,处理完以后将结果通过writeXXX存到reply中,所以前面的_reply就有值了。
总结:本文对多进程的使用场景进行了分析,还简单提到了多种跨进程的方式,经过对比AIDL的性能会更高。所以分享了通过AIDL语言生成了binder调用代码实现了跨进程通信,进程A成功获取到并修改了进程B的值。并且查看了AIDL生成的binder文件的源码,知道了AIDL语言的作用,以及简单分析了binder工作的一整套流程。但是binder其实还有更多复杂的内容需要去学习,比如binder为什么要借助service来完成通信等,以后有空会继续学习和更新。