AIDL整理

  • 何为AIDL

Android Interface Definition Language即Android接口定义语言

我们知道,因为用户需要,Android系统中可以同时运行着多个进程,为了进程间能够正常的运行,进程之间必须保持独立互不干扰,这就是进程隔离。但是很多时候我们又不得不和另一个进程通信,那么这种进程间的通信就叫做IPC(Inter-Process Communication)。实现IPC的方式有很多种,像管道、信号、消息队列、共享内存、Socket等,RPC(Reomote Procedure Call,译称远程过程调用)也是IPC的一种方式,AIDL使用的就是这种方式。

  • AIDL原理简述

binder-model

图片来源:Binder学习指南 (ps:这篇文章对Binder机制写的不错,值得一看)

简单来说,就是方法提供方称为server,方法调用方称为client,client和server之间的交互就是IPC。首先server注册到一个称为ServiceManager的地方,client调用时会去查询该server是否已经注册,若已注册则取到server的代理对象并执行要调用的方法,对于调用方来说就和调用自己进程中的方法一样,但是背后的工作确是由Binder驱动转交给真正拥有该方法的server去执行并把结果返回给调用方client。

  • AIDL是如何做的

我们还是从使用开始,一步步揭开AIDL的神秘面纱

  • 创建AIDL文件

在src/main下新建文件夹aidl,在其下新建一个.aidl文件,如果你是使用studio的创建AIDL文件功能选项创建的aidl文件,则会看到自动生成了一个basicTypes方法:

image-20200710151840622

这个方法里面的参数就是aidl支持的基本类型(你可以把这个方法删掉也不会有什么问题),除此之外,最好都需要import语句导入(List也可以不用导包,但是是建立在存在jdk的基础上),所以最规范的做法就是除基本类型之外的类型都要导入,和java语法一样,导入为止如下图:

image-20200710152247796

当需要自定义的数据类型时,首先要求这个自定义类型是parcelable类型,并实现Parcelable需要的所有东西,这里值得注意的是,因为AIDL规定除了基本数据类型之外,其他所有类型都需要定向tag显示声明,有in、out、inout三种,基本数据类型默认都是in(只能是in),后面会讲到其作用,这里我们只要记得如果要支持out或者inout定向的话要手动添加readFromParcel()方法,因为这个方法Parcelable接口中没有,需要自行添加:

image-20200710164356911

其次需要定义此类的aidl文件,这个aidl文件因为不是接口所以不会被自动生成(generated):

image-20200710153904493

特别注意: Demo02.aidl的包名要和Demo02.java的包名完全一致!!

然后在要使用这个类的aidl文件中引入:

image-20200710154018178

可以看到pringtList和addDemo02方法中的参数都不是基本类型,所以前面都必须有一个定向tag修饰(这里是in),那么in、out、inout的意义是什么呢?简单来说,根据前面我们提到的进程间通信的机制,通信的双方是client和server,当我们从client传递一个引用类型的数据给server端的时候,如果server端修改了数据,若定向是in,则client的数据不会被改变,若是out或者是inout则client端的数据也会跟着改变(比如说你调用test(a),如果服务端处理时a的某些属性值变了,那客户端的a对象的对应属性值也会跟着改变);同理如果定向修饰为out,则server端不会收到client发送的引用数据类型(比如说你调用test(a),服务端是不会使用这个a的,它只会重新创建一个同样类型的新对象)。这里可以发现,只有引用变量才可能被改变,因为引用数据类型有地址指向,所以这也就是为什么基本数据类型默认是in定向修饰符,而且只能是in,这里其实有一个readFromParcel的操作,后面会看到。关于定向tag,这篇文章讲的很详细:你真的理解AIDL中的in、out、inout么?

好了到现在为止我们已经做好了所有准备工作,现在只需要点击一下studio的rebuild project就会看到在对应文件夹中自动生成的java文件,我猜测studio版本和gradle版本不同,可能生成的目标目录也会不一样,所以不要死记这个路径,但肯定会在build/generated目录下:

image-20200710160104208

下面我们去看看给我们自动生成的java接口类:

1594368452548

可以看到我们的接口继承了一个android.os.IInterface接口:

image-20200710161039214

没什么特别含义,就是为了转化成IBinder类型的。

再来看内部,除了简单的声明了我们aidl中定义的接口方法之外,静态内部抽象类Stub是自动帮我们创建的内容,它继承了android.os.Binder并实现了外部的AIDL接口,它是AIDL的核心。

  • 使用AIDL

我们使用常见的Activity和Service的通信来梳理AIDL的工作,首先创建Service:

image

注意这里的Service可以被多个主题绑定,所以这里的方法要考虑线程安全,其次,因为binder是匿名类实现,同时,它又是私有变量只通过onBind方法暴露,所以这里自定义的方法不能够被外部调用。

再来看一下client端关键代码:

image-20200712091343773

mAidlService是一个AidlTest02类型的变量,在service绑定成功之后,通过AidlTest02.Stub.asInterface(IBinder)方法赋值,我们看看这个方法:

image-20200712092257714

