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

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

C#如何优雅地取消进程的执行之Cancellation详解

《C#如何优雅地取消进程的执行之Cancellation详解》本文介绍了.NET框架中的取消协作模型,包括CancellationToken的使用、取消请求的发送和接收、以及如何处理取消事件... 目录概述与取消线程相关的类型代码举例操作取消vs对象取消监听并响应取消请求轮询监听通过回调注册进行监听使用Wa

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【区块链 + 人才服务】区块链集成开发平台 | FISCO BCOS应用案例

随着区块链技术的快速发展,越来越多的企业开始将其应用于实际业务中。然而,区块链技术的专业性使得其集成开发成为一项挑战。针对此,广东中创智慧科技有限公司基于国产开源联盟链 FISCO BCOS 推出了区块链集成开发平台。该平台基于区块链技术,提供一套全面的区块链开发工具和开发环境,支持开发者快速开发和部署区块链应用。此外,该平台还可以提供一套全面的区块链开发教程和文档,帮助开发者快速上手区块链开发。