跨进程通信机制

跨进程通信机制


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架构中的组件主要包括 ClientServerServiceManager 以及 Binder 驱动四种,各角色作用如下:

角色作用
ClientAndroid客户端,使用服务的进程
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 任务队列),然后向 ServiceManagersvcinfo 列表中缓存注册的服务;
  • Server端注册完成后,Client端就可以与其进行通讯了。在通讯之前Client端需要先获取服务,即拿到服务的代理,也可以理解为引用。获取Server端的方式就是通过 ServiceManagersvcinfo 列表中查询所需服务并返回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中的八种基本数据类型:booleanbytecharshortintfloatdoublelong
      • 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.javaPerson.java分别与Pet.aidlPerson.aidl对应,对应的java文件与aidl文件的包名需一致(自动编译)。

Pet.aidlPerson.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类并实现这些方法。
  • 向客户端公开接口:
    • 实现Service 并重写 onBind(),从而返回Stub 类的实现。

2.3.1. 创建 .aidl文件

创建Pet.aidlPerson.aidl文件,代码如下:

package com.example.aidlparcelableservertest;

parcelable Pet;
package com.example.aidlparcelableservertest;

parcelable Person;

定义接口服务时注意:

  • 方法可带零个或多个参数,返回值或空值;
  • 所有非默认支持的数据类型的参数均需要指示数据流向的方向标记:inoutinout。默认支持的数据类型的参数只能为in;
  • 可以在 ADL 接口中定义 String 常量和 int 字符串常量,如:const int VERSION = 1;

2.3.2. 实现 Parcelable 接口

可以通过 IPC 接口,将某个类从一个进程发送至另一个进程。但必须确保 IPC 通道的另一端可使用该类的代码,并且该类必须支持 Parcelable 接口。

实现 Parcelable 接口相当于 Android 提供的一种自定义序列化机制,不仅要求实现Parcelable接口中定义的方法,而且要求在实现类中定义一个名为CREATOR、类型为Parcelable.Creator的静态常量。

  • 创建与Pet.aidlPerson.aidl对应的文件Pet.javaPerson.java,对应的java文件与aidl文件的包名一致。Pet.aidlPerson.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 方法的访问权限)的副本,即将PetPerson类的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>中,可以通过软件包名称packageintent签名或提供程序授权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>中不能使用 pathpathPrefixpathPattern 以及 port 属性,否则会被当作通用通配符*
  • <data>中不能使用<mimeGroup>属性;
  • 在单个<intent><data>中,mimeTypescheme以及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);
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值