分享

三步掌握 Android 中的 AIDL

 codingSmart 2021-10-22

第一步 创建 aidl 接口文件

AndroidStudio 中直接右键创建,或者自己一步步建目录喽。

创建完成后会生成一个 XXX.aidl接口文件,我们需要根据需求在这个接口类中添加接口。

在看接口怎么写前,先记住以下三点:

支持的参数类型

  1. 八种基本数据类型;

  2. String、CharSequence;

  3. List、Map,它们中的数据类型也应该是AIDL支持的;

  4. 实现Parcelabel的引用类型。


自定义引用类型使用

如果要使用自定义的数据类型,需要先为它也生成一个aidl文件,里面内容只需两行:

AIDL的包名; parcelable 类名;

例如:

package com.coorchice.coorchicelibone; parcelable Role;

接着就可以创建对应的java数据类了,然后实现Parcelable接口,注意包名需要和aidl一模一样!如果你对自定义类型使用了in或者inout标识符的话,你必须再给自定义类实现 readFromParcel()方法。比如:

public void readFromParcel(Parcel in) {  name = in.readString();  skill = in.readString(); }

然后在定义 aidl 接口时,一定要记得手动写一下自定义引用类的import!

参数修饰符

  • in: 表示参数数据只能由客户端传递到服务端,基本类型就默认只支持in修饰符。

  • out:表示参数数据只能由服务端传递到客户端。即服务端如果修改了参数对象的值,那么客户端的值也会变化,但是服务端无法读取到客户端对象的值。

  • inout:表示参数数据能够双向传递。


定义服务接口

现在,我们可以开始定义服务接口了!这里定义的服务接口就是后面我们需要在客户端调用的。

CoorChice 定义了3个接口,接着编译一下。然后编译器会自动根据我们定义的接口生成对应的类。

注意:自定义的类必须加in、out、inout等标识符,否则会报错!


编译后生成的类解析

编译之后编译器自动帮我们生成了一个服务接口类名.Stub的抽象类,它继承自 Binder,然后实现了我们定义的服务接口(注意我们的服务接口实现了IInterface接口)。嗯,重点是它是一个Binder!它就是我们用来进行 Binder 通讯的!

其实,Android 的 AIDL 就是让编译器帮助我们生成一个实现了我们接口的 Binder,以帮助我们简化开发。当然,如果你了解原理的话,也可以自己写。

下面我们一步一步来看看这编译器生成的类都有些什么?

1. 服务接口实现IInterface

public interface AIDLDemo extends android.os.IInterface {    ... }

编译器根据我们写的服务接口,重新生成了一个接口,唯一的区别就是新的接口继承了IInterface接口。这个接口是干什么的呢?

public interface IInterface {    public IBinder asBinder(); }

可以看到,它只有一个接口方法。这个方法用来定义将实现接口的类应该具备返回一个与之相关联的Binder的功能,以提供通讯能力。一般实现这个方法的类自己本身就会去继承Binder。

这样的设计使得Binder机制不用关心具体的接口是什么,只要是IInterface就行。事实上相当于是我们在IInterface接口的基础上扩展了接口功能,本质上还是一个IInterface,所以Binder能够认出它。

2. 继承Binder,实现服务接口

要进行 Binder 通讯,我们自然需要一个 Binder;要实现我们定义的服务接口功能,自然就需要实现服务接口。那么需要满足这两个条件怎么办?很简单,继承 Binder,然后实现服务接口就行。

编译器为我们生成的类中有一个内部类Stub就是这么干的。事实上,这样的设计随处可见。你可以看看 CoorChice 这篇文章《3分钟看懂Activity启动流程》http://www.jianshu.com/p/9ecea420eb52 中出现的ActivityManagerNative 就是这么干的,虽然它是代理系统的ActivityManagerService,但是模式都是一样的。

public static abstract class Stub extends android.os.Binder implements com.coorchice.coorchicelibone.AIDLDemo


3. Binder需要绑定服务接口,定义DESCRIPTOR描述

为了一个Binder和一个特定服务接口绑定,以对外提供功能,需要给Binder定义一个DESCRIPTOR描述,表示我这个Binder是提供特定功能链接的,不是随便可以用的。

通常,DESCRIPTOR描述会直接使用包名 + 服务接口

4. 实现asInterface()供客户端调用

