Android 进程间通信(三) --通过 AIDL 理解Binder,并手写Binder服务

2024-06-07 20:08

本文主要是介绍Android 进程间通信(三) --通过 AIDL 理解Binder,并手写Binder服务,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

系列文章
Android 进程间通信(一) – Android 多进程模式
Android 进程间通信(二) – 理解 Binder 的机制
Android 进程间通信(三) --通过 AIDL 理解Binder,并手写Binder服务

上一章,已经学习了 Binder 的通信原理,这里再通过 AIDL 了,再来捋一遍,并自己写个 Binder。

如果你对 AIDL 不熟悉,可以参考这篇文章 AIDL使用详解及进程回调

一. AIDL 基本使用

这里也是用上面的代码,首先是任务类 TaskInfo,需要继承 Parcelable 接口,让as 帮你实现方法即可,如下:

public class TaskInfo implements Parcelable {public int id;public String url;public int progress;public TaskInfo() {}protected TaskInfo(Parcel in) {id = in.readInt();url = in.readString();progress = in.readInt();}@Overridepublic void writeToParcel(Parcel dest, int flags) {dest.writeInt(id);dest.writeString(url);dest.writeInt(progress);}@Overridepublic int describeContents() {return 0;}public static final Creator<TaskInfo> CREATOR = new Creator<TaskInfo>() {@Overridepublic TaskInfo createFromParcel(Parcel in) {return new TaskInfo(in);}@Overridepublic TaskInfo[] newArray(int size) {return new TaskInfo[size];}};@Overridepublic String toString() {return "TaskInfo{" +"id=" + id +", url='" + url + '\'' +", progress=" + progress +'}';}
}

然后是 TaskInfo.aidl

//注意这个 TaskInfo 必须是在 com.example.ipcdemo 下,不如会提示找不到。
parcelable TaskInfo;

IRemoteService.aidl :

//记得导入 TaskInfo.aidl 的包
import com.example.ipcdemo.TaskInfo;
interface IRemoteService {//两数之和int add(int num1,int num2);//添加一个任务TaskInfo addTask(in TaskInfo info);
}

然后 build 一下,就会发现,在 build 下生成了 IRemoteService.java
在这里插入图片描述

接着看这个 IRemoteService 的 java ,代码有点长,可以略过:

