Android 进阶9:进程通信之 AIDL 解析

2024-06-02 07:18

本文主要是介绍Android 进阶9:进程通信之 AIDL 解析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

读完本文你将了解:

    • AIDL
    • AIDL 生成文件分析
      • Stub
      • Proxy
      • AIDL 生成的内容小结
    • AIDL 的使用回顾
      • 服务端
      • 客户端
    • 小结
    • 手动写个 Binder
      • 首先是定义跨进程接口实现 IInterface
      • 然后在其中创建这个接口与对应 Binder 的转换类 Stub
      • 最后创建代理接口在不同进程中客户端持有的是代理
    • Thanks

在 Android 进阶7:进程通信之 AIDL 的使用 中我们使用 AIDL 实现了跨进程的通信,但是不清楚 AIDL 帮我们做了什么。

AIDL 的本质是简化我们 IPC 开发,它使用的是 Binder 机制,于是在上篇文章 Android 进阶8:进程通信之 Binder 机制浅析 中我们简单了解了 Binder 机制的流程。

有了前面的铺垫,这篇文章我们就一起来看看 AIDL 究竟帮我们做了什么。

AIDL

前面我们讲了,在使用 AIDL 编写 IPC 代码时,我们只需要编写简单的 接口 aidl 文件:

这里写图片描述

Make Project 后系统就会帮我们生成 Java 文件:

这里写图片描述

AIDL 生成文件分析

AIDL 帮我们生成内容:

这里写图片描述

代码如下:

这里写图片描述

可以看到,生成的接口 IMyAidl 继承了 IInterface,Android 进阶8:进程通信之 Binder 机制浅析 中我们介绍了,IInterface 是进程间通信定义的通用接口。

同时 IMyAidl 中也包含了我们在 aidl 文件中声明的两个方法。

除此外,IMyAidl 中还包括一个抽象类 Stub,它是一个 Binder,实现了 IMyAidl 接口:

Stub

这里写图片描述

代码如下:

public static abstract class Stub extends android.os.Binder implements net.sxkeji.shixinandroiddemo2.IMyAidl {//唯一标识,一般为完整路径private static final java.lang.String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.IMyAidl";/*** 将当前接口与 Binder 绑定*/public Stub() {this.attachInterface(this, DESCRIPTOR);}/*** 将一个 IBinder 转换为 IMyAidl,如果不在一个进程就创建一个代理*/public static net.sxkeji.shixinandroiddemo2.IMyAidl asInterface(android.os.IBinder obj) {if ((obj == null)) {return null;}//拿着标识从本地查询接口android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin != null) && (iin instanceof net.sxkeji.shixinandroiddemo2.IMyAidl))) {return ((net.sxkeji.shixinandroiddemo2.IMyAidl) iin);}//查不到就返回代理return new net.sxkeji.shixinandroiddemo2.IMyAidl.Stub.Proxy(obj);}//覆盖 IInterface 的方法,获取当前接口对应的 Binder 对象@Overridepublic android.os.IBinder asBinder() {return this;}//关键的方法,处理操作,返回@Overridepublic boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {switch (code) {case INTERFACE_TRANSACTION: {    //获取当前接口的描述符reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_addPerson: {    //执行 addPerson 方法data.enforceInterface(DESCRIPTOR);net.sxkeji.shixinandroiddemo2.bean.Person _arg0;if ((0 != data.readInt())) {        //反序列化传入的数据_arg0 = net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR.createFromParcel(data);} else {_arg0 = null;}//调用 addPerson 方法,这个方法的实现是在服务端this.addPerson(_arg0);reply.writeNoException();return true;}case TRANSACTION_getPersonList: {data.enforceInterface(DESCRIPTOR);java.util.List<net.sxkeji.shixinandroiddemo2.bean.Person> _result = this.getPersonList();reply.writeNoException();reply.writeTypedList(_result);return true;}}return super.onTransact(code, data, reply, flags);}//不在一个进程时返回的代理private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {...}//用于 onTransact 方法的两个 code,分别标识要进行的操作static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

Stub 的几个关键内容介绍:

