跨进程通信机制
1. Binder
1.1. Binder定义
Binder是Android系统中进程间通讯(IPC)的一种方式,也是Android系统中最重要的特性之一。Android中的四大组件Activity,Service,Broadcast,ContentProvider,不同的App等都运行在不同的进程中,它是这些进程间通讯的桥梁。
1.2. Binder架构
Binder跨进程通信机制模型基于Client - Server
模式,其在 framework 层进行了封装,通过 JNI 技术调用 Native(C/C++)层的 Binder 架构。而在Native层,Binder 则以 ioctl 的方式与 Binder 驱动进行通讯,其架构图如下:
Binder架构中的组件主要包括 Client、 Server、 ServiceManager 以及 Binder 驱动四种,各角色作用如下:
角色 | 作用 |
---|---|
Client | Android客户端,使用服务的进程 |
Server | 服务器端,提供服务的进程 |
ServiceManager | 管理Service的注册与查询,将字符形式的Binder名字转化成Client中对该Binder的代理 |
Binder驱动 | 负责进程之间Binder通信的建立,Binder在进程之间的传递,Binder引用计数管理,数据包在进程之间的传递和交互等一系列底层支持 |
1.3. Binder机制
Binder机制如下图所示:
- 首先需要注册
Server
端,只有注册了Server
端,Client
端才有通讯的目标,Server
端通过ServiceManager
注册服务,注册的过程就是向 Binder 驱动的全局链表binder_procs
中插入Server
端的信息(binder_proc
是结构体,每个binder_proc
结构体中都有todo
任务队列),然后向ServiceManager
的svcinfo
列表中缓存注册的服务; - 在
Server
端注册完成后,Client
端就可以与其进行通讯了。在通讯之前Client
端需要先获取服务,即拿到服务的代理,也可以理解为引用。获取Server
端的方式就是通过ServiceManager
到svcinfo
列表中查询所需服务并返回Server
端的代理,svcinfo
列表就是所有已注册服务的通讯录,保存了所有已注册服务的信息; - 在有了
Server
端的代理之后即可向Server
端发送请求。Client
端通过BinderProxy
将请求参数发送给ServiceManager
,通过共享内存的方式使用内核方法copy_from_user()
将请求参数先拷贝到内核空间,此时Client
端进入等待状态,然后 Binder 驱动向Server
端的todo
队列里面插入一条事务,执行完成之后通过copy_to_user()
将内核的执行结果拷贝到用户空间(这里只执行拷贝命令,并没有拷贝数据),唤醒等待的客户端并把结果返回,即完成了一次通讯。
1.4. Binder驱动
Linux 内核的运行空间与用户程序的运行空间是相互隔离的,即使用户程序崩溃了,内核也不受影响。内核空间与用户空间的交互过程如下图所示:
内核空间可以执行任意命令,调用系统的一切资源,而用户空间只能执行简单的运算,不能直接调用系统资源。虽然从逻辑上抽离出内核空间和用户空间,但是不可避免的的是,总有那么一些用户空间需要访问内核的资源。而用户空间访问内核空间的唯一方式就是系统调用(System
call
),通过这个统一的入口接口,所有的资源访问都是在内核的控制下执行,以免导致用户程序对系统资源的越权访问,从而保障了系统的安全和稳定。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,就称进程处于内核运行态(或简称为内核态),此时处理器处于特权级最高的(0级)内核代码中执行,处理器在特权等级高的时候才能执行特权CPU指令。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态),即此时处理器在特权级最低的(3级)用户代码中运行。
通过系统调用,用户空间可以访问内核空间,而当一个用户空间想与另外一个用户空间进行通信时就需要让操作系统内核添加支持,如Socket、管道等都是内核支持的。但 Binder 并不是 Linux 内核的一部分,而是 Linux 的动态可加载内核模块(Loadable Kernel Module,LKM)。Binder是具有独立功能的程序,可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行。因此,通过添加一个运行在内核空间的内核模块,作为用户进程之间通信的桥梁,从而实现进程间通信。在 Android 系统中,这个运行在内核空间的,负责各个用户进程通过 Binder 通信的内核模块叫做 Binder 驱动;
在上图中,用户空间中 binder_open()
, binder_mmap()
, binder_ioctl()
方法通过 System
call
来调用内核空间 Binder 驱动中相对应的方法。内核空间与用户空间的共享内存通过 copy_from_user()
和 copy_to_user()
内核方法来完成用户空间与内核空间的数据传输。此外,Binder 驱动中有一个全局的 binder_procs
链表,用来保存Server
端的进程信息。
1.5. Binder进程与线程
对于底层Binder驱动而言,通过 binder_procs
链表记录所有创建的 binder_proc
结构体,Binder 驱动中的每一个 binder_proc
结构体都与用户空间中的一个用 Binder 通信的进程相对应,且每个进程有且只有一个 ProcessState
对象,通过单例模式实现。在每个进程中可以有多个线程,每个线程对应一个 IPCThreadState
对象,IPCThreadState
对象也是通过单例模式实现,在 Binder 驱动层也有与之相对应的结构,即Binder_thread
结构体。在 binder_proc
结构体中通过成员变量 rb_root threads
来记录当前进程内所有的 binder_thread
。
每个 Server 进程在启动时创建一个 Binder 线程池,并向其中注册一个 Binder 线程,之后 Server 进程也可以向 binder 线程池注册新的线程。当 Binder 驱动在探测到没有空闲 binder 线程时,也可以主动向 Server 进程池注册新的的 binder 线程。对于一个 Server 进程有一个最大 Binder 线程数限制,默认为16个 Binder 线程。对于所有 Client
端进程的 Binder 请求都是交由 Server
端进程的 Binder 线程来处理的。
1.6. ServiceManager启动
ServiceManager提供注册服务与查询服务的功能,其启动如下图所示:
ServiceManager 分为 framework 层和 native 层,framework 层只是对 native 层进行了封装方便调用,图上展示的是 native 层的 ServiceManager 的启动过程。
ServiceManager 的启动是系统在开机时,init
进程解析 init.rc
文件并调用 service_manager.c
中的 main()
方法启动的。 native 层的 binder.c
封装了一些与 Binder 驱动交互的方法。
ServiceManager 的启动分为三步:首先打开驱动创建全局链表 binder_procs
;然后将自己当前进程信息保存到 binder_procs
链表;最后开启 loop
不断的处理共享内存中的数据,并处理 BR_xxx
命令(ioctl
的命令)。
1.7. ServiceManager 注册服务
Service组件运行在Server进程中,首先要将Service注册到Service Manager中,再启动一个Binder线程池来等待和处理Client端的通信请求。
注册过程(addService)的核心工作是在服务所在进程创建binder_node
,在ServiceManager进程创建binder_ref
。
以Media服务为例,注册的过程涉及到MediaPlayerService(作为Client进程)和Service Manager(作为Service进程),通信流程图如下所示:
- 注册
MediaPlayerService
服务端,通过 ServiceManager 的addService()
方法来注册服务; - 首先 ServiceManager 向 Binder 驱动发送
BC_TRANSACTION
命令,并携带ADD_SERVICE_TRANSACTION
命令,同时注册服务的线程进入等待状态waitForResponse()
。 Binder 驱动收到请求命令向 ServiceManager 的todo
队列里面添加一条注册服务的事务。事务的任务就是创建服务端进程binder_node
信息并插入到binder_procs
链表中; - 事务处理完之后发送
BR_TRANSACTION
命令,ServiceManager 收到命令后向svcinfo
列表中添加已经注册的服务。最后发送BR_REPLY
命令唤醒等待的线程,通知注册成功。
1.8. ServiceManager 获取服务
请求服务过程,就是向serviceManager进程查询指定服务,当执行binder_transaction()
时,会区分请求服务所属进程情况:
- 当请求服务的进程与服务属于不同进程,则为请求服务所在进程创建
binder_ref
对象,指向服务进程中的binder_node
,即返回请求服务的一个代理对象; - 当请求服务的进程与服务属于同一进程,则不再创建新对象,而是返回的对象的真实子类;
ServiceManager 获取服务的流程如下图所示:
- 获取服务的过程与注册类似,相反的过程。通过 ServiceManager 的
getService()
方法来注册服务; - 首先 ServiceManager 向 Binder 驱动发送
BC_TRANSACTION
命令,并携带CHECK_SERVICE_TRANSACTION
命令,同时获取服务的线程进入等待状态waitForResponse()
; - Binder 驱动收到请求命令向 ServiceManager 的发送
BC_TRANSACTION
查询已注册的服务,若查询到所需服务则直接响应BR_REPLY
并唤醒等待的线程,否则将与binder_procs
链表中的服务进行一次通讯再响应。
1.9. 进行一次完整通讯
进行一次完整通讯的流程如下图所示:
- 服务注册完成;
- 首先通过 ServiceManager 获取到服务端的
BinderProxy
代理对象,通过调用BinderProxy
将参数,方法标识传给 ServiceManager,同时客户端线程进入等待状态; - ServiceManager 将用户空间的参数等请求数据复制到内核空间,并向服务端插入一条执行方法的事务。事务执行完通知 ServiceManager 将执行结果从内核空间复制到用户空间,并唤醒等待的线程,响应结果,通讯结束。
2. AIDL
2.1. 概述
AIDL,全称是 “Android Interface Definition Language”,也就是 “Android接口定义语言”。设计这门语言的目的是为了实现进程间通信,尤其是在涉及多进程并发情况下的进程间通信。
在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供操作的对象。而通过 AIDL 即可定义客户端与服务端均认可的编程接口,以便二者之间实现通信。
2.2. AID语法
AIDL 的语法基本上和 Java 是一样的,只是在一些细微处有些许差别,差别之处主要如下所示:
-
文件类型:用 AIDL 书写的文件的后缀是**
.aidl
**,而不是.java
。 -
数据类型:AIDL 默认支持一些数据类型,在使用这些数据类型的时候是不需要导包的。但是除了这些类型之外的数据类型,在使用之前必须导包,即使目标文件与当前正在编写的 .aidl 文件在同一个包下也是需要导包的(在 Java 中,这种情况是不需要导包的)。
- 默认支持的数据类型包括:
- Java中的八种基本数据类型:boolean,byte,char,short,int,float,double,long;
- String 类型;
- CharSequence 类型;
- List 类型:List中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable,List可以使用泛型;
- Map 类型:Map中的所有元素必须是AIDL支持的类型之一,或者是一个其他AIDL生成的接口,或者是定义的parcelable,Map是不支持泛型的。
- 默认支持的数据类型包括:
-
定向tag:AIDL 中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端; out 表示数据只能由服务端流向客户端;而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个客户端中传入方法的对象的完整数据,但是客户端该对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
注:Java 中的基本类型和 String ,CharSequence 的定向 tag 默认且只能是 in。
-
两类AIDL文件:第一类是用来定义 parcelable 对象,以供其他 AIDL 文件使用 AIDL 中非默认支持的数据类型的。第二类是用来定义方法接口,以供系统使用来完成跨进程通信的。两类文件都是在“定义”,而不涉及具体的实现,这就是为什么它叫做“Android接口定义语言”。
注:所有的非默认支持数据类型必须通过第一类AIDL文件定义 parcelable 对象才能被使用。
举例如下:
目录结构如图所示:
其中,Pet.java
、Person.java
分别与Pet.aidl
、Person.aidl
对应,对应的java文件与aidl文件的包名需一致(自动编译)。
Pet.aidl
、Person.aidl
以及Ipet.aidl
文件代码如下:
package com.example.aidlparcelableservertest;
// Pet.aidl
// 第一类AIDL文件
// 这个文件的作用是引入一个序列化对象 Pet 供其他的AIDL文件使用
// Pet.aidl 与 Pet.java的包名应该是一样的
parcelable Pet;
package com.example.aidlparcelableservertest;
// Person.aidl
// 第一类AIDL文件
// 这个文件的作用是引入一个序列化对象 Person 供其他的AIDL文件使用
// Person.aidl 与 Person.java的包名应该时一样的
parcelable Person;
package com.example.aidlparcelableservertest;
// Ipet.aidl
// 第二类AIDL文件
// Pet和Person不是默认支持的类型
// 即使Ipet.aidl与Pet.aidl、Person.aidl在同一包内,也需要导入
import com.example.aidlparcelableservertest.Pet;
import com.example.aidlparcelableservertest.Person;
interface IPet {
// 无论是什么类型,所有的返回值前都不需要加任何东西
// 定义一个Person对象作为传入参数
List<Pet> getPets(in Person owner);
// 传参时除Java基本类型
void addPetIn(in Pet pet);
void addPetOut(out Pet pet);
void addPetInout(inout Pet pet);
}
2.3. 如使用AIDL实现跨进程通信
在进行跨进程通信时,AIDL 中定义的方法里包含非默认支持的数据类型与否,要进行的操作是不一样的。如果不包含,则只需要编写一个.aidl
文件(第二类),如果包含,那么我们通常需要写 n+1 个 .aidl
文件( n 为非默认支持的数据类型的种类数)。所以接下来以 AIDL 文件中包含非默认支持的数据类型为例进行介绍。
在构建每个包含.aidl
文件的应用时,Android SDK 工具会生成基于该.aidl
文件的IBinder
接口,并将其保存到项目的 app/build/generated/aidl_source_output_dir
目录中。服务必须视情况实现IBinder
接口。然后,客户端应用便可绑定到该服务,并调用IBinder
中的方法来执行 IPC。
使用 AIDL 创建绑定服务的步骤如下:
- 创建
.aidl
文件:- 此文件定义带有方法签名的编程接口。
- 实现接口:
- Android SDK 工具基于
.aidl
文件,使用 Java 编程语言生成接口。此接口拥有一个名为Stub
的内部抽象类,用于扩展Binder
类并实现 AIDL 接口中的方法,必须扩展该Stub
类并实现这些方法。
- Android SDK 工具基于
- 向客户端公开接口:
- 实现
Service
并重写onBind()
,从而返回Stub
类的实现。
- 实现
2.3.1. 创建 .aidl
文件
创建Pet.aidl
、Person.aidl
文件,代码如下:
package com.example.aidlparcelableservertest;
parcelable Pet;
package com.example.aidlparcelableservertest;
parcelable Person;
定义接口服务时注意:
- 方法可带零个或多个参数,返回值或空值;
- 所有非默认支持的数据类型的参数均需要指示数据流向的方向标记:
in
、out
或inout
。默认支持的数据类型的参数只能为in; - 可以在 ADL 接口中定义 String 常量和 int 字符串常量,如:
const int VERSION = 1;
2.3.2. 实现 Parcelable 接口
可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。但必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持 Parcelable
接口。
实现 Parcelable 接口相当于 Android 提供的一种自定义序列化机制,不仅要求实现Parcelable
接口中定义的方法,而且要求在实现类中定义一个名为CREATOR
、类型为Parcelable.Creator
的静态常量。
- 创建与
Pet.aidl
、Person.aidl
对应的文件Pet.java
、Person.java
,对应的java文件与aidl文件的包名一致。Pet.aidl
、Person.aidl
中的内容分别如下所示:
package com.example.aidlparcelableservertest;
import android.os.Parcel;
import android.os.Parcelable;
public class Pet implements Parcelable {
private String name;
private double weight;
public Pet() {
}
public Pet(String name, double weight) {
super();
this.name = name;
this.weight = weight;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
// 实现Parcelable接口必须实现的方法
@Override
public int describeContents() {
return 0;
}
// 实现Parcelable接口必须实现的方法
// 将Pet对象的数据写入到parcel中
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeString(name);
parcel.writeDouble(weight);
}
// 添加一个静态成员CREATOR,该对象实现了Parcelable.Creator接口
// 该静态常量的值负责从parcel数据包中回复Pet对象
public static final Creator<Pet> CREATOR = new Creator<Pet>() {
@Override
public Pet createFromParcel(Parcel parcel) {
// 从Parcel中读取数据,返回Pet对象
return new Pet(parcel.readString(), parcel.readDouble());
}
@Override
public Pet[] newArray(int i) {
return new Pet[i];
}
};
@Override
public String toString() {
return "Pet{" + "name='" + name + ", weight=" + weight + '}';
}
}
package com.example.aidlparcelableservertest;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Objects;
public class Person implements Parcelable {
private int id;
private String name;
private String pass;
public Person() {
}
public Person(Integer id, String name, String pass) {
super();
this.id = id;
this.name = name;
this.pass = pass;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name) && Objects.equals(pass, person.pass);
}
@Override
public int hashCode() {
return Objects.hash(id, name, pass);
}
// 实现Parcelable接口必须实现describeContents
@Override
public int describeContents() {
return 0;
}
// 实现Parcelable接口必须实现describeContents
// 将Person对象的数据写入到parcel中
@Override
public void writeToParcel(Parcel parcel, int i) {
// 把该对象所包含的数据写到Parcel中
parcel.writeInt(id);
parcel.writeString(name);
parcel.writeString(pass);
}
// 添加一个静态成员CREATOR,该对象实现了Parcelable.Creator接口
// 该静态常量的值负责从parcel数据包中回复Person对象
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel parcel) {
// 从Parcel中读取数据,返回Person对象
return new Person(parcel.readInt(), parcel.readString(), parcel.readString());
}
@Override
public Person[] newArray(int i) {
return new Person[i];
}
};
}
注:若AIDL文件中涉及到的所有数据类型均为默认支持的数据类型,则无此步骤。因为默认支持的数据类型都是可序列化的。
- 创建通信接口的
Ipet.aidl
文件,代码如下:
package com.example.aidlparcelableservertest;
import com.example.aidlparcelableservertest.Pet;
import com.example.aidlparcelableservertest.Person;
interface IPet {
List<Pet> getPets(in Person owner);
}
在定义完该通信接口的 AIDL 文件之后,在项目的 app/build/generated/aidl_source_output_dir
目录中会生成基于该.aidl
文件的IBinder
接口(未生成则点击Make Project)。接口文件分析见后文。
2.3.3. 向客户端公开接口
- 实现
ParcelableService
类并重写onBind()
,从而返回Stub
类的实现,代码如下:
package com.example.aidlparcelableservertest;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ParcelableService extends Service {
private static final String TAG = "ParcelableService";
private PetBinder petBinder;
private static Map<Person, List<Pet>> pets = new HashMap<>();
static {
// 初始化pets Map集合
List<Pet> list1 = new ArrayList<>();
list1.add(new Pet("旺财", 4.3));
list1.add(new Pet("来福", 5.8));
pets.put(new Person(1, "sun", "1234"), list1);
List<Pet> list2 = new ArrayList<>();
list2.add(new Pet("kitty", 6.6));
list2.add(new Pet("bobby", 8.8));
pets.put(new Person(2, "moon", "4321"), list2);
}
// 继承Stub,也就是实现了IPet接口,并实现了IBinder接口
class PetBinder extends IPet.Stub {
// getPets()方法的实现
@Override
public List<Pet> getPets(Person owner) throws RemoteException {
return pets.get(owner);
}
}
@Override
public void onCreate() {
super.onCreate();
petBinder = new PetBinder();
}
@Override
public IBinder onBind(Intent intent) {
/**
* 返回petBinder对象
* 在绑定本地Service的情况下,该petBinder对象会直接传给客户端的ServiceConnection对象的onServiceConnected方法的第二个参数
* 在绑定远程Service的情况下,只将petBinder对象的代理传给客户端的ServiceConnection对象的onServiceConnected方法的第二个参数
*/
return petBinder;
}
}
- 创建客户端进程
MainActivity.java
,代码如下。客户端必须拥有接口类的访问权限,因此如果客户端和服务在不同应用内,则客户端应用的src/
目录内必须包含.aidl
文件(该文件会生成android.os.Binder
接口,进而为客户端提供 AIDL 方法的访问权限)的副本,即将Pet
与Person
类的java文件和AIDL文件以及IPet.aidl
复制到客户端中。注意,复制时需要保持包名不变。
package com.example.aidlparcelableclienttest;
import androidx.appcompat.app.AppCompatActivity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
// 引入IPet.aidl及Pet.java、Person.java
import com.example.aidlparcelableservertest.IPet;
import com.example.aidlparcelableservertest.Pet;
import com.example.aidlparcelableservertest.Person;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private IPet petService;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
// 获取远程Service的onBind方法所返回的对象的代理
petService = IPet.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
petService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EditText personView = findViewById(R.id.person);
ListView showView = findViewById(R.id.show);
Button getBtn = findViewById(R.id.get);
Intent intent = new Intent();
intent.setAction("PARCELABLE_SERVICE");
intent.setPackage("com.example.aidlparcelableservertest");
bindService(intent, conn, Service.BIND_AUTO_CREATE);
getBtn.setOnClickListener(view -> {
String personName = personView.getText().toString();
try {
// 调用远程Service方法
List<Pet> pets = petService.getPets(new Person(1, personName, personName));
// 将程序返回的list包装成ArrayAdapter
ArrayAdapter<Pet> adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, pets);
showView.setAdapter(adapter);
} catch (RemoteException e) {
e.printStackTrace();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
this.unbindService(conn);
}
}
activity_main.xml
:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<Button
android:id="@+id/get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/get" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/person" />
<EditText
android:id="@+id/person"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/person"/>
<ListView
android:id="@+id/show"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#00000000"
android:textColor="#ffffff" />
</LinearLayout>
注:当应用的版本是Android 11(API 级别 30)或更高版本时,需要在AndroidManifest.xml文件中添加<queries>
,该标签的具体应用可见<queries>标签解析章节,如下所示:
<queries>
<package android:name="com.example.aidlparcelableservertest"/>
</queries>
或:
<queries>
<intent>
<action android:name="PARCELABLE_SERVICE"/>
</intent>
</queries>
- 运行程序:
2.4. 接口文件分析
接口中内容如下:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.example.aidlparcelableservertest;
public interface IPet extends android.os.IInterface {
/**
* Default implementation for IPet.
*/
public static class Default implements com.example.aidlparcelableservertest.IPet {
// 定义一个Person对象作为传入参数
@Override
public java.util.List<com.example.aidlparcelableservertest.Pet> getPets(com.example.aidlparcelableservertest.Person owner) 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.example.aidlparcelableservertest.IPet {
// binder唯一标识
// 不同的进程之间,通过序列化传递DESCRIPTOR来找到对应的Binder
// 相同进程,也需要DESCRIPTOR才能找到对应的Binder
private static final java.lang.String DESCRIPTOR = "com.example.aidlparcelableservertest.IPet";
/**
* Construct the stub at attach it to the interface.
*/
// 将interface提供出去,这样当同一进程其他位置执行IBinder.queryLocalInterface的时候就可以获取到这个Binder
public Stub() {
// 初始化时调用attachInterface(),相同进程调用queryLocalInterface()时才能找到该Binder
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.aidlparcelableservertest.IPet interface,
* generating a proxy if needed.
*/
// 接收服务端的IBinder(通常是传递给客户端onServiceConnected() 回调方法的参数),并返回Stub接口的实例。
public static com.example.aidlparcelableservertest.IPet asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
// 调用attachInterface()方法,返回该Binder
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
// server和client同一进程,直接通过queryLocalInterface(DESCRIPTOR)找到Binder返回Stub对象
if (((iin != null) && (iin instanceof com.example.aidlparcelableservertest.IPet))) {
return ((com.example.aidlparcelableservertest.IPet) iin);
}
// server和client不同进程,返回一个封装后的Proxy对象
return new com.example.aidlparcelableservertest.IPet.Stub.Proxy(obj);
}
@Overrides
public android.os.IBinder asBinder() {
// 返回stub的Binder对象
return this;
}
// 服务端
// 运行在服务器端中 Binder 线程池中,客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法来处理
// 方法调用由 onTransact() 代码分派,该代码通常基于接口中的方法索引。
@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_getPets: {
// 需要传递DESCRIPTOR,到另一个进程可以找到对应的Binder
data.enforceInterface(descriptor);
com.example.aidlparcelableservertest.Person _arg0;
if ((0 != data.readInt())) {
// 读取CREATOR中参数
_arg0 = com.example.aidlparcelableservertest.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
// 运行getPets()方法获取结果
java.util.List<com.example.aidlparcelableservertest.Pet> _result = this.getPets(_arg0);
reply.writeNoException();
// 写入返回值
reply.writeTypedList(_result);
// 在执行完 return true 之后系统将会把 reply 流传回客户端,只是过程被隐藏了
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
// 客户端
private static class Proxy implements com.example.aidlparcelableservertest.IPet {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
// Proxy在初始化时引用server的IBinder
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
// 返回当前Proxy的Binder
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
// 定义一个Person对象作为传入参数
@Override
public java.util.List<com.example.aidlparcelableservertest.Pet> getPets(com.example.aidlparcelableservertest.Person owner) throws android.os.RemoteException {
// _data存储流向服务端的数据流
android.os.Parcel _data = android.os.Parcel.obtain();
// _reply存储流回客户端的数据流
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.aidlparcelableservertest.Pet> _result;
try {
// 需要传递DESCRIPTOR,到另一个进程可以找到对应的Binder
_data.writeInterfaceToken(DESCRIPTOR);
if ((owner != null)) {
_data.writeInt(1);
owner.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
// 通过调用mRemote.transact()来触发远端Stub的onTransact()
// 0:数据双向流通,1:数据从服务端流向客户端
boolean _status = mRemote.transact(Stub.TRANSACTION_getPets, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getPets(owner);
}
_reply.readException();
// 从_reply中取出服务端执行方法的结果
_result = _reply.createTypedArrayList(com.example.aidlparcelableservertest.Pet.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
// 将结果返回
return _result;
}
public static com.example.aidlparcelableservertest.IPet sDefaultImpl;
}
// getPets的调用id 调用IPC方法的唯一id
// Stub.onTransact()中会通过TRANSACTION_getPets来对应执行getPets()方法的逻辑。
// Proxy调用transact的时候,也是通过传递TRANSACTION_getPets,来标识自己想要执行的逻辑。
static final int TRANSACTION_getPets = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
public static boolean setDefaultImpl(com.example.aidlparcelableservertest.IPet 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.example.aidlparcelableservertest.IPet getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
// 定义一个Person对象作为传入参数
public java.util.List<com.example.aidlparcelableservertest.Pet> getPets(com.example.aidlparcelableservertest.Person owner) throws android.os.RemoteException;
}
DESCRIPTOR
:Binder 中唯一的标识,自动生成时用当前 Binder 类名表示;TRANSACTION_getPets
:声明的整型的 id 用于标识在 transact 过程中客户端中请求的到底是自身还是代理的getPets()
方法;asInterface(android.os.IBinder obj) {...}
:将服务端的 Binder 对象按是否同一进程转换成客户端所需 AIDL 接口类型的对象, 客户端和服务器在同一进程中,返回的是服务端 Stub 本身,否则就返回系统封装后的Stub.proxy
对象;onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) {...}
:运行在服务器端中 Binder 线程池中,客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法来处理:通过 code 确定客户端的方法,再从 data 中取得参数 (如果存在参数的话),然后执行在服务端的目标方法,执行完成之后,向reply
中写入返回值 (如果客户端中需要返回值的话);Proxy.getPets(com.example.aidlparcelableservertest.Person owner){...}
:在客户端中运行,当客户端远程调用此方法时,内部实现方法如下:先创建所需的输入型 Parcel 对象_data
, 输出型 Parcel 对象_replay
和返回值对象_result
。先将需求参数写入data 中,接着调用transact()
方法发起远程调用 (RPC) 请求,同时当前线程会挂起,然后服务端的onTransact()
方法会被调用,当 RPC 方法结束返回后,当前线程从挂起状态变成重新运行状态,并从reply中取出 RPC 过程的返回结果,最后返回_reply
中的数据。
基本步骤如下:
- Client通过
ServiceConnection()
获取到Server的Binder,并且封装成一个Proxy; - 通过Proxy来同步调用IPC方法。同时通过Parcel将参数传给Binder,最终触发Binder的
transact()
方法; - Binder的
transact()
方法最终会触发到Server上Stub的onTransact()
方法; - Server上Stub的
onTransact()
方法中,会先从Parcel中解析中参数,然后将参数带入真正的方法中执行,然后将结果写入Parcel后传回; - Client的IPC方法中,执行Binder的
transact()
时,是阻塞等待的。一直到Server逻辑执行结束后才会继续执行; - 当Server返回结果后,Client从Parcel中取出返回值,于是实现了一次IPC调用。
bindService()->onBind()->onServiceConnected()->asInterface()->getPets()->transact()(Stub本身直接触发该方法,代理是在Proxy.getPets()方法中触发)->onTransact()->返回结果
3. **<queries>
**标签解析
当创建的应用以 Android 11(API 级别 30)或更高版本为目标平台时,在默认情况下,系统只会让部分应用可见,而隐藏其他应用,以鼓励最小权限原则并保障用户的隐私安全。但是,应用的可见与否会影响到提供其他应用相关信息的方法的返回结果,如:queryIntentActivities()
。此外,还会影响与其他应用的显式交互,例如启动另一个应用的服务。
3.1. 自动可见的应用
在 Android 11及以上版本,无需声明\<queries>
便可进行交互的应用如下:
- 我们自己的应用;
- 实现 Android 核心功能的某些系统软件包;
- 安装了我们自己应用的应用;
- 使用
startActivityForResult()
方法启动Activity
的任何应用; - 启动或者绑定到我们自己的应用中的某项服务的任何应用;
- 访问我们自己的应用中的
ContentProvider
的任何应用; - 具有
ContentProvider
的任何应用,其中我们自己的应用已被授予URI权限来访问该ContentProvider
;- 读取权限:
FLAG_GRANT_READ_URI_PERMISSION
- 写入权限:
FLAG_GRANT_WRITE_URI_PERMISSION
- 读取权限:
- 我们自己的应用作为输入法应用提供输入,接收该输入的任何应用;
此外,无论某一应用对我们自己的应用是否可见,我们都可以使用隐式或者显示的intent
来启动该应用的 activity
。
如需查看特定设备的完整软件包列表,在该设备的终端中运行adb shell dumpsys package queries
命令。在命令输出中,找到forceQueryable
部分,即包含该设备上对我们自己的应用可见的软件包列表。如下图所示:
3.2. 查询应用
当创建的应用以 Android 11(API 级别 30)或更高版本为目标平台,并且需要与非自动可见的应用进行交互,则需要在该应用的AndroidManifest.xml
清单文件中添加<queries>
。在<queries>
中,可以通过软件包名称package
、intent
签名或提供程序授权provide
来查询特定软件包并与之交互。
3.2.1. 按软件包名称查询特定软件包及与之交互
当知道要查询或与之交互的一组特定应用时,可以将其软件包名称添加到<queries>
内的一组<package>
元素中,如下所示:
<manifest package="com.example.game">
<queries>
<package android:name="com.example.store" />
<package android:name="com.example.services" />
</queries>
...
</manifest>
3.2.2. 按 intent 过滤器查询应用及与之交互
当需要查询一组具有特定用途的应用但却并不知道其具体的软件包名称时,可以在<queries>
中按intent
过滤器进行查询,使创建的应用能够查询到匹配<intent-filter>
的应用,如下所示:
<manifest package="com.example.game">
<queries>
<intent>
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/jpeg" />
</intent>
</queries>
...
</manifest>
<intent>
元素的限制:
- 至少要有一个
<action>
或者一个<data>
元素,各属性至多只有一个; <data>
中不能使用path
、pathPrefix
、pathPattern
以及port
属性,否则会被当作通用通配符*
;<data>
中不能使用<mimeGroup>
属性;- 在单个
<intent>
的<data>
中,mimeType
、scheme
以及host
最多使用一次,但可以在多个<data>
之间分配这些属性,也可以在单个<data>
中使用这些属性。
<intent>
支持通用通配符*
作为以下属性的值:
<action>
元素的name
属性;<data>
元素的mimeType
属性的子类型 (image/*
);<data>
元素的mimeType
属性的类型和子类型 (*/*
);<data>
元素的scheme
属性;<data>
元素的host
属性。
除非前面列表中另有说明,否则系统不支持混合使用文本和通配符,如 prefix*
。
3.2.3. 在给定提供程序授权的情况下查询应用及与之交互
当需要查询ContentProvider
但不知道具体的软件包名时,可以在 <provider>
元素中声明该提供程序的授权,如下所示:
<manifest package="com.example.suite.enterprise">
<queries>
<provider android:authorities="com.example.settings.files" />
</queries>
...
</manifest>
可以在单个 <queries>
元素中声明多项提供程序授权:
- 在单个
<provider>
元素中,声明以英文分号分隔的授权列表; - 在同一个
<queries>
元素中添加多个<provider>
元素 ,在每个<provider>
元素中,声明单项授权或以英文分号分隔的授权列表。
3.2.4. 查询所有应用及与之交互
在极少数情况下,我们创建的应用可能需要查询设备上的所有已安装的应用。此时,我们可以通过QUERY_ALL_PACKAGES
权限以查询其他所有已安装应用,如下所示:
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
3.2.5. parseQueries
方法解析
/**
* 根据<queries>标签中的内容进行解析
*
* @param input:与输入类型无关的,用来被转换为ParseResult输出类型的输入源参数
* @param pkg:被解析的包
* @param res:用于访问应用程序资源的类
* @param parser:返回读取的XML资源
* @return ParseResult:ParserInput的输出端
*/
private static ParseResult<ParsingPackage> parseQueries(ParseInput input, ParsingPackage pkg,
Resources res, XmlResourceParser parser) throws IOException, XmlPullParserException {
final int depth = parser.getDepth();
int type;
// 判断是否还可以读取parser中的资源
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > depth)) {
// 初始时刻 type = START_TAG
if (type != XmlPullParser.START_TAG) {
continue;
}
// 当queries中标签使用intent时
if (parser.getName().equals("intent")) {
// 对Intent中的action、category以及data信息进行解析,解析结果存放在result中
ParseResult<ParsedIntentInfo> result = ParsedIntentInfoUtils.parseIntentInfo(null,
pkg, res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/, input);
if (result.isError()) {
return input.error(result);
}
// 获取含有解析的action、category以及data等信息的ParsedIntentInfo对象intentInfo
ParsedIntentInfo intentInfo = result.getResult();
Uri data = null;
String dataType = null;
String host = null;
// 分别获取action、data scheme(为空返回0)、data types(为空返回0)、Host的长度
final int numActions = intentInfo.countActions();
final int numSchemes = intentInfo.countDataSchemes();
final int numTypes = intentInfo.countDataTypes();
final int numHosts = intentInfo.getHosts().length;
// 每个intent至少要有一个action或者一个data,各属性至多只有一个
if ((numSchemes == 0 && numTypes == 0 && numActions == 0)) {
return input.error("intent tags must contain either an action or data.");
}
if (numActions > 1) {
return input.error("intent tag may have at most one action.");
}
if (numTypes > 1) {
return input.error("intent tag may have at most one data type.");
}
if (numSchemes > 1) {
return input.error("intent tag may have at most one data scheme.");
}
if (numHosts > 1) {
return input.error("intent tag may have at most one data host.");
}
Intent intent = new Intent();
// private ArrayList<String> mCategories = null;
// 将intentInfo获取的category属性添加到intent对象中
for (int i = 0, max = intentInfo.countCategories(); i < max; i++) {
intent.addCategory(intentInfo.getCategory(i));
}
// host属性赋值
if (numHosts == 1) {
host = intentInfo.getHosts()[0];
}
// data属性赋值
if (numSchemes == 1) {
data = new Uri.Builder()
.scheme(intentInfo.getDataScheme(0))
.authority(host)
// /*
.path(IntentFilter.WILDCARD_PATH)
.build();
}
// dataType属性赋值
if (numTypes == 1) {
dataType = intentInfo.getDataType(0);
// The dataType may have had the '/' removed for the dynamic mimeType feature.
// If we detect that case, we add the * back.
if (!dataType.contains("/")) {
dataType = dataType + "/*";
}
// data设置默认值
if (data == null) {
data = new Uri.Builder()
.scheme("content")
// */*
.authority(IntentFilter.WILDCARD)
.path(IntentFilter.WILDCARD_PATH)
.build();
}
}
intent.setDataAndType(data, dataType);
if (numActions == 1) {
intent.setAction(intentInfo.getAction(0));
}
// 将intent添加到空List<Intent> queriesIntents中
pkg.addQueriesIntent(intent);
} else if (parser.getName().equals("package")) { // 当queries中标签使用package时
// 获取包名
final TypedArray sa = res.obtainAttributes(parser,
R.styleable.AndroidManifestQueriesPackage);
final String packageName = sa.getNonConfigurationString(
R.styleable.AndroidManifestQueriesPackage_name, 0);
if (TextUtils.isEmpty(packageName)) {
return input.error("Package name is missing from package tag.");
}
// 将packageName添加到空List<String> queriesPackages中
pkg.addQueriesPackage(packageName.intern());
} else if (parser.getName().equals("provider")) { // 当queries中标签使用provider时
final TypedArray sa = res.obtainAttributes(parser,
R.styleable.AndroidManifestQueriesProvider);
try {
// 获取authorities属性
final String authorities = sa.getNonConfigurationString(
R.styleable.AndroidManifestQueriesProvider_authorities, 0);
if (TextUtils.isEmpty(authorities)) {
return input.error(
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
"Authority missing from provider tag."
);
}
// 将多个authorities以";"拆分并加入到pkg中
StringTokenizer authoritiesTokenizer = new StringTokenizer(authorities, ";");
while (authoritiesTokenizer.hasMoreElements()) {
// 将authorities添加到空Set<String> queriesProviders中
pkg.addQueriesProvider(authoritiesTokenizer.nextToken());
}
} finally {
sa.recycle();
}
}
}
return input.success(pkg);
}