public interface IRemoteService extends android.os.IInterface
{/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.example.ipcdemo.IRemoteService{private static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.IRemoteService";/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.example.ipcdemo.IRemoteService interface,* generating a proxy if needed.*/public static com.example.ipcdemo.IRemoteService asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.example.ipcdemo.IRemoteService))) {return ((com.example.ipcdemo.IRemoteService)iin);}return new com.example.ipcdemo.IRemoteService.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_add:{data.enforceInterface(descriptor);int _arg0;_arg0 = data.readInt();int _arg1;_arg1 = data.readInt();int _result = this.add(_arg0, _arg1);reply.writeNoException();reply.writeInt(_result);return true;}case TRANSACTION_addTask:{data.enforceInterface(descriptor);com.example.ipcdemo.TaskInfo _arg0;if ((0!=data.readInt())) {_arg0 = com.example.ipcdemo.TaskInfo.CREATOR.createFromParcel(data);}else {_arg0 = null;}com.example.ipcdemo.TaskInfo _result = this.addTask(_arg0);reply.writeNoException();if ((_result!=null)) {reply.writeInt(1);_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);}else {reply.writeInt(0);}return true;}default:{return super.onTransact(code, data, reply, flags);}}}private static class Proxy implements com.example.ipcdemo.IRemoteService{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 int add(int num1, int num2) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();int _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeInt(num1);_data.writeInt(num2);boolean _status = mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().add(num1, num2);}_reply.readException();_result = _reply.readInt();}finally {_reply.recycle();_data.recycle();}return _result;}//添加一个任务@Override public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();com.example.ipcdemo.TaskInfo _result;try {_data.writeInterfaceToken(DESCRIPTOR);if ((info!=null)) {_data.writeInt(1);info.writeToParcel(_data, 0);}else {_data.writeInt(0);}boolean _status = mRemote.transact(Stub.TRANSACTION_addTask, _data, _reply, 0);if (!_status && getDefaultImpl() != null) {return getDefaultImpl().addTask(info);}_reply.readException();if ((0!=_reply.readInt())) {_result = com.example.ipcdemo.TaskInfo.CREATOR.createFromParcel(_reply);}else {_result = null;}}finally {_reply.recycle();_data.recycle();}return _result;}public static com.example.ipcdemo.IRemoteService sDefaultImpl;}static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);public static boolean setDefaultImpl(com.example.ipcdemo.IRemoteService impl) {if (Stub.Proxy.sDefaultImpl == null && impl != null) {Stub.Proxy.sDefaultImpl = impl;return true;}return false;}public static com.example.ipcdemo.IRemoteService getDefaultImpl() {return Stub.Proxy.sDefaultImpl;}}/** Default implementation for IRemoteService. */public static class Default implements com.example.ipcdemo.IRemoteService{//两数之和@Override public int add(int num1, int num2) throws android.os.RemoteException{return 0;}//添加一个任务@Override public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException{return null;}@Overridepublic android.os.IBinder asBinder() {return null;}}//两数之和public int add(int num1, int num2) throws android.os.RemoteException;//添加一个任务public com.example.ipcdemo.TaskInfo addTask(com.example.ipcdemo.TaskInfo info) throws android.os.RemoteException;

在解释之前,先了解一些概念:

  • IInterface : AIDL 文件必须继承的接口,它只有一个方法 IBinder asBinder() ,实现它的类,代表的是能够跨进程传输 Binder 对象,或者 Binder 代理对象,比如上面的代码中,返回this,表示 Stub 这个内部类是具有跨进程的作用的。
  • IBinder : 实现这个接口的对象具备跨进程书传输的能力,在跨进程数据流经驱动时,驱动会识别 Binder 类型的数据,从而自动完成不同进程 Binder 本地对象以及 Binder 代理对象的转换。

二. 分析Binder 流程

首先,我们看看我们在服务中时怎么构建 Binder 服务的:

    //AIDL 的服务,IRemoteService.Stub mBinder = new IRemoteService.Stub() {@Overridepublic int add(int num1, int num2) throws RemoteException {/*** 这里为具体的实现方法,比如直接返回两数之和*/Log.d(TAG, "zsr add: 接收到客户端传递的两个数字: "+num1+" "+num2);return (num1 + num2);}@Overridepublic TaskInfo addTask(TaskInfo info) throws RemoteException {Log.d("zsr", "收到客户端的信息: "+info);info.id = 0;info.progress = 50;// mHandler.sendEmptyMessage(1);// mTaskInfo = info;return info;}};

可以看到,使用的是 IRemoteService 的子类 Stub ,那我们从这个类开始分析好了。

DESCRIPTOR
看到在 Stub 子类中,实现了一个标记字符串,用包名加类型表示:

 private static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.IRemoteService";

然后在它的构造方法中,注册当前的 Binder 和 字符串:

    public Stub() {this.attachInterface(this, DESCRIPTOR);}

后面需要从 ServerManager 中拿到binder 时,就需要这个字符串了,比如:

queryLocalInterface(DESCRIPTOR)

asInterface
将服务端的 Binder 对象转换成客户端所有的 AIDL 对象;比如:

IRemoteService mBinder = IRemoteService.Stub.asInterface(service);

这个转换是分进程的,如果同个进程,则直接返回自身即可,如果是跨进程,则需要使用它的代理类,去转换了。从代码也可以看出来:

    public static com.example.ipcdemo.IRemoteService asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.example.ipcdemo.IRemoteService))) {return ((com.example.ipcdemo.IRemoteService)iin);}return new com.example.ipcdemo.IRemoteService.Stub.Proxy(obj);}

asBinder
从上面的介绍中已经知晓,返回的对象,具备跨进程的能力,这里返回 this,表示 Stub。

onTransact
该方法运行在服务端的Binder线程池中,数据的写入和结果读取都在这。

当客户端发起跨进程请求时,远程请求会通过底层封装后,交由次方法来处理;从 code 中,可以知道当前执行的是哪个方法,比如我们定义的,add() 和 addTask() 它们的 index 为:

    static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_addTask = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

