Android 核心理解 :Binder 和 Server

Android 核心理解 1 :Binder 和 Server


 Binder架构基础
 

Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块。如下图所示:

(参考《Android内核剖析》 柯元旦 著)



服务端一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程。该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行到Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务代码。

因此,要实现一个Binder服务,就必须重载onTransact()方法。

其实,重载onTransact()函数的主要内容是把onTransact()函数的参数转换为服务函数的参数,而onTransact()函数的参数来源是客户端调用transact()函数时输入的,因此,如果transact()有固定格式的输入,那么onTransact()就会有固定格式的输出。具体参照客户端transact(...)方法。


比如:基于Binder设计一个Server端

设计Service端很简单,从代码的角度来讲,只要基于Binder类新建一个Servier类即可。以下以设计一个MusicPlayerService类为例。假设该Service仅提供两个方法:start(String filePath)和stop(),那么该类的代码可以如下:

public class MusicPlayService extends Binder {

  @Override
  protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
      throws RemoteException {
    // TODO Auto-generated method stub
    return super.onTransact(code, data, reply, flags);
  }

  // other
  public void start(String filePath) {

  }

  public void stop() {

  }

}

    当要启动该服务时,只需要初始化一个MusicPlayerService对象即可。比如可以在主Activity里面初始化一个MusicPlayerService,然后运行,此时可以在ddms中发现多了一个线程。

 

Binder驱动任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。客户端要访问远程服务时,都是通过mRemote对象。

Binder驱动中,mRemote对象也重载了transact()方法,重载的内容:

  •   以线程间消息通信的模式,向服务端发送客户端传递过来的参数。
  •   挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知(notify)
  •   接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区。



客户端: 客户端要想访问远程服务,必须获取远程服务在Binder对象中对应的mRemote引用,获得该mRemote对象后,就可以调用其transact()方法。

对应用程序开发员来讲,客户端似乎是直接调用远程服务对应的Binder,而事实上则是通过Binder驱动进行了中转。即存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,所不同的是Binder驱动中的对象不会再额外产生一个线程。

其中transact(...)方法的函数原型如下:

   public final boolean transact (int code, Parcel data, Parcel reply,int flags)

其中data表示的是要传递给远程Binder服务的包裹(Parcel),远程服务函数所需要的参数必须放入这个包裹中。包裹中只能放入特定类型的变量,这些类型包括常用的原子类型,比如String、int、long等,要查看包裹可以放入的全部数据类型,可以参照Parcel类。除了一般的原子变量外,Parcel还提供了一个writeParcel()方法,可以在包裹中包含一个小包裹。因此,要进行Binder远程服务调用时,服务函数的参数要么是一个原子类,要么必须继承于Parcel类,否则,是不能传递的。


 // 对于MusicPlayerService的客户端而言,可以如下调用transact()方法。
    IBinder mRemote = null;
    String filePath = "/sdcard/music/hello_world.mp3";
    int code = 1000;
    Parcel data = Parcel.obtain();
    Parcel replay = Parcel.obtain();
    data.writeInterfaceToken("MusicPlayService");
    data.writeString(filePath);
    try {
      mRemote.transact(code, data, replay, 0);
      IBinder binder = replay.readStrongBinder();
    } catch (RemoteException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    replay.recycle();
    data.recycle();


首先,包裹不是客户端自己创建的,而是调用Parcel.obtain()申请的。

其中data和reply变量都由客户端提供,reply变量用户服务端把返回的结果放入其中。

writeInterfaceToken() 方法标注远程服务名称,理论上讲,这个名称不是必需的,因为客户端既然已经获取指定远程服务的Binder引用,那么就不会调用到其他远程服务。该名称将作为Binder驱动确保客户端的确想调用指定的服务端。

writeString()  方法用于向Parcel中添加一个String变量。注意,包裹中添加的内容是有序的,这个顺序必须是客户端和服务端事先约定好的,在服务端的onTransact() 方法中会按照约定的顺序取出变量。

接着调用transact()方法。调用该方法后,客户端线程进入Binder驱动,Binder驱动就会挂起当前线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹。服务端拿到包裹后,会对包裹进行拆解,然后执行指定的服务函数,执行完毕后,再把执行结果放入客户端提供的reply包裹中。然后服务端向Binder驱动发送一个notify的消息,从而使得客户端线程从Binder驱动代码区返回到客户端代码区。


transact() 的最后一个参数的含义是执行IPC调用的模式,分为两种:一种是双向,用常量0表示,其含义是服务端执行完指定服务后会返回一定的数据;另一种是单向,用常量1表示,其含义是不返回任何数据


最后,客户端就可以从reply中解析返回的数据了,同样,返回包裹中包含的数据也必须是有序的,而且这个顺序也必须是服务端和客户端事先约定好的。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值