作为一个服务提供者,为了能够给调用者提供远程功能,自然需要能够提供和远程服务关联的 Binder 来通讯,请求服务。获取 Binder 的接口就是 IInterface 中定义的。

先查询一下获取到的和远程 Service 通讯的 Binde 中是否已经添加了功能接口的实现,如果没有则创建代理,通过代理间接的操作 Binder 和远程 Service 通讯,实现功能。

思考:既然我们已经获取到了能够直接和远程 Service 通讯的Binder,为什么不直接操作它去向远程 Service 请求服务呢?

确实,我们可以直接操作 Binder 向远程 Service 请求服务,但这过程中有很多繁琐的操作,还有一些 code码的区别,如果不封装隔离的话,随着功能的扩展,我们将很难再去维护这段通讯逻辑。还有就是通过这种方式统一的管理通讯逻辑使得它可以随处使用,而不用没一个要用的地方都去写一遍。

既然是要代理和远程服务通讯,而且通讯的目的是为了请求服务接口定义的功能,那么很自然就能想到去实现服务接口,然后再对应的接口方法中实现逻辑即可。这样就可以分开来维护客户端和服务端的对应的每个功能了。

所以,我们的代理也需要实现服务接口,然后在代理中操作远程通讯的Binder进行通讯。

5. 重写onTransact()

在Binder通讯中,一个和远程端通讯的Binder::transact()方法,会触发通讯目标端执行Binder::onTransact(),就是说这个时候已经收到了通讯发起端的请求了。看看这个方法的参数:

public boolean transact(int code, Parcel data, Parcel reply, int flags){}
参数解释
code用来标识指令,即这次通讯是使用什么功能。需要客户端和服务端约定好code码。
data来自发送端的数据包。
reply来自发送端的接收包,往这个包中写数据,就相当于给发送端返回数据。
flags特殊操作标识。

对应的我们在看看Binder::transact()方法:

public boolean transact(int code, Parcel data, Parcel reply, int flags){}
参数解释
code用来标识指令,即这次通讯是使用什么功能。需要客户端和服务端约定好code码。
data发送给远程端的数据包。
reply用于让远程端写回复数据的数据包
flags特殊操作标识。

可以看出来,两个是成对的操作。Binder::transact()操作是一个阻塞式的操作,就是说在这个方法执行返回成功后,直接从reply中读取的数据就是远程端在Binder::onTransact()中填充的数据。

这个过程可以大概抽象成这个样子:

在编译器自动帮我们生成的onTransact()中,会读取data中数据,然后调用对应的方法(这个方法还没实现,所以我们可以继承Stub然后重写,以实现在服务端处理的效果)。比如这个样子:

第二步. 创建一个远程 Service

我们正常创建一个支持其它应用调用的 Service,Service  怎么创建就不说。主要看看在 Service 最重要的一步,就是继承上面生成的Stub,然后自定义一个 Binder。

接着,在onBinder()中返回一个上面这个Binder的实例给客户端。

第三步. 客户端链接Binder

首先重要的一步是,我们必须把这个aidl文件夹拷贝到客户端工程的对应目录下。

包名不能变!

包名不能变!

包名不能变!

然后通过绑定的方式启动这个Service。

总结

恭喜你!现在你已经掌握AIDL了!

实际上,从上面的分析可以看出,AIDL其实就是对Binder机制的简化封装。Android这一套封装使得我们在自己定义Service时方便了许多!你可以不用去编写繁杂的交互,看看编译器自动生成的文件有多不堪入目吧。

但是不管怎么封装,Binder通讯机制的灵魂是不变的,所以要更好的理解这个过程,你可以看看CoorChice的这几篇文章:

《从getSystemService()开始,开撸Binder通讯机制》http://www.jianshu.com/p/1050ce12bc1e

《能用【白话文】来分析Binder通讯机制?》http://www.jianshu.com/p/fe816777f2cf

《Binder机制之一次响应的故事》

http://www.jianshu.com/p/4fba927dce05

这几篇文章从我们接触最多的上层入手,一步步分析到了 Binder 内核层,描述了内核的 Bidner 驱动是如何实现一次完整的 c/s 通讯的。

与之相关

从未如此惊艳!你好,SuperTextView

Android-Material Design风格MVP模式的新闻App(视频图片音乐)

微信号:code-xiaosheng

公众号

「code小生」

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多