  1. 构造函数
    • 调用了 attachInterface() 方法
    • 将一个描述符、特定的 IInterface 与当前 Binder 绑定起来,这样后续调用 queryLocalInterface 就可以拿到这个
    • 需要创建一个 DESCRIPTOR,一般是类的具体路径名,用于唯一表示这个 IInterface
  2. asInterface()
    • IBinder 转换为 IMyAidl ,这用于返回给客户端
    • 不在一个进程的话,客户端持有的是一个代理
  3. onTransact()
    • Binder 关键的处理事物方法
    • 根据传入的 code,调用本地/服务端的不同方法

其中可以看到,在不同进程时返给客户端的是代理。

Proxy

这里写图片描述

代码:

private static class Proxy implements net.sxkeji.shixinandroiddemo2.IMyAidl {private android.os.IBinder mRemote;    //代理的远端 IBinderProxy(android.os.IBinder remote) {mRemote = remote;}//获取代理的 Binder@Overridepublic android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}/*** 代理嘛,处理下数据后直接调用实际 Binder 来处理*/@Overridepublic void addPerson(net.sxkeji.shixinandroiddemo2.bean.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);}//调用远端mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);_reply.readException();} finally {_reply.recycle();_data.recycle();}}@Overridepublic java.util.List<net.sxkeji.shixinandroiddemo2.bean.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<net.sxkeji.shixinandroiddemo2.bean.Person> _result;try {_data.writeInterfaceToken(DESCRIPTOR);mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);_reply.readException();_result = _reply.createTypedArrayList(net.sxkeji.shixinandroiddemo2.bean.Person.CREATOR);} finally {_reply.recycle();_data.recycle();}return _result;}
}

AIDL 生成的内容小结

  • IInterface 类型的接口,包括:
    • Stub 抽象类
    • aidl 接口定义的操作方法
  • Stub ,是一个 Binder,同时也是一个 IInterface,包括:
    • 将 Binder 转成 IInterface 的 asInterface() 方法
    • 处理调度的 onTransact() 方法
    • 用于在 onTransact() 中标识要进行的操作的两个标志
    • 一个 IInterface 类型的代理
  • ProxyIInterface 类型的代理,包括:
    • 接口定义方法的伪实现,实际调用的是真正的 Binder 的方法

一句话总结:AIDL 帮我们生成了 Binder 和 跨平台接口的转换类 Stub,以及在不同进程时,客户端拿到的代理 Proxy

现在回去看一下 AIDL 的使用,就会多了些理解。

AIDL 的使用回顾

服务端

使用时先在另一个进程的 Service 中实现 AIDL 生成文件中的 Stub 类,然后在 onBind() 中返回:

这里写图片描述

结合上面的分析,可以知道,我们在服务端实例化的是 Stub 的实体,它既是 Binder 也是 IInterface。在其中实现了接口定义的方法,然后在 onBind() 中返回自己。

客户端

在 Activity 中使用 bindService() 绑定服务,然后再回调中,调用 Stub.asInterface() 将拿到的远端 Binder 转换为定义的接口,跨进程的话这里拿到的实际是代理接口:

这里写图片描述

然后就可以调用 Service 中方法了。

小结

根据上面的分析,我们可以看到,AIDL 帮我们做了以下几件事:

