AIDL跨进程通信

AIDL的定义

AIDL,Android Interface Definition Language(接口定义语言)。

AIDL是一种IPC(Inter-Process Communication)机制,即实现跨进程通信的一种方式。其余的IPC机制还有,文件的共享,使用Bundle传递数据,Messenger(基于AIDL),AIDL,ContentProvider。

AIDL的实现步骤

(1)服务端:创建IBookManage.aidl文件,在里面声明业务方法接口(不实现)。

注意:IBookManage.aidl文件中并不是所有的类型都可以使用,能使用的具体有如下:

  • 基本数据类型(int,float,long,double,boolean,char等)
  • String和CharSequence
  • List:只支持ArrayList,里面每个元素都必须能够被AIDL支持
  • Map:只支持HashMap,里面的每个元素都必须能够被AIDL支持
  • Parcelable:所有实现了Parcelable的对象
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

自定义的Parcelable对象和AIDL接口必须要显式import进来,不管是否位于同一个包中。平时我们如果位于同一个包就能不用import就可以使用。

另外,如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个跟它同名的AIDL文件,并在里面添加如下内容,以下是例子:

package com.example.yuli.ipctest.aidl;

parcelable Book;

(2)如果IBookManager.aidl文件内容没错的话,该项目会自动生成一个IMyService.java文件。切换到Project视图,IMyService.java文件所在位置:


(3)服务端:创建一个Service类,假如叫MyService。重写方法onBind方法。在MyService类里创建一个内部类,MyBinder类继承IBookManage.stub()内部类。并在onBind方法里返回一个MyBinder类实例。

(4)服务端:在AndroidManifest.xml文件中注册该Service类。由于远程调用服务是通过隐式调用的,所以我们要设置intent-filter里的内容。在action里写上自定义的action匹配字段,一般为该Service类所在的完整路径,例如:"com.example.yuli.ipctest.ContactService"。category中一定要写一个"android.intent.category.DEFAULT"。

(5)把服务端的IMyService.java文件复制粘贴到客户端中,并且很重要的一点是IMyService所在的位置必须一致,因为IMyService.java文件中:


声明Stub类的时候implement时,已经完整指明IMyService的具体包名和类名

这时如果随便把该文件复制到客户端的一个位置,例如:com.example.yuli.MessageWhistle.IMyService,明显就会报类找不到的错误,所以这时应该在客户端创建一个一样的位置才不会报错。如:

(6)客户端:因为是要跨进程通信,所以必定要绑定远程服务,因此用bindService方法。创建一个ServiceConnection类对象作为bindService方法里的参数。创建ServiceConnection时需要重写onServiceConnected方法和onServiceDisconnected方法。其中onServiceConnected方法中有一个从服务器端中返回的IBinder对象,其实就是onBind方法中返回的MyBinder对象,但我们知道我们要调用的业务方法是声明在IMyService.java接口文件中的,因此我们要把IBinder对象转化成IMyService的对象才能调用业务方法。而我们使用的方法是IMyService里实现的asInterface方法。

注意:有人会问,为什么不能直接强制转换?

这是一个继承实现关系图


有这幅图一目了然,之所以IBinder类对象不能强制转换成MyService类对象是因为,IBinder类中有一个内部类BinderProxy,它和MyService位于两根树枝上,因此不能实现强制转换。

由AIDL文件自动生成的Java文件中的内容说明

IMyService文件的内容结构如下图所示:


asInterface(IBinder):把服务端传过来的Binder对象转化成AIDL接口类型的对象。这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.Proxy对象。

asBinder():此方法用于返回当前Binder对象。

onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags):这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过code可以确定客户端想要调用的目标方法是哪个,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact方法的执行过程就是这样的。需要注意的是,如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能调用我们的服务。

Proxy#getValue():刚才说了,asInterface的转换过程是区分进程的,如果客户端和服务端位于同一进程,asInterface返回的是服务端的Stub对象本身,调用的是stub对象中实现的getValue,如果位于同一进程,调用的是Stub.proxy对象,则调用的方法是Stub.proxy中的getValue,但其实最后返回的都是相同的结果,因为最后都是回到MyBinder中的getValue的实现部分。当返回的IBinder对象是Stub类对象,当客户端发起请求时,会触发Stub类里的onTransact方法,再执行到MyBinder的getValue;当返回的IBinder对象是Stub.proxy对象,则当客户端发起请求时,触发的是proxy类里的getValue方法,里面调用了IBinder的transact方法,这个方法其实就是调用onTransact方法,最后也同样调用到MyBinder的getValue方法。所以其实两者都是返回相同的结果。

注意:Proxy类里的业务方法都是运行在客户端的,而不是像onTransact方法那样运行的服务端的Binder线程池中。理由:我是这样理解的,当我们的对象引用指向哪里的方法内存块,该方法就运行在哪里。假如客户端和服务端不位于同一进程中,当我们绑定远程服务后,客户端的IBinder引用指向服务端的MyBinder实例的内存,而onTransact方法是属于Stub类的,而MyBinder是Stub类的子类,因此onTransact方法也是属于MyBinder类的,因此当为MyBinder对象分配内存时,onTransact方法所对应的内存也在服务端中,所以onTransact是运行在服务端的,而且还是服务端的Binder线程池中。当我们通过IMyService.Stub.asInterface(IBinder)转换成Proxy对象时,其实这个过程是走了客户端中我们复制过来的代码,所以proxy对象是在客户端中生成的,也即分配的内存在客户端中,调用proxy对象的getValue方法自然是调用客户端内存里的方法,因此Proxy对象的业务方法是运行在客户端的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值