使用AIDL
Posted oude
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用AIDL相关的知识,希望对你有一定的参考价值。
copy from: https://www.jianshu.com/p/8a188d1d973a
1.服务端
服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。
2.客户端
首先绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。需要注意的是,AIDL的包结构在服务端和客户端要保持一致,否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法正常运行。
步骤一:创建Book.java类、Book.aidl类、IBookManager.aidl类
详见:IPC机制(二)
步骤二:创建Service并重写IBookManager类
不要忘了在androidManifest.xml中注册:
步骤三:在客户端中启动Service
运行结果:
假设有一种需求:用户不想时不时地去查阅图书馆列表了,于是他去问图书馆,“当有新书时能不能把新书的信息告诉我呢?”这是一种典型的观察者模式,每个感兴趣的用户都观察新书,当新书到的时候,图书馆就通知每个对这本书感兴趣的用户。
首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,当然用户也可以随时取消这种提醒。之所以选择AIDL接口而不是普通接口,是因为AIDL中无法使用普通接口。
这里我们创建一个IOnNewBookArrivedListener.aidl文件,我们所期望的情况是:当服务端有新书到来时,就会通知每一个已经申请提醒功能的用户。从程序上来说就是调用所有的IOnNewBookArrivedListener对象中的onNewBookArrived方法,并把新书的对象通过参数传递给客户端。内容如下所示:
除了要新加一个AIDL接口,还需要在原有的接口中添加两个新方法,代码如下所示。
对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须实现Parcelable接口的原因。那么我们该怎么做才能实现解注册功能呢?答案是使用RemoteCallbackList。
RemoteCallbackList是系统专门提供的用于删除跨进程Listener的接口。虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们底层的Binder对象是同一个,利用这个特性,就可以实现解注册。当客户端解注册的时候,我们只要遍历服务端所有的Listener,找到那个和解注册Listener具有相同的Binder对象的服务端Listener并把它删掉即可,这就是RemoteCallbackList为我们做的事情。同时RemoteCallbackList还有个很有用的功能,那就是当客户端进程终止后,它能够自动移除客户端所注册的Listener。另外,RemoteCallbackList内部实现了线程同步的功能,所以我们使用它来注册和解注册时,不需要做额外的线程同步工作。
使用RemoteCallbackList,有一点需要注意,我们无法像操作List一样去操作它,尽管它的名字中也带个List,但是它并不是一个List。遍历RemoteCallbackList,必须要按照下面的方式进行,其中beginBroadcast()和finishBroadcast()必须要配对使用,哪怕我们仅仅是想要获取RemoteCallbackList中的元素个数,这是必须要注意的地方。
我们知道,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR。因此,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法。由于客户端的onServiceConnected和onServiceDisconnected的方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法,这点要尤其注意。另外,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做。避免出现ANR,我们只需要把调用放在非UI线程即可,如下所示。
同理,当远程服务需要调用客户端的Listener中的方法时,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池。所以,我们同样不可以在服务端中调用客户端的耗时方法。比如针对BookManagerService的onNewBookArrived方法,如果客户端的这个onNewBookArrived方法比较耗时的话,那么请确保BookmanagerService中的onNewBookArrived运行在非UI线程中,否则将导致服务端无法响应。
另外,由于客户端的IOnNewBookArrivedListener中的onNewBookArrived方法运行在客户端的Binder线程池中,所以不能在它里面去访问UI相关的内容,如果要访问UI,请使用Handler切换UI线程。
为了程序的健壮性,我们还需要做一件事。Binder是可能意外死亡的,这往往是由于服务端进程意外停止了,这时我们需要重新连接服务。
有两种方法,第一种方法是给Binder设置DeathRecipient监听,当Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务。另外一种方法是在onServiceDisconnected中重连远程服务.
这两种方法的区别在于:onServiceDisconnected在客户端的UI线程中被回调,而binderDied在客户端的Binder线程池中被回调也就是在binderDied方法中我们不能访问UI,这就是它们的区别。
最后,如何在AIDL中使用权限验证功能。默认情况下,我们远程服务任何人都可以连接,但这应该不是我们愿意看到的,所以我们必须给服务加入权限验证功能,权限验证失败则无法调用服务中的方法。在AIDL中进行权限验证,这里介绍两种常用的方法。
第一种方法,我们可以在onBind中进行验证,验证不通过就直接返回null,这样验证失败的客户端直接无法绑定服务,至于验证方式可以有多种,比如使用permission验证。首选在AndroidMenifest中声明所需的权限,比如
定义了权限以后,就可以在BookManagerService的onBind方法中做权限验证了。
一个应用来绑定我们的服务时,会验证这个应用的权限,如果它没有使用这个权限,onBind方法就会返回null,最终结果是这个应用无法绑定到我们的服务,这样就达到了权限验证的效果,这种方法同样适用于Messenger中。如果我们自己的内部的应用想绑定到我们的服务中,只需要在它的AndroidMenifest文件中采用如下方式使用permission即可。
第二种方法,我们可以在服务端的onTransact方法中进行权限验证,如果验证失败就直接返回false,这样服务端就不会终止执行AIDL中的方法从而达到保护服务端的效果。至于具体验证方式很多,可以采用permission验证,具体实现方式和第一种方法一样。还可以采用Uid和Pid来做验证,通过getCallingUid和getCallingPid可以拿到客户端所属应用的Uid和Pid,通过这两个参数我们可以做一些验证工作,比如验证包名。
作者:熊出没之大熊快跑
链接:https://www.jianshu.com/p/8a188d1d973a
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
以上是关于使用AIDL的主要内容,如果未能解决你的问题,请参考以下文章
Binder 机制AIDL 分析 ( 创建 AIDL 文件 | 创建 Parcelable 类 | AIDL 中使用 Parcelable 类 | 编译工程生成 AIDL 对应的Java源文件 )(代