这里出现了两个return分支,本地调用返回和远程调用返回。

  • 本地调用

    如果在单个App的内部通过bindService实现Activity和Service交互,就会走queryLocalInterface本地获取,此时这里的obj就是我们的Service里面定义的AidlTest02.Stub的匿名类对象binder,通过onBind方法传到了这里,如果不为空则调用它的queryLocalInterface方法去获取一个IInterface类型的对象,这是IBinder接口的的方法,因为我们的Stub继承了Binder,所以我们直接去Binder下面去找这个方法的实现:
    image-20200712092830056

    很简单,如果mDescriptor比对成功则会返回一个IInterface类型的mOwner,那这两个值是何时赋值的呢?还记得我们前面截图中有一个Stub的构造方法吗,这个构造方法是在Service里定义binder匿名类的时候调用的,构造方法里调用了this.attachInterface(this,DESCRIPTOR):
    image-20200712093553659

    DESCRIPTOR是Stub内部定义的常量,owner参数就是Stub本身,因为Stub实现了AidlTest02接口,AidlTest02又实现了IInterface接口。

    另外,因为是直接使用本地的binder对象,并没有走Proxy逻辑,所以本地调用时定向tag是无效的。

  • 远程调用

    因为是Android间的系统进程间通信,所以不是传统意义上以网络为传输介质的RPC,这里的远程调用就是App之间的进程通信。我们需要创建两个App(一个项目中的两个Module即可),两个App分别是client端和service端,都需要共同的aidl文件,而且他们的包名要严格相同 。

    这里只贴一下另一个Module中Service相关代码:

    image-20200712200841772
    image-20200712200752483

    下面部分截图还是上面的本地调用的,不过原理是一样的,没什么影响,AidlAutoCreateTest03是我新建的远程服务端aidl文件,这里有三个方法分别使用了in、out和inout定向,下面有一些表现出关于in、out和inout的不一样的地方我会用这个新的远程代理服务的一些源码截图说明。

    如果是远程调用,那对于这个asInterface方法来说,参数IBinder其实是client的服务端代理,所以这里会返回一个Stub的内部类Proxy对象,它的构造方法里的IBinder就是服务端注册给client的代理。若现在我们调用getDemo02()方法,其实调用的就是Proxy的getDemo02方法:

    image-20200712115602366

    取得了两个Parcel对象,_data是用来保存Client传递过来的数据的, _reply是用来保存返回给调用方(client)的数据的。上面的是无参数的所以 _data没有存储额外数据的操作,看下面这段代码:

    image-20200712124616268

    client传递的数据是demo,demo.writeToParcel(_data,0)保存到 _data。

    然后通过mRemote.transact方法(mRemote此时是代理Binder)调用服务端的真正对象的方法,这个方法后面做的工作牵扯到了Binder机制的深层逻辑,这里先不讨论,后面单独整理,现在只要知道,调用这个方法之后,最后会走到Stub的onTransact方法里(我们之前在Service里面定义的叫binder的Stub匿名类对象):

    img

    看到this.printList、this.getDemo02、this.addDemo02了吧,这调用的就是我们在Service里面定义的那个匿名类对象的对应方法。对于getDemo02方法,因为没有传递数据进来,所以没有从data中取数据的操作,但是有返回值,所以通过_result.writeToParcel(reply,android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE)方法把getDemo02的返回值写到reply;而对于addDemo02方法,会通过Parcelable类型的CREATOR从之前mRemote的transact方法传递进来的data构造出当时客户端传递的真实数据,然后传递给真正的addDemo02()方法,因为没有返回值,所以这里reply并没有执行操作。

    中间还有部分代码是Proxy和Stub的之间的数据传递的,包括判断客户端传递的非基本类型参数或引用型返回值是否为null,通过writeInt和readInt是否为0来判断是否有必要将参数继续往下传递。

    值得注意的是:

    • out定向

      如果AIDL的方法参数是out定向,则服务端不会收到客户端的数据,或者说它不会去取,Proxy中是这样做的:
      image-20200712191124166

      可以看到_data并没有继续往下传递参数,作为比较,如果是in或者inout定向时:
      image-20200712191531343

      Stub的onTransact方法里是:

      image-20200712191918877

      可以看到,它是直接new了一个新对象,同样,作为对比,in和inout是这么做的:

      image-20200712192107399

      这里额外说明一下这个地方:

      image-20200712193927649

      这就是写回给client传递过来的对象的值的地方,只有out和inout会有,这就是前面说的为什么如果有out或者inout定向的话要给自定义Parcelable类手动加上readFromParcel方法的原因。

    • in定向

      in定向修饰的话就是参数会使用客户端传递过来的,Proxy中:

      image-20200712194523107

      Stub中:

      image-20200712194829706

      同时,它不会将参数保存到reply中,Proxy中也不会有dd.readFromParcel(reply)的操作。

    • inout定向

      inout没什么特殊的,就是兼备了in和out。

总结

到此为止,我们顺利地梳理了一遍AIDL从创建aidl文件到使用AIDL机制来实现跨进程通信地流程,不过其中还有bindService方法之后Android框架做了什么、onTransact方法中return true之后Binder是如何去做的问号,后面有时间会研究一下。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342