Binder的原理
要想了解AIDL
就需要先了解Binder
的原理,所以这里先说一下Binder
原理,Binder
的原理大概是这样:
服务器端:当我们在服务端创建好了一个Binder对象后,内部就会开启一个线程用于接收binder驱动发送的消息,收到消息后会执行相关的服务器代码。
Binder驱动:当服务端成功创建一个Binder对象后,Binder驱动也会创建一个mRemote对象,该对象的类型也是Binder类,客户就可以借助这个mRemote对象来访问远程服务,注意这里是借助,真正调用的时候需要将这个转换成对应的对象,比如使用AIDL
的时候就要转换成AIDL
对象。
客户端:客户端要想访问Binder的远程服务,就必须获取远程服务的Binder对象在binder驱动层对应的mRemote引用。当获取到mRemote对象的引用后,就可以调用相应Binder对象的暴露给客户端的方法(如果有方法的话)。
AIDL
AIDL
的本质其实就是系统为我们提供了一种快速实现Binder
的工具,我们完全可以不用AIDL
,自己去写代码实现Binder
,但是当你写出来的时候会发现其实和AIDL
自动生成的代码一模一样。我们接下来来分析一下原理,因为AIDL
的实现其实就是快速实现Binder
,所以原理自然离不开Binder
。但是在分析原理之前,我们先将系统根据我们定义的AIDL
文件自动生成的java文件分析一下。比较重要的就是Stub
和它的内部代理类Proxy
。我们说一下重要的方法:
asInterface(android.os.IBinder obj)
用于将服务器的Binder对象转换成客户端所需的AIDL
接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于统一进程,那么返回服务器的Stub
对象本身,否则返回的是系统封装后的Stub.proxy
对象。
onTransact(int code,android.os.Parcel data,android.os.Parcel reply,int flags)
这个方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由服务端 的onTransact
方法来处理。这个方法有四个参数,分别是code
,data
,reply
,flags
.code是确定客户端请求的方法是哪个,data是目标方法所需的参数,reply是服务器端执行完后的返回值。如果这个方法返回false
,那么客户端的请求会失败。
Proxy#getBookList
这里的getBookList
方法就是在自定义的AIDL
文件中定义的方法,这个方法运行在客户端,当客户端远程调用此方法的时候,内部实现是这样的:首先在代理类中创建该方法所需要的输入型Parcel对象_data,输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data
中,接着mRemote
调用transact
方法来发起RPC(远程过程调用)请求, 同时当前线程挂起,然后服务端的onTransact
方法会被调用,直到RPC返回后,当前线程继续执行,并从_reply
中取出RPC过程的返回结果并返回(如果有返回值的话),之前创建的参数其实就是onTransact()
方法需要的参数。
说完了重要方法,接下来分析AIDL
原理:
服务端:因为要实现Binder
,必须在服务器端创建一个Binder
对象,如何创建呢?就是newAIDL
接口中的Stub
内部类,代码示例如:
Binder mBinder=new IBookManager.Stub(){接口方法实现}
其中IBookManager
是系统根据我们自己定义的IBookManager.AIDL
所生成的类。
Binder驱动:在AIDL
中,Binder驱动其实就是Service。
客户端:要实现客户端跨进程和服务端通信,必须获得服务端的Binder
对象在binder驱动层对应的mRemote引用,如何获得呢?首先绑定远程服务,绑定成功后的ServiceConnection
中的IBinder service
其实就是mRemote引用
,但是因为是使用AIDL
方式,所以需要在客户端中调用IBookManager.Stub.asInterface(android.os.IBinder obj)
方法将服务器返回的Binder对象转换成AIDL
接口,然后就可以通过这个接口去调用服务器的远程方法了。
根据原理,我们得出AIDL
的使用流程,其实很简单,大致就是在服务端创建一个Service
,然后创建一个Binder
对象,最后在客户端得到这个Binder
对象。
AIDL
使用流程:
先建立AIDL
,如果在你建立的AIDL
接口中,有自定义的类,那么,也需要建立这个类的AIDL,并且名字要完全相同。同时在使用的时候,一定要显示的导入这个类。接下来的流程就是跟Binder的一样了。
服务器端:创建Binder
对象,并且实现接口中的方法。
客户端:绑定service,得到Binder
对象在驱动层对应的mRemote引用。
重点
1.当你在客户端调用服务器的方法的时候,其实是通过代理去访问,详情可以看上面的重点方法介绍里的Proxy#getBookList
,所以你在客户端连续调用两次服务器的同一个方法的时候,比如,这里的getBookList,你会发现,里面的对象都不一样。因为每次在调用方法的时候,在代理类中都会创建该方法所需要的参数对象,所以里面的对象会变化。
2.AIDL
中无法使用普通的接口,只能使用AIDL
接口,并且实现AIDL
接口的时候不能用implements
,因为需要实现的接口其实是自定义接口.Stub
,而不是自己定义的那个接口。使用implements
无法实现。
3.解注册的时候需要使用到RemoteCallbackList
,需要注意的是这个类的beginBroadcast()
和finishBroadcast()
一定要配对使用,否则会出现异常java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast
,特别是在使用for
循环的时候。
4.对于AIDL
中的in
,out
,inout
这里就直接附上一篇别人写的博客,这篇博客讲的很详细,而且我也赞同他的观点,纸上得来终觉浅,绝知此事要躬行。
5.当使用客户端调用服务器的方法的时候,被调用的方法运行在服务器的Binder线程池中,同时客户端会被挂起,如果此时服务端方法执行耗时的话,就会导致客户端线程长时间阻塞,如果客户端线程是UI线程的话,就会导致客户端ANR,注意的是onServiceConnected(ComponentName name, IBinder service)
和onServiceDisconnected(ComponentName name)
都运行在UI线程,所以不能在这里调用服务端耗时的方法。同理,对于服务端调用客户端的方法的情况,比如服务端调用客户端的listener
中的方法的时候也是一样。即服务端挂起,方法运行在客户端的Binder线程池中。
6.当服务端因为某种异常原因停止,我们需要重新启动服务端,这里有两种方式,因为AIDL
的底层是Binder
,所以可以使用Binder
的linkToDeath
和unlinkToDeath
方法。还有一种方式是在onServiceDisconnected(ComponentName name)
重新绑定。这两个区别就是第二种方式可以访问UI,第一种不行,因为像之前说的,onServiceDisconnected(ComponentName name)
是运行在UI线程里的。而第一种方式使用的时候需要设置一个IBinder.DeathRecipient
接口用于接收服务端binder
因为特殊原因消失的通知,当收到通知的时候就会回调binderDied()
方法,我们在这里unlinkToDeath
并且重新绑定service。而这个binderDied()
方法是运行在客户端的Binder
线程池中的。
Messenger的原理及使用
Messenger大致的原理是这样的,因为Messenger的底层还是AIDL
,所以,原理和AIDL
差不多。
服务器:首先需要在服务器创建Binder对象,如何创建呢?通过Messenger
来创建,所以我们需要先构造Messenger
对象,对于Messenger
的构造方法有两种,如下:
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
所以我们需要先构造一个Handler,这个Handler的作用其实就是处理消息。然后我们再通过这个Handler
来构造Messenger
对象,这个Messenger对象其实就是将客户端发送来的消息传递给Handler来处理,然后我们需要得到Binder对象,通过在Service
的onBind
方法中return Messenger.getBinder()
,这样就得到了Binder
对象。
Binder驱动:跟AIDL
一样,还是Service
。
客户端:也是需要得到服务端的Binder
对象在binder驱动层对应的mRemote引用,获得的方式是将ServiceConnection
中的IBinder service
当做参数传入Messenger的构造函数中,如:
Messenger mService=new Messenger(service);
然后就可以用mService.send(msg)
给服务器发消息。实现跨进程通信。
因为这里是借助Messenger
,所以无法调用服务器端的方法,只能通过message来传递消息。而当服务器需要回应客户端的时候,就需要客户端提供一个Messenger
,然后服务器得到这个Messenger
,因为在就像客户端向服务端发送请求的时候,也是服务器提供一个Messenger
,然后客户端得到这个Messenger
。那么如何实现呢?因为客户端和服务器已经建立了连接,所以只需要在客户端发送消息的时候,通过消息的replyTo
参数向服务器传入一个Messenger
,然后服务器在接收到客户端的消息的时候得到通过message
的replyTo
参数得到这个Messenger
,然后利用这个向客户端发送消息就可以了。主要代码如下:
在客户端发送消息给服务器的时候:
message.replyTo=clientMessenger;
服务器接收消息的时候
Messenger clientMessenger=msg.replyTo;
这样就在服务器端得到了客户端的Messenger
,然后在服务器端通过clientMessenger.send(message);
就向客户端发送了消息。
重点
1.对于使用Messenger
而言,底层其实是AIDL
,但是没有AIDL
灵活,因为这是借助Messenger
来发送消息从而进行消息的传递,不能直接调用服务端的方法,而使用AIDL
是直接可以调用服务端的方法。
2.对于服务端的Messenger
的作用是将客户端传递的消息传递给Handler
来处理,而客户端的是发送消息给服务端。
3.Messenger
是以串行的方式处理客户端发来的消息,当消息多的时候就就不合适了。而AIDL
是可以并发处理客户端传来的消息。
网友评论