这样,我们就可以通过 code 去拿到对应的方法:
在这里插入图片描述
继续看 onTransact(int code, Parcel data, Parcel reply, int flags),它的参数有可以序列换和反序列化的 Parcel 参数,data 和 reply;
我们可以从 data 中取出参数(如果方法有参数的话),注意有顺序,然后把结构写入 replay ,这样当你的方法有返回值的时候,replay 返回的就是你需要的。
注意,onTransact 需要 返回 true,返回false 则表示请求失败。

IRemoteService#Proxy
从上面到说,当跨进程调用时,asInterface 返回的是代理类的实力,它也是实现 IRemoteService 接口,在Android 进程间通信(二) – 理解 Binder 的机制 我们知道,Binder 的代理模式,其实就是当 A 访问 B 的 object 方法,B 不会把的 object 给到 A,而是给它一个具体相同方法的代理类,然后A 通过这个代理把参数传递给 B,B 计算之后,把结果再传递给 A;从而实现跨进程的作用。

而它也确实是这么做的:
在这里插入图片描述
首先通过 _data 把参数都写进去,接着使用mRemote.transact 把参数传递给服务,它胡调用 onTransact 方法,然后通过线程池,把结果写入 reply ,最后再返回给 Binder,完成了跨进程的动作。

这样,我们就完成了 AIDL 的分析

三. 自定义 AIDL 服务

从上面看,AIDL 中,系统生成的逻辑比较乱,且所有类都集成在一起,但我们理清楚之后,还是比较容易理解的。那么这里,我们自己来写一个,而不是使用系统生成的。

首先,定义一个接口,让它集成 IInterface,并添加 add 和 addTask 方法,且定义好方法的 id:

public interface IRemote extends IInterface {static final java.lang.String DESCRIPTOR = "com.example.ipcdemo.aidl.IRemote";static final int TRANSACTION_add = (IBinder.FIRST_CALL_TRANSACTION + 0);static final int TRANSACTION_addTask = (IBinder.FIRST_CALL_TRANSACTION + 1);//两数之和public int add(int num1, int num2) throws RemoteException;//添加一个任务public TaskInfo addTask(TaskInfo info) throws RemoteException;}

然后创建一个具备跨进程的实现类 IRemoteLmpl ,集成 Binder 和实现 IRemote 接口:

public class IRemoteImpl extends Binder implements IRemote {@Overridepublic int add(int num1, int num2) throws RemoteException {return 0;}@Overridepublic TaskInfo addTask(TaskInfo info) throws RemoteException {return null;}@Overridepublic IBinder asBinder() {return this;}
}

接着,我们应该在的构造方法中,把binder 和 字符串描述注册到服务中:

    public IRemoteImpl() {this.attachInterface(this,DESCRIPTOR);}

实现供客户端调用的服务端转换的 AIDL 对象的方法 asInterface:

    public static IRemote asInterface(IBinder obj){if (obj == null) {return null;}if (obj instanceof IRemote){return (IRemote) obj;}return new Proxy(obj);}

其中 Proxy 代理类,我们后面再写。

接着,编写 IRemote 的具体实现方法 onTransact :

