android aidl及binder基础知识总结

news/2024/11/24 23:10:13/

1、什么是binder

binder是android framework提供的,用于跨进程方法调用的机制,具有安全高效等特点。

我们知道,在 Android 系统中,每个应用程序都运行在一个独立的进程中,各个进程之间需要进行数据交换和调用,以实现各种功能。为了实现这个目的,binder应运而生。

2、binder的历史

android基于linux内核,linux提供了非常多的跨进程机制。
为什么android要使用binder而不使用linux已有的ipc机制呢,这个抉择在android内部团队中有过争论。 部分工程师认为binder内部的线程等待带来了系统开销,同时没有显示出更好的用处。
但最后binder还是凭借其优势胜出了。 其实,binder是部分android工程师在PalmSource工作时开源的一种ipc机制,负责开发ipc的工程师直接在此binder上进行了移植和开发。

在《Android传奇》一书中有介绍binder的相关历史。

3、binder基本使用

binder是一种架构,这种架构提供了服务端接口、binder驱动和客户端三个模块。

客户端通过binder远程调用服务端接口,binder驱动负责完成这次远程调用。
在android framework中提供了Binder类,一个类如果扩展Binder类,那么该类就有提供远程服务的能力,该类对象一旦创建,其内部就会创建一个隐藏的线程,用来接收binder驱动发送的消息,从而调用Binder类的onTransact()方法。

binder驱动从表现上看,就是在客户端和服务端传递各种消息,以完成跨进程调用。
在任意一个服务端binder对象创建时,同时也会在binder驱动中创建一个对应的mRemote对象,该对象也是binder类型。 客户端就是通过mRemote对象来访问远程服务的,相当于经过了一层代理。 这种机制提供了更好的安全性。

客户端通过获取远程服务在binder驱动中对应的mRemote对象,通过调用mRemote.transact方法从而实现对远程服务的调用。

从整个架构上看,就是服务端提供binder,binder驱动负责转发消息,客户端获取binder引用并调用。 粗略看很简单,细节是魔鬼。

摘抄一个书中的例子,从demo理解binder的调用过程。
比如要提供一个音乐播放的远程调用, 可以提供一个MusicPlayerService,代码如下:

