美文网首页AndroidDevAndroidAndroid进阶之旅
超简单的Binder,AIDL和Messenger的原理及使用流

超简单的Binder,AIDL和Messenger的原理及使用流

作者: sakurajiang | 来源:发表于2016-12-06 15:58 被阅读1205次

    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方法来处理。这个方法有四个参数,分别是codedatareplyflags.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中的inoutinout这里就直接附上一篇别人写的博客,这篇博客讲的很详细,而且我也赞同他的观点,纸上得来终觉浅,绝知此事要躬行。

    5.当使用客户端调用服务器的方法的时候,被调用的方法运行在服务器的Binder线程池中,同时客户端会被挂起,如果此时服务端方法执行耗时的话,就会导致客户端线程长时间阻塞,如果客户端线程是UI线程的话,就会导致客户端ANR,注意的是onServiceConnected(ComponentName name, IBinder service)onServiceDisconnected(ComponentName name)都运行在UI线程,所以不能在这里调用服务端耗时的方法。同理,对于服务端调用客户端的方法的情况,比如服务端调用客户端的listener中的方法的时候也是一样。即服务端挂起,方法运行在客户端的Binder线程池中。

    6.当服务端因为某种异常原因停止,我们需要重新启动服务端,这里有两种方式,因为AIDL的底层是Binder,所以可以使用BinderlinkToDeathunlinkToDeath方法。还有一种方式是在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对象,通过在ServiceonBind方法中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,然后服务器在接收到客户端的消息的时候得到通过messagereplyTo参数得到这个Messenger,然后利用这个向客户端发送消息就可以了。主要代码如下:
    在客户端发送消息给服务器的时候:

    message.replyTo=clientMessenger;
    

    服务器接收消息的时候

     Messenger clientMessenger=msg.replyTo;
    

    这样就在服务器端得到了客户端的Messenger,然后在服务器端通过clientMessenger.send(message);就向客户端发送了消息。

    重点

    1.对于使用Messenger而言,底层其实是AIDL,但是没有AIDL灵活,因为这是借助Messenger来发送消息从而进行消息的传递,不能直接调用服务端的方法,而使用AIDL是直接可以调用服务端的方法。
    2.对于服务端的Messenger的作用是将客户端传递的消息传递给Handler来处理,而客户端的是发送消息给服务端。
    3.Messenger是以串行的方式处理客户端发来的消息,当消息多的时候就就不合适了。而AIDL是可以并发处理客户端传来的消息。

    相关文章

      网友评论

      • d3a056d4bd34:每次有新的client连接server,都会在server内部创建一个新的binder线程吗?
        d3a056d4bd34:@sakurajiang 好的谢谢!我再揣摩揣摩
        sakurajiang: @huiwang server端一般都会设置线程池,所以并不是每次都会新建线程,我的理解,水平还不够,怕误导你。

      本文标题:超简单的Binder,AIDL和Messenger的原理及使用流

      本文链接:https://www.haomeiwen.com/subject/aisrmttx.html