Android多进程
哪个大型项目不是多进程的?进程间最基本的通信原理你了解多少?
手机正在运行的进程:
进程间通信基本原理
- 进程间通信的原理
- Binder 的作用
- Binder 的使用场景
Binder 是什么?
Binder简介:
Binder是Android系统新增的一种高效的进程间通信机制。四大组件都在使用Binder机制进行跨进程通信,Binder基于OpenBinder项目实现,java层的Binder通信用到的相关java类型都是对应native层C++类型的一个封装。
什么时候需要用到进程间通信?常见使用多进程的场景?
1.Android中的Binder无处不在,四大组件间的通信用的都是Binder。
2.调用系统服务,比如获取输入法服务、闹钟服务、摄像头、电话等系统服务都要用到进程间通信Binder。
3.吃大内存的模块,比如:地图模块、大图浏览、webview等,Android对内存的限制是针对于进程的,即每个进程的内存空间是有大小限制的。
为什么要多进程?使用多进程的原因?
1.突破虚拟机分配进程的运行内存限制。
Android虚拟机分配给每个进程的内存是有限的(如16M,32M,64M,96M等等),可以在新的进程中去执行,避免主进程OOM
2.提高app稳定性,单个进程崩溃不影响整个app
如微信的小程序进程崩溃后,微信依然可以正常运行。
3.对内存更可控,通过主动释放进程,减小系统压力,提高系统流畅性
在接收到系统发出的trimMemory(int level)里主动释放重要级低的进程。
4.Android的LMK机制
当系统内存不足时会杀死低优先级的进程,这样不会杀死主进程,主功能不受影响。
内存划分
进程A为什么不能直接与进程B通信?涉及到操作系统的内存划分。
内存空间分为用户空间和内核空间,用户空间与用户空间之间是隔离的,不能随意访问。
用户空间要访问其他用户空间的数据可以通过内核空间来进行。
对于32位系统,一般是内核空间是1GB,用户空间是3GB
看个例子: 包裹发送
城市A的包裹要发送到城市B,需要通过快递小哥经过公路来完成,这里的公路就可以认为是内核空间。快递小哥就可以认为是进程间的通信机制Binder。
即:
公路 - 内核空间
快递 - 进程间的通信机制
进程间通信为什么要新增Binder机制?
参考下面的 Android新增Binder机制的原因
Android新增Binder机制的原因
- 性能:Binder更高效
- 特点:Binder只需要一次数据拷贝
- 安全性:Binder更安全
Linux已经有通信机制,比如共享内存,Socket,消息队列,管道,信号量等等,为什么还要增加Binder 机制呢?
Binder与传统 IPC 对比
这里仅做Binder和共享内存、Socket的对比分析,对于消息队列、管道等可以类似分析。
传统的Socket的IPC传输数据
传统 IPC 传输数据,比如Socket,需要两次数据拷贝,性能低。
Socket(也是C/S架构)作为一种通用的通信方式,传输效率是非常低的,而且Socket一般用于跨网络的进程间通信方式,即使是用于本地的进程间通信时,一般也是用于低速的进程间通信的情况,而Binder的进程间通信是高速的。
进程A访问进程B无非是访问它的数据或者调用它的方法。
数据从用户空间到内核空间,再从内核空间到用户空间,经过两次系统调用:copy_from_user()和copy_to_user()。
和送快递非常相似,即把快递打包成包裹,然后给快递小哥(数据经历一次拷贝),然后快递小哥通过公路到达另一个城市,把包裹送给目标客户(数据经历一次拷贝),总共是两次数据拷贝。
Binder传输数据
整体看起来大致流程也是和上述的传统IPC传输数据一样的,数据也是从用户空间到内核空间,再从内核空间到用户空间,那为什么只需要一次数据拷贝呢?因为服务端进程的用户空间通过系统调用mmap机制和内核空间进行了映射,当客户端进程把数据拷贝到内核空间的那块内存后,服务端进程就可以读取到数据。
相当于你送快递的目标客户就是快递小哥或者是快递小哥认识的朋友,你把快递给快递小哥(一次数据拷贝),快递小哥直接把包裹给他朋友,无需经过公路跑去另外一个城市。
传统的共享内存的传输数据
共享内存为什么无需拷贝?
因为共享内存是,客户端进程的用户空间也和服务端进程一样,通过系统调用mmap机制和内核空间进行了映射,实现了客户端进程的用户空间、服务端进程的用户空间、内核空间这三者都映射到了同一块内存空间,服务端进程可以直接访问到客户端进程的数据,因此无需拷贝。
但是这种方式也造成了共享内存控制复杂,易用性差的特点,因为通过mmap把客户端进程的用户空间、服务端进程的用户空间、内核空间这三者都映射到了同一块内存空间,会造成读写数据同步的问题,可能存在客户端进程修改了这块内存空间的数据,结果服务端进程去读取的时候读取到的是之前旧的数据的情况,这就需要用到数据同步机制,而数据同步机制处理不好就非常容易导致数据不同步,产生死锁等问题。
Binder为什么不设计成像共享内存那样无需数据拷贝
Binder为什么选择设计成拷贝一次,为什么不像共享内存那样用mmap机制把客户端进程的用户空间、服务端进程的用户空间、内核空间这三者都映射到同一块内存,从而无需拷贝呢?
上述共享内存的传输数据已经讲述了共享内存的控制复杂,易用性差的特点以及原因,这就是Binder宁愿牺牲一些性能也要拷贝一次的原因。
当然,Binder为什么不选择像传统IPC传输数据那样拷贝两次?那当然是为了性能考虑,数据拷贝一次性能高。
从安全性考虑
1.Binder是使用Android系统为每个app分配的pid进行通信的,安全性高;而共享内存和Socket是依赖上层协议的,安全性低。比如,客户端进程通过Socket与服务端进程通信,客户端进程的pid是自己根据通信协议写在数据包上的,即pid是自己写的,而不是系统分配给你的,服务端进程无法根据pid判断安全性,这样就无法控制安全性。
2.Binder同时支持实名和匿名,安全性高; 而共享内存和Socket的访问接入点时开放的,不安全。即谁都可以访问接入点,是不安全的。
Binder的实名和匿名:
ServiceManager与实名Binder:SMgr的作用是将字符形式的Binder名字转化成Client中对该Binder的引用,使得Client能够通过Binder名字获得对Server中Binder实体的引用。
匿名Binder:并不是所有Binder都需要注册给SMgr广而告之的。Server端可以通过已经建立的Binder连接将创建的Binder实体传给Client,当然这条已经建立的Binder连接必须是通过实名Binder实现。由于这个Binder没有向SMgr注册名字,所以是个匿名Binder。
总结
Binder通信机制的优点:
- 采用C/S的通信模式。而在linux通信机制中,目前只有socket支持C/S的通信模式,但是Socket是低速的且安全性低,Binder比Socket速度快且安全性高。
- 传输性能更好。Binder只需一次数据拷贝。对比于Linux的通信机制,socket:是一个通用接口,导致其传输效率低,开销大;管道和消息队列:因为采用存储转发方式,所以至少需要拷贝2次数据,效率低;共享内存:虽然在传输时不需要拷贝数据,但其控制机制复杂(比如跨进程通信时,需获取对方进程的pid,得多种机制协同操作)。
- 安全性高。
(1). Binder是使用Android系统为每个app分配的pid进行通信的,安全性高;而共享内存和Socket是依赖上层协议的,安全性低。
(2). Binder同时支持实名和匿名,安全性高;而共享内存和Socket的访问接入点时开放的,不安全。
(3). Linux的IPC机制在本身的实现中,并没有安全措施,得依赖上层协议来进行安全控制。而Binder机制的UID/PID是由Binder机制本身在内核空间添加身份标识,安全性高;并且Binder可以建立私有通道,这是Linux的通信机制无法实现的(Linux访问的接入点是开放的)。 - 调用方便。客户端像调用本地服务一样调用服务端的服务。对用户来说,通过Binder屏蔽了client的调用server的隔阂,client端函数的名字、参数和返回值和server的方法一模一样,对用户来说犹如就在本地(也可以做得不一样),这样的体验或许其他ipc方式也可以实现,但Binder最初就是这么设计的。
源码分析
- 如何获得另一个进程的对象(另一个进程的句柄之类的东西)
- 如何进行通信的
以一个具体例子进行分析。
AIDL简介
- 客户端如何获取到AIDL的遥控器的?
- 通过这个遥控器是如何调到服务端的呢?
- 服务端又是如何做处理的?
AIDL就是基于Binder实现的,只是通过自动生成相关代码帮助我们简化了Binder机制的实现,否则,如果完全通过我们手写代码,要写很多Binder通信相关的代码,复杂且易出错,AIDL简化了这一过程。
1.Android Studio中,AIDL文件创建好后必须手动编译下(点击 Build -> Make Projet)才会在build目录下生成对应的代码;Eclipse会自动生成,不需要手动编译。
2.Android SDK里面的构建工具(Android\Sdk\build-tools\30.0.1\aidl.exe)会根据AIDL文件生成对应的代码。
3.aidl目录下的包名必须和java目录下的包名一样,否则会报错:
IMyAidl should be declared in a file called com\example\binder_client\IMyAidl.aidl
4.客户端的aidl文件必须和服务端的aidl文件一模一样,即把服务端的aidl文件拷贝一份到客户端中即可。
AIDL中的定向 tag
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
AIDL的简单使用
一个具体例子看下AIDL如何使用。
定义aidl文件IMyAidl.aidl,里面声明了接口方法:
这个文件以及内容在服务端和客户端中是一模一样的:
经过编译后(Build -> Make Project),会在build目录下生成对应的IMyAidl.java文件,内容如下:
//IMyAidl.java
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.binder.service;
//Person即使在同一个包下,也要声明,只要是非默认的类型,都要声明。
public interface IMyAidl extends android.os.IInterface {
/**
* Default implementation for IMyAidl.
*/
public static class Default implements com.binder.service.IMyAidl {
@Override
public void addPerson(com.binder.service.Person person) throws android.os.RemoteException {
}
@Override
public java.util.List<com.binder.service.Person> getPersonList() throws android.os.RemoteException {
return null;
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.binder.service.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.binder.service.IMyAidl";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.binder.service.IMyAidl interface,
* generating a proxy if needed.
*/
public static com.binder.service.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.binder.service.IMyAidl))) {
return ((com.binder.service.IMyAidl) iin);
}
return new com.binder.service.IMyAidl.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_addPerson: {
data.enforceInterface(descriptor);
com.binder.service.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.binder.service.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getPersonList: {
data.enforceInterface(descriptor);
java.util.List<com.binder.service.Person> _result = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.binder.service.IMyAidl {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public void addPerson(com.binder.service.Person person) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((person != null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addPerson(person);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.util.List<com.binder.service.Person> getPersonList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.binder.service.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getPersonList();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.binder.service.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
public static com.binder.service.IMyAidl sDefaultImpl;
}
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.binder.service.IMyAidl impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.binder.service.IMyAidl getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public void addPerson(com.binder.service.Person person) throws android.os.RemoteException;
public java.util.List<com.binder.service.Person> getPersonList() throws android.os.RemoteException;
}
生成的IMyAidl.java文件内容的整体结构:
主要就三个内容:AIDL接口定义的addPerson方法、getPersonList方法、以及一个Stub类(Stub类里面有一个Proxy类)。
IMyAidl.java实现了IInterface,aidl接口都要实现IInterface:
/**
* Base class for Binder interfaces. When defining a new interface,
* you must derive it from IInterface.
*/
public interface IInterface
{
/**
* Retrieve the Binder object associated with this interface.
* You must use this instead of a plain cast, so that proxy objects
* can return the correct result.
*/
public IBinder asBinder();
}
客户端调用服务端方法的时序图:
客户端的使用很简单,首先获取aidl对象,然后通过aidl对象调用定义的接口方法。
1.首先获取aidl对象:
//获取aidl对象的方法
iMyAidl = IMyAidl.Stub.asInterface(service);
这句代码具体是在ServiceConnection#onServiceConnected()方法中:
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected(): success, name=" + name);
iMyAidl = IMyAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "onServiceDisconnected(): name=" + name);
iMyAidl = null;
}
};
IMyAidl.Stub.asInterface()方法返回的其实是IMyAidl.Stub.Proxy对象(客户端与服务端不在同一个进程的情况):
/**
* Cast an IBinder object into an com.binder.service.IMyAidl interface,
* generating a proxy if needed.
*/
public static com.binder.service.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.binder.service.IMyAidl))) {
return ((com.binder.service.IMyAidl) iin);
}
return new com.binder.service.IMyAidl.Stub.Proxy(obj);
}
2.然后通过aidl对象调用定义的接口方法:
iMyAidl.addPerson(new Person("流川枫", 3));
Stub.asInterface()方法分析
//IMyAidl.java
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.binder.service.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.binder.service.IMyAidl";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.binder.service.IMyAidl interface,
* generating a proxy if needed.
*/
public static com.binder.service.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.binder.service.IMyAidl))) {
return ((com.binder.service.IMyAidl) iin);
}
return new com.binder.service.IMyAidl.Stub.Proxy(obj);
}
...
}
看下queryLocalInterface()方法
//Binder.java
/**
* Use information supplied to attachInterface() to return the
* associated IInterface if it matches the requested
* descriptor.
*/
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
if (mDescriptor != null && mDescriptor.equals(descriptor)) {
return mOwner;
}
return null;
}
该方法主要是判断Binder的mDescriptor属性是否和传递来的参数descriptor相等,那么这个mDescriptor是在哪里初始化呢,是在Stub的构造方法里进行初始化的:
//IMyAidl.java
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.binder.service.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.binder.service.IMyAidl";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
...
}
//Binder.java
/**
* Convenience method for associating a specific interface with the Binder.
* After calling, queryLocalInterface() will be implemented for you
* to return the given owner IInterface when the corresponding
* descriptor is requested.
*/
public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
DESCRIPTOR 是一个字符串常量,就是定义的AIDL接口IMyAidl的全类名。
是不是很疑惑啊,构造的时候传递的值是DESCRIPTOR ,queryLocalInterface(DESCRIPTOR)传递的值也是DESCRIPTOR,那肯定相等啊!其实不一定,因为这个Stub不一定是在本地进程创建的,也就是在调用IMyAidl.Stub.asInterface(service)
时本地进程很可能并没有Stub对象,而且android.os.IBinder obj
的obj对象也不一定是Stub对象,有可能只是实现了IBinder接口的对象而已,因此 mDescriptor 的值很可能是null。(熟悉Binder机制的同学肯定知道Stub对象是在服务端创建的,而服务端和客户端不一定运行在同一个进程,很可能是运行在另外的一个服务端进程,Stub对象通过Binder机制传递到客户端时是转为一个BinderProxy对象)。
其实调式下就可看看obj.queryLocalInterface(DESCRIPTOR)
返回的结果是什么(调式时客户端和服务端是在不同进程):
当客户端和服务端不在同一进程时, public void onServiceConnected(ComponentName name, IBinder service)
返回的service对象是一个BinderProxy对象,BinderProxy的queryLocalInterface()返回是null,因此asInterface()方法最终会执行return new com.binder.service.IMyAidl.Stub.Proxy(obj);
这句代码,返回一个Stub.Proxy对象。
Stub对象是在服务端创建的:
private IBinder iBinder = new IMyAidl.Stub() {
@Override
public void addPerson(Person person) throws RemoteException {
persons.add(person);
}
@Override
public List<Person> getPersonList() throws RemoteException {
return persons;
}
};
客户端接收:
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected(), name=" + name + " ,service=" + service);
//onServiceConnected(), name=ComponentInfo{com.binder.service/com.binder.service.MyService} ,service=android.os.BinderProxy@305651b
iMyAidl = IMyAidl.Stub.asInterface(service);
Log.i(TAG, "iMyAidl=" + iMyAidl);
//iMyAidl=com.binder.service.IMyAidl$Stub$Proxy@e6c5045
}
...
}
因此,如果服务端和客户端是在同一个进程,客户端onServiceConnected()回调方法接收到的对象service就是Stub对象,那么android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
的obj.queryLocalInterface的结果就是Stub对象。可以进行测试:
private ServiceConnection mlocalserviceconnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected(), name=" + name + " ,service=" + service);
//onServiceConnected(), name=ComponentInfo{com.binder.client/com.binder.service.MyLocalService} ,service=com.binder.service.MyLocalService$1@e6c5045
//MyLocalService$1@e6c5045其实就是MyLocalService中创建的Stub对象(准确说是Stub的匿名子类对象)
iMyAidl = IMyAidl.Stub.asInterface(service);
Log.i(TAG, "iMyAidl=" + iMyAidl);
//iMyAidl=com.binder.service.MyLocalService$1@e6c5045
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "onServiceDisconnected(), name=" + name);
iMyAidl = null;
}
};
/**
* 测试如果Service和客户端在同一个进程的情况
* MyLocalService与客户端进程在同一个进程,这种情况下,onServiceConnected()回调方法的service参数就是MyLocalService中创建的Stub对象
*/
private void bindLocalService() {
Log.i(TAG, "bindLocalService()");
Intent intent = new Intent();
//注意pkg参数是取applicationId的值,不是取AndroidManifest.xml中package的值(applicationId的值可能与package的值不一样)
intent.setComponent(new ComponentName("com.binder.client", "com.binder.service.MyLocalService"));
bindService(intent, mlocalserviceconnection, Context.BIND_AUTO_CREATE);
}
可以看到onServiceConnected()回调方法的service参数:service=com.binder.service.MyLocalService$1@e6c5045
,MyLocalService$1@e6c5045
其实就是MyLocalService中创建的Stub对象(准确说是Stub的匿名子类对象)。
这个时候调用iMyAidl.addPerson()方法就是直接调用,与调用本地方法一模一样,不需要经过binder驱动,因为根本没有经过binder驱动,可以看下调用栈:
2020-12-26 18:13:52.770 8988-8988/com.binder.client I/MyLocalService: addPerson()
java.lang.Exception
at com.binder.service.MyLocalService$1.addPerson(MyLocalService.java:33)
at com.binder.client.MainActivity$3.onClick(MainActivity.java:80)
at android.view.View.performClick(View.java:7448)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
2020-12-26 18:13:52.770 8988-8988/com.binder.client E/MainActivity: [Person{name='流川枫', grade=3}]
以上分别对客户端和服务端在同一个进程、客户端和服务端不在同一个进程的情况进行了分析。
Binder的客户端/服务端架构模式(C/S架构)
Binder将通信的双方分为client和server,即C/S架构。
跨进程通信时,两端进程均使用IBinder接口的实例进行通信(BinderProxy和Binder),客户端进程通过Proxy的mRemote对象(mRemote是BinderProxy对象,BinderProxy实现了IBinder)的transact方法将数据经过Binder驱动发送给服务端。
transact方法定义在IBinder中:
//IBinder.java
/**
* Perform a generic operation with the object.
*
* @param code The action to perform. This should
* be a number between {@link #FIRST_CALL_TRANSACTION} and
* {@link #LAST_CALL_TRANSACTION}.
* @param data Marshalled data to send to the target. Must not be null.
* If you are not sending any data, you must create an empty Parcel
* that is given here.
* @param reply Marshalled data to be received from the target. May be
* null if you are not interested in the return value.
* @param flags Additional operation flags. Either 0 for a normal
* RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
*
* @return Returns the result from {@link Binder#onTransact}. A successful call
* generally returns true; false generally means the transaction code was not
* understood.
*/
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
throws RemoteException;
客户端进程通过上述的mRemote.transact方法将数据发出后,会通过客户端进程的native层 -> Binder驱动 -> 服务端的native层 -> 调用服务端的Stub对象(Stub继承了Binder)的onTransact()方法 -> 调用Stub实现了的aidl接口中定义的接口方法。
onTransact()方法定义在Binder中:
//Binder.java
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
...
}
BinderProxy与Binder
Binder 通信机制提供了 Binder 和 BinderProxy 作为 IBinder 接口的实现类:
//android/os/Binder.java
public class Binder implements IBinder
//android/os/BinderProxy.java
public final class BinderProxy implements IBinder
- transact方法定义在IBinder中
- onTransact()方法定义在Binder中
当跨进程通信时,Binder对象是在服务端进程的,BinderProxy对象是在客户端进程的,BinderProxy意味着是服务端进程的Binder对象在客户端进程的一个代理对象。
每一个Binder对象都有一个BinderProxy对象与之对应。
客户端进程通过BinderProxy对象的transact方法发出数据,服务端进程的Binder对象的onTransact方法接收数据。
Binder中的transact方法是什么时候调用的?
Binder对象是在服务端进程的,BinderProxy对象是在客户端进程的。
Binder和BinderProxy都实现了IBinder的transact方法,但Binder中的transact方法与BinderProxy中的transact方法是不一样的,光看代码,Binder中的transact方法代码要少的很多,我们知道当跨进程通信时,客户端进程是通过BinderProxy对象的transact方法发出数据,服务端进程的Binder对象的onTransact方法接收数据,那么Binder的transact方法是什么时候调用的?
Binder机制支持进程间的递归调用。例如两个进程A和进程B,客户端进程A执行transact方法调用服务端进程B的Binder的onTransact方法,而进程B在其Binder#onTransact方法中又调用transact方法向进程A发起调用,那么进程A在等待它发出的调用返回结果的同时,还会用Binder#onTransact方法响应进程B的transact调用。
客户端(Proxy):
Proxy(是BinderProxy的代理):
private static class Proxy implements com.binder.service.IMyAidl {
...
}
- Proxy是一个实体类,不是抽象类(Stub是一个抽象类)。
- Proxy实现了我们定义的AIDL接口,因此Proxy实现了AIDL接口中定义的方法。
- Proxy里面维护了一个BinderProxy对象,Proxy是BinderProxy的代理。
Proxy实现的AIDL接口中的方法:addPerson()、getPersonList()
private static class Proxy implements com.binder.service.IMyAidl {
private android.os.IBinder mRemote;//BinderProxy对象
...
@Override
public void addPerson(com.binder.service.Person person) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((person != null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().addPerson(person);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.util.List<com.binder.service.Person> getPersonList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.binder.service.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getPersonList();
}
_reply.readException();
_result = _reply.createTypedArrayList(com.binder.service.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
...
}
其中关键的代码:
//_data 发送到服务端的数据
//_reply 服务端返回的数据
boolean _status = mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);
code参数:传的是Stub.TRANSACTION_addPerson,代表方法的序号(序号是从1开始的)。
_data: 发送到服务端的数据
_reply: 服务端返回的数据
flags参数:0表示阻塞调用,1表示非阻塞调用。这里mRemote.transact方法的flags参数设置的是0,即发起远程过程调用(RPC)请求,同时客户端对应的线程会挂起,等待服务端返回数据后才会继续执行。
flags 参数:
flags – Additional operation flags. Either 0 for a normal RPC, or FLAG_ONEWAY for a one-way RPC.
默认情况下flags = 0,跨进程的操作是同步的,即transact方法会阻塞当前调用线程,直到远程服务端返回;当flags = FLAG_ONEWAY时,表示对transact方法调用是单向调用,不会阻塞当前调用线程,而是立即返回。
那么Android framework中哪些调用是阻塞调用,哪些调用是立即返回的呢?
1.APP调用sysytem_server的方法基本是阻塞调用,比如app调用AMS的方法基本上是阻塞调用,ActivityManagerProxy中的方法mRemote.transact()使用的是的flag为0。
2.sysytem_server调用APP的方法基本是非阻塞调用,比如AMS调用app的方法基本上是非阻塞调用,ApplicationThreadProxy中的方法mRemote.transact()使用的是flag为IBinder.FLAG_ONEWAY。
总体而言,系统进程sysytem_server是很忙的,不能阻塞等待应用,一般是应用等待系统进程。
总结:
1、参数的传递过程,首先客户端数据转换成序列化数据,然后传递给remote对象,remote.transact()方法执行后的结果_reply是序列化数据,后续会转换成方法的返回数据;binder进程里面会将序列化数据转换成类进行处理,处理的结果也会转换成序列化数据,跨进程通讯只能传递序列化数据。
2.mRemote.transact方法的flags参数是0,线程会阻塞,直到服务端返回数据后才继续执行。
3.服务端和客户端在同一个进程时,onServiceConnected()回调方法返回的是Stub对象;服务端和客户端不在同一个进程时,onServiceConnected()回调方法返回的是BinderProxy对象。
服务端(Stub):
Stub(是一个Binder对象):
public static abstract class Stub extends android.os.Binder implements com.binder.service.IMyAidl {
...
}
- Stub是一个抽象类
- Stub继承了android.os.Binder,因此Stub就是一个Binder对象,实现了在服务端的代码
- Stub实现了我们定义的AIDL接口,因此Stub要实现AIDL接口中定义的方法,因为Stub是抽象类,本身并没有实现AIDL接口中定义的方法,因此服务端代码在创建Stub对象返回给客户端时要实现AIDL接口中定义的方法。
Stub称为存根类,或者桩。Stub 跟 Proxy 是一对,俗称“代理-桩”,一般在代理设计模式或者RMI远程方法调用会用到。
Proxy 相当于是拿在手里的遥控器,而 Stub 相当于长在电视机里的遥控接收器,它们有着一一对应的接口方法,但操作的方向刚好相反。
Proxy 的接口供客户端程序调用,然后它内部会把信息包装好,以某种方式传递给 Stub,而后者通过对应的接口作用于服务端系统,从而完成了“远程调用”。
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.binder.service.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.binder.service.IMyAidl";
...
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
//根据客户端传递的code值判断调用哪个方法
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_addPerson: {//根据code值判断调用addPerson方法
//进行descriptor的校验
data.enforceInterface(descriptor);
//binder传输的是序列化数据,需要进行数据反序列化
com.binder.service.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.binder.service.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//调用定义的aidl接口方法
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getPersonList: {//根据code值判断调用getPersonList方法
data.enforceInterface(descriptor);
java.util.List<com.binder.service.Person> _result = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
...
}
关键的是onTransact()
方法,方法里会调用this.addPerson(_arg0);
方法。
看下服务端的调用栈:
2020-12-26 17:42:00.335 6652-6672/com.binder.service I/MyService: addPerson()
java.lang.Exception
at com.binder.service.MyService$1.addPerson(MyService.java:30)
at com.binder.service.IMyAidl$Stub.onTransact(IMyAidl.java:72)
at android.os.Binder.execTransactInternal(Binder.java:1154)
at android.os.Binder.execTransact(Binder.java:1123)
即数据经过Binder驱动后会到达服务端进程的Binder的native层,然后最终会调用服务端创建的Stub对象的onTransact方法,onTransact方法中会调用Stub对象的实现的aidl接口中的方法。
bindService()流程分析:
只有先经过了bindService()流程,才有前面分析的客户端调用服务端方法的过程,那么bindService()流程是怎么样的?下面来分析。
读相关源码时一遍不理解很正常,读2遍,读3遍,多读几遍多思考,第一遍时理解整体大概流程就可以了,后面再理解细节源码。
AMS的bindService方法与我们自定义的aidl接口的方法的比较
将ActivityManagerService与IMyAidl做个对比,ActivityManagerService就类似我们在MyService中创建的Stub的子类对象:
private IBinder iBinder = new IMyAidl.Stub() {
@Override
public void addPerson(Person person) throws RemoteException {
Log.i(TAG, "addPerson()", new Exception());
persons.add(person);
}
@Override
public List<Person> getPersonList() throws RemoteException {
return persons;
}
};
只不过我们创建的是Stub的匿名子类对象,而ActivityManagerService是继承ActivityManagerNative(ActivityManagerNative就类似IMyAidl.Stub类)。
即new IMyAidl.Stub()创建的对象是继承IMyAidl.Stub,ActivityManagerService对象是继承ActivityManagerNative。
ActivityManagerService中的bindService方法(定义在IActivityManager中)就类似IMyAidl中的addPerson方法。
ActivityManagerService是一个Binder对象,那他是在哪个系统服务中返回的?
SystemServer进程启动了一系列的系统服务,ActivityManagerService是其中一个重要的服务。
整体原理图
不同进程中的对象是不能直接调用方法的,只有当对象在同一个进程中才可以。
图中A进程的Test对象可以直接调用B进程的Test对象的方法吗?是不能的,只有通过Binder驱动进行跨进程通信才可以实现。这道理看起来很简单,但是理解它非常重要,只有理解了这一点,那么就知道上述例子中,如果启动的Service(即例子中的MyLocalService)是与客户端进程在同一个进程,那么iMyAidl = IMyAidl.Stub.asInterface(service);
得到的iMyAidl对象其实就是MyLocalService中创建的Stub对象;如果启动的Service(即例子中的MyService)与客户端进程不在同一个进程,那么iMyAidl = IMyAidl.Stub.asInterface(service);
得到的iMyAidl对象其实是Stub.Proxy对象,Stub.Proxy对象里面维护了一个BinderProxy对象,BinderProxy意味着是远端服务端进程的Binder对象在客户端进程的一个代理对象。
总结
Binder通信机制在系统组件间以及很多大型项目中都经常使用,比如:
- 插件化:支付宝纳入海量应用
- 大型登入架构的实现
理解了Binder通信机制的原理后再看相关源码时才能容易读懂。
Binder深入
关于Binder驱动
Binder在Android里被设计成了一个驱动,安装在/dev/binder,这也是Android和linux的重要区别之一,Binder是Android特有的IPC机制,linux中并没有。
参考:
https://developer.android.google.cn/guide/components/aidl.html?hl=zh-cn
整体流程:
Android Binder(也许是最容易理解的)
关于Binder机制的简单认识
简单理解 Binder 调用流程
Android中的Binder机制二(匿名Binder)
AIDL:
Android:学习AIDL,这一篇文章就够了
Binder的发展历史:
为什么 Android 要采用 Binder 作为 IPC 机制?
【Android系统】binder 到底是什么?openbinder 又是什么?它是什么机制?
关于Binder驱动
Android Binder实现浅析-Binder驱动