  1. 根据定好的接口生成不同进程都可以共同访问的接口类
  2. 在接口类中提供了 Binder 和接口的共同载体 Stub
  3. Stub 中创建了代理类,用于映射调用实际接口实现

有了 AIDL,我们编写跨进程操作就变得十分简单,我们只需要关注业务接口的实现即可。

手动写个 Binder

我们可以模仿 AIDL 创建的文件,手动写个 Binder 来加深下理解。

首先是定义跨进程接口,实现 IInterface

在其中定义要跨进程做的操作,以及标识这两个操作的 code:

public interface IMyAidlDiy extends IInterface {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 void addPerson(Person person) throws RemoteException;public List<Person> getPersonList() throws RemoteException;
}

然后在其中创建这个接口与对应 Binder 的转换类 Stub

既然要两头讨好,那它就需要继承 Binder 的同时实现前面定义的接口,同时提供 Binder 和接口转换的方法,以及作为接口处理事物的方法:

public static abstract class Stub extends Binder implements IMyAidlDiy {private static final String DESCRIPTOR = "net.sxkeji.shixinandroiddemo2.activity.ipc.IMyAidlDiy";public Stub() {attachInterface(this, DESCRIPTOR);}@Overridepublic IBinder asBinder() {return this;}public static IMyAidlDiy asInterface(IBinder binder){if (binder == null){return null;}IInterface localInterface = binder.queryLocalInterface(DESCRIPTOR);if (localInterface != null && localInterface instanceof IMyAidlDiy){return (IMyAidlDiy) localInterface;}else {return new Stub.Proxy(localInterface);}}@Overrideprotected boolean onTransact(final int code, final Parcel data, final Parcel reply, final int flags) throws RemoteException {switch (code){case TRANSACTION_addPerson:data.enforceInterface(DESCRIPTOR);Person _arg0;if (data.readInt() != 0){_arg0 = Person.CREATOR.createFromParcel(data);  //反序列化参数}else {_arg0 = null;}this.addPerson(_arg0);reply.writeNoException();return true;case TRANSACTION_getPersonList:data.enforceInterface(DESCRIPTOR);List<Person> personList = this.getPersonList();reply.writeNoException();reply.writeTypedList(personList);break;}return super.onTransact(code, data, reply, flags);}}

最后创建代理接口,在不同进程中,客户端持有的是代理

它的作用就是伪装成真的 Binder,实际被调用时将数据处理成 Parcel,然后让被代理的 Binder 去处理:

private static class Proxy implements IMyAidlDiy {private IBinder mRemote;public Proxy(final IBinder obj) {mRemote = obj;}public java.lang.String getInterfaceDescriptor() {  //伪装的和真的 Binder 名一样return DESCRIPTOR;}@Overridepublic void addPerson(final Person person) throws RemoteException {Parcel _data = Parcel.obtain();Parcel _reply = Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);if (person != null) {_data.writeInt(1);person.writeToParcel(_data, 0);} else {_data.writeInt(0);}mRemote.transact(TRANSACTION_addPerson, _data, _reply, 0);  //这里调用实际的实现_reply.readException();} finally {_data.recycle();_reply.recycle();}}@Overridepublic List<Person> getPersonList() throws RemoteException {Parcel _data = Parcel.obtain();Parcel _reply = Parcel.obtain();List<Person> _result;try {_data.writeInterfaceToken(DESCRIPTOR);mRemote.transact(TRANSACTION_getPersonList, _data, _reply, 0);_reply.readException();_result = _reply.createTypedArrayList(Person.CREATOR);} finally {_reply.recycle();_data.recycle();}return _result;}@Overridepublic IBinder asBinder() {return mRemote;}
}

自己写完实现对整个 Binder 实现的跨进程调用流程是否理解更深了呢,这里用《Android 开发艺术探索》的一张图总结下:

这里写图片描述

需要注意的是,客户端在发起远程请求时,当前线程会被挂起直到服务端返回,因此尽量不要在 UI 线程发起远程请求。

而在服务端,Binder 方法是运行在 Binder 线程池中的,因此可以直接使用同步的方式实现。

Thanks

《Android 开发艺术探索》
http://weishu.me/2016/01/12/binder-index-for-newer/

这篇关于Android 进阶9:进程通信之 AIDL 解析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1023303

相关文章

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

一文解析C#中的StringSplitOptions枚举

《一文解析C#中的StringSplitOptions枚举》StringSplitOptions是C#中的一个枚举类型,用于控制string.Split()方法分割字符串时的行为,核心作用是处理分割后... 目录C#的StringSplitOptions枚举1.StringSplitOptions枚举的常用

Python函数作用域与闭包举例深度解析

《Python函数作用域与闭包举例深度解析》Python函数的作用域规则和闭包是编程中的关键概念,它们决定了变量的访问和生命周期,:本文主要介绍Python函数作用域与闭包的相关资料,文中通过代码... 目录1. 基础作用域访问示例1:访问全局变量示例2:访问外层函数变量2. 闭包基础示例3:简单闭包示例4

MyBatis延迟加载与多级缓存全解析

《MyBatis延迟加载与多级缓存全解析》文章介绍MyBatis的延迟加载与多级缓存机制,延迟加载按需加载关联数据提升性能,一级缓存会话级默认开启,二级缓存工厂级支持跨会话共享,增删改操作会清空对应缓... 目录MyBATis延迟加载策略一对多示例一对多示例MyBatis框架的缓存一级缓存二级缓存MyBat

前端缓存策略的自解方案全解析

《前端缓存策略的自解方案全解析》缓存从来都是前端的一个痛点,很多前端搞不清楚缓存到底是何物,:本文主要介绍前端缓存的自解方案,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、为什么“清缓存”成了技术圈的梗二、先给缓存“把个脉”:浏览器到底缓存了谁?三、设计思路:把“发版”做成“自愈”四、代码

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Java JDK Validation 注解解析与使用方法验证

《JavaJDKValidation注解解析与使用方法验证》JakartaValidation提供了一种声明式、标准化的方式来验证Java对象,与框架无关,可以方便地集成到各种Java应用中,... 目录核心概念1. 主要注解基本约束注解其他常用注解2. 核心接口使用方法1. 基本使用添加依赖 (Maven