    @Overrideprotected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {//通过 code 对应的方法switch (code) {case TRANSACTION_add:data.enforceInterface(DESCRIPTOR);//接受到参数数据int num1 = data.readInt();int num2 = data.readInt();int result = this.add(num1, num2);reply.writeNoException();//把结果返回到返回参数中reply.writeInt(result);return true;case TRANSACTION_addTask:data.enforceInterface(DESCRIPTOR);TaskInfo info = null;if (data.readInt() != 0) {//通过反序列化拿到数据info = TaskInfo.CREATOR.createFromParcel(data);}TaskInfo resultInfo = this.addTask(info);reply.writeNoException();if ((resultInfo!=null)) {reply.writeInt(1);info.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);}else {reply.writeInt(0);}return true;default:return super.onTransact(code, data, reply, flags);}}

无法就是一些数据的转换,没啥问题,接着,实现代理类,方便跨进程调用:

 /*** 跨进程的代理类*/private static class Proxy implements IRemote {private IBinder mRemote;Proxy(IBinder iBinder) {mRemote = iBinder;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic IBinder asBinder() {return mRemote;}@Overridepublic int add(int num1, int num2) throws RemoteException {Parcel data = Parcel.obtain();Parcel replay = Parcel.obtain();int reslut;try {data.writeInterfaceToken(DESCRIPTOR);data.writeInt(num1);data.writeInt(num2);/*** 可以看到,当跨进程被调用的时候,只是把调用方的参数,给自身的方法运行,然后再把结果返回回去*/mRemote.transact(TRANSACTION_add, data, replay, 0);replay.readException();reslut = replay.readInt();} finally {data.recycle();replay.recycle();}return reslut;}@Overridepublic TaskInfo addTask(TaskInfo info) throws RemoteException {Parcel data = Parcel.obtain();Parcel replay = Parcel.obtain();TaskInfo result;try {data.writeInterfaceToken(DESCRIPTOR);if ((info != null)) {//用数据 0,1 ,来区分是否写入成功data.writeInt(1);info.writeToParcel(data, 0);} else {data.writeInt(0);}mRemote.transact(TRANSACTION_addTask, data, replay, 0);replay.readException();if ((0 != replay.readInt())) {result = TaskInfo.CREATOR.createFromParcel(replay);} else {result = null;}} finally {data.recycle();replay.recycle();}return result;}}

这样,我们的代码就写完了
在这里插入图片描述
接着修改给其他调用的 RemoteService 的 AIDL 服务:

    //AIDL 的服务,IRemoteImpl mBinder = new IRemoteImpl() {@Overridepublic int add(int num1, int num2) throws RemoteException {/*** 这里为具体的实现方法,比如直接返回两数之和*/Log.d(TAG, "zsr add: 接收到客户端传递的两个数字: "+num1+" "+num2);return (num1 + num2);}@Overridepublic TaskInfo addTask(TaskInfo info) throws RemoteException {Log.d("zsr", "收到客户端的信息: "+info);info.id = 0;info.progress = 50;// mHandler.sendEmptyMessage(1);// mTaskInfo = info;return info;}};

然后,把这两个方法复制到 客户端:

在这里插入图片描述

绑定服务端服务:

//绑定 AIDL 服务
intent.setClassName("com.example.ipcdemo","com.example.ipcdemo.service.RemoteService");
    class RemoteService implements ServiceConnection{@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {//  mBinder = IRemoteService.Stub.asInterface(service);mBinder = IRemoteImpl.asInterface(service);}@Overridepublic void onServiceDisconnected(ComponentName name) {}}

然后在点击事件中,调用 AIDL 方法,拿到数据:

    public void testAidl(View view) {try {if (mBinder != null) {TaskInfo info = new TaskInfo();info.url = "www.google.com";TaskInfo taskInfo = mBinder.addTask(info);int num = mBinder.add(2,3);Log.d(TAG, "zsr testAidl: 获取到服务端数据: "+taskInfo+" "+num);}} catch (RemoteException e) {e.printStackTrace();}}

打印如下;
在这里插入图片描述
这样,我们不通过 AIDL 也实现了跨进程通信了。

工程代码:https://gitee.com/zhengshaorui/IpcDemo

这篇关于Android 进程间通信(三) --通过 AIDL 理解Binder,并手写Binder服务的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Android ClassLoader加载机制详解

《AndroidClassLoader加载机制详解》Android的ClassLoader负责加载.dex文件,基于双亲委派模型,支持热修复和插件化,需注意类冲突、内存泄漏和兼容性问题,本文给大家介... 目录一、ClassLoader概述1.1 类加载的基本概念1.2 android与Java Class

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

关于DNS域名解析服务

《关于DNS域名解析服务》:本文主要介绍关于DNS域名解析服务,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录DNS系统的作用及类型DNS使用的协议及端口号DNS系统的分布式数据结构DNS的分布式互联网解析库域名体系结构两种查询方式DNS服务器类型统计构建DNS域

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Java进程异常故障定位及排查过程

《Java进程异常故障定位及排查过程》:本文主要介绍Java进程异常故障定位及排查过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、故障发现与初步判断1. 监控系统告警2. 日志初步分析二、核心排查工具与步骤1. 进程状态检查2. CPU 飙升问题3. 内存

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

Linux中SSH服务配置的全面指南

《Linux中SSH服务配置的全面指南》作为网络安全工程师,SSH(SecureShell)服务的安全配置是我们日常工作中不可忽视的重要环节,本文将从基础配置到高级安全加固,全面解析SSH服务的各项参... 目录概述基础配置详解端口与监听设置主机密钥配置认证机制强化禁用密码认证禁止root直接登录实现双因素