public class MusicPlayerService extends Binder {public void startPlay(String filePath) {}public void stop() {}@Overridepublic void onTransact(int code, Parcel data, Parcel reply, int flag) {// 这个方法很重要,由binder驱动调用// 根据约定的code值分别调用不同的业务方法,此处是startPlay和stopif (code == 0x100) {this.startPlay(file_path)} else if (code == 0x101) {this.stop()}}
}

如上MusicPlayerService就可以提供了远程调用服务了。
但是客户端如何使用呢。通过binder架构知道,要想远程调用,必须获取远程服务在binder驱动中对应的binder代理对象。此处假设已经获取到相关的引用mRemote,那么客户端便可以如下调用。

IBinder mRemote = null;
String filePath = "xxx";
int code = 0x100;Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain(); // 接收远程调用的结果
data.writeString(filePath);
mRemote.transact(code, data, reply, 0)

如上便完成了客户端对服务端的远程调用。 从这个demo就可以看出跨进程调用其实就是要通过Binder驱动来中转一下调用。 以前没看过这个示例时老是不理解aidl生成的规则,不断的复习也总是记不住规则。但是通过这个示例一下就明白了这个过程,在理解的基础上再去手动编写binder类就水到渠成了(虽然一般也用不到手动去写binder类)。

现在就只剩如何获取远程服务对应的binder对象了。 为了简化这个过程,android frameworks通过service提供这个能力。
在service的生命周期方法中有一个onBind方法返回IBinder对象,方法签名如下:

override fun onBind(intent: Intent?): IBinder? {

在调用context.bindService方法进行绑定时,要求传入一个ServiceConnection的对象,在服务绑定成功时会回调onServiceConnection方法,一并传回服务端的Binder代理对象。 如下:

Intent it = new Intent(this, xxxService.class);
context.bindService(intent, new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {// 此处就拿到了远程服务对应的binder了,客户端通过此binder就可以调用远程服务的方法了。 }@Overridepublic void onServiceDisconnected(ComponentName name) {}
}, Service.BIND_AUTO_CREATE);

仔细想想,应用开发好像也只有这么一条路获取服务端的binder。
但是最初我们定义的MusicPlayerService并不是一个真正的service,无法使用bindService方法,故需要改造一下,让MusicPlayerService继承Service类。 如下:

public class MusicPlayerService extends Service {private XXXBinder mBinder;@Overridepublic IBinder onBind(Intent intent) {return mBinder;  // 此处返回服务端的binder}public void startPlay(String filePath) {}public void stop() {}// 由于android应用开发上只能通过service进行远程调用,故在service内部类创建binder,方便调用service定义的业务方法。 public xxxBinder extends Binder {@Overridepublic void onTransact(int code, Parcel data, Parcel reply, int flag) {// 这个方法很重要,由binder驱动调用// 根据约定的code值分别调用不同的业务方法,此处是startPlay和stopif (code == 0x100) {startPlay(file_path)} else if (code == 0x101) {stop()}}}
}

由于java不能多继承,继承了service类就不能继承Binder类了。 那么可以在MusicPlayerService类内部构建一个内部类,继承binder,并通过onBind返回binder对象给客户端。 这个代码架构便是使用binder的基本雏形了。但是,在日常开发过程中,为了扩展性和解耦,一般将业务方法通过接口抽象出来。 如下抽象出播放接口。

public interface IPlayer {public void startPlay(String filePath);public void stop();
}

通过上面的代码可以发现,播放的实现放在XXXBinder类或者MusicPlayerService都是可行的。 为了方便binder调用业务方法,尝试用binder实现接口并实现业务。 改造后的代码如下:

public class MusicPlayerService extends Service {private XXXBinder mBinder;@Overridepublic IBinder onBind(Intent intent) {return mBinder;  // 此处返回服务端的binder}// binder实现了IPlayer业务接口public xxxBinder extends Binder implemention IPlayer {@Overridepublic void onTransact(int code, Parcel data, Parcel reply, int flag) {// 这个方法很重要,由binder驱动调用// 根据约定的code值分别调用不同的业务方法,此处是startPlay和stopif (code == 0x100) {this.startPlay(file_path)} else if (code == 0x101) {this.stop()}}public void startPlay(String filePath) {Log.i("test", "startPlay");}public void stop() {Log.i("test", "stop");}}
}

至此,使用binder的代码结构就较为清晰了。以上demo一步步的改造,其实就是为了更加深刻的理解binder的使用,并向aidl靠拢,从而更好的理解aidl生成的代码。 从上代码可以看出,使用binder的代码接口基本是固定的,所以android framewrok提供了一个aidl的工具来简化这个过程。

4、aidl的使用

在对应模块上右键选择aidl,会弹出创建aidl的对话框,输入名字as会自动生成对应的aidl文件。 在aidl文件中加入自己的业务接口即可。 build一下as就会根据aidl自动生成相应的接口类和binder类,具体文件此处省略。

在这里插入图片描述

需要注意的是,aidl中只支持Parcelable对象和原子类,如果有自定义的类需要跨进程访问时需要实现Parcelable接口。

客户端通过如下代码获取接口对象并实现调用。

class MusicClient {private var mIMusicPlayer: IMusicPlayer? = nullprivate var mServiceConnection = object: ServiceConnection {override fun onServiceConnected(name: ComponentName?, service: IBinder?) {// 这个方法中提供了是本地通信还是跨进程通信的判断// 这里就客户端进程就获取了远程服务的binder引用了。 aidl工具生成了Stub及Proxy类封装了直接通过binder.transact调用的逻辑,让开发者只需和接口交互即可。 不必关心binder交互的细节。 mIMusicPlayer = IMusicPlayer.Stub.asInterface(service)}override fun onServiceDisconnected(name: ComponentName?) {}}fun bindMusicService(context: Context) {val intent = Intent(context, PlayerService::class.java)context.bindService(intent, mServiceConnection, Service.BIND_AUTO_CREATE)}
}

注意在使用aidl时,如果客户端和服务端在不同的工程时,两端的aidl文件要是一样的。实际中可以直接拷贝。

5、binder架构

此处摘抄一张binder架构图

binder架构
从这张图就可以清晰看到客户端、驱动及服务端的职责,也能更好的理解binder的交互过程。这么看下来,感觉binder驱动其实就是负责对消息进行了转发,同时对交互的过程进行了一定控制。

6、总结

1、一个类要想序列化就要实现Serializable或Parcelable接口,同理一个类要想提供跨进程服务,就必须继承binder类。 binder就像一个标记类一样,只要继承了,就有资格在进程间通信了。

2、一个binder跨进程的通信包含了客户端、服务端和binder驱动三方。

3、在应用开发中主要通过aidl工具、Service及Context.bindService实现跨进程访问。

4、aidl是一个命令行工具,协助生成跨进程调用的样板代码。

7、参考

1、《Android传奇》

2、《Android内核剖析》

3、《Android进阶解密》


http://www.ppmy.cn/news/84165.html

相关文章

C++第三章:字符串、向量和数组

字符串、向量和数组 一、命名空间的using声明每个名字独立using声明头文件不应包含using声明 二、标准库类型string2.1 定义和初始化string对象直接初始化和拷贝初始化 2.2 string对象上的操作读写string对象读取未知数量的string对象使用getline读取一整行string的empty和size…

python绘制带置信区间的折线图

本文目录 一、数据准备二、添加置信区间三、完整代码四、运行结果五、python绘图往期系列文章目录 在统计学和数据分析领域中,我们常常需要比较两个或多个样本数据之间的差异。而带置信区间的折线图则是一种直观且常用的展示数据差异的方式。在这篇文章中&#xff0…

开箱即用的ChatGPT替代模型,还可训练自己数据

一、普遍关注是什么? OpenAI 是第一个在该领域取得重大进展的公司,并且使围绕其服务构建抽象变得更加容易。然而,便利性带来了集中化、通过中介的成本、数据隐私和版权问题。 而数据主权和治理是这些新的LLM服务提供商如何处理商业秘密或敏…

【发电厂用JDHF-1010 合闸(分闸)监测继电器(220V/110V) JOSEF约瑟】

■JDHF-1000合闸(分闸)监测继电器主要用于各种保护和自动控制装置中,作为断路器操作运行状态的监测继电器。■交直流两用■监测继电器具有高内阻特性,可适应各种框架式断路器的合分回路。■快速导轨安装结构,适合各种导轨安装。■螺钉压接式端…

外购设备PDA

专业扫描引擎,扫尽千军万码 工业级专业扫描引擎,数据采集精准、快速、安全; 同时增加摄像头扫描,自带绿点定位,实现快速对准; 可识别破损,沾染灰渍等条码提高工作效率。 一、产品特点 216GB/…

国产易灵思FPGA的FIFO应用详解

一、软件设置界面 FIFO(First In First Out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写。与 ROM 或 RAM 的按地址读写方式不同, FIFO 的读写遵循“先进先出”的原则,即数据按顺…

【Java学习记录-4】相关名词和概念记录(持续更新)

1 注解 Override是一个注解,可以帮助我们检查重写方法的方法声明的正确性 注意: 私有方法不能被重写(父类私有成员子类是不能继承的)子类方法访问权限不能更低(public > 默认 > 私有) 2 包 说明…

SSM(Spring篇)

Spring Spring的IOC和DI Spring简介 介绍 Spring的分层Java SE\EE应用full-stack轻量级开源框架,以IOC(Inverse Of Control:反转控制)和AOP(Aspect Oriented Programing:面向切面编程)为内核…