Android Binder 机制 实例分析

前言

这里将讲述Binder机制的框架以及它是如何进行IPC通信的。

一、Binder架构的组成

1.1.Binder 框架 有3个方面组成: Binder服务端、Binder驱动以及客户端组成。

Binder服务端:Binder 服务端实际上就是一个Binder 对象,该对象一旦创建就会开启一个隐藏的线程,该线程用来接收 Binder 驱动发送的消息,然后执行 onTransact函数,并根据 onTransact 的参数执行不同的服务代码;因此要实现一个 Binder 服务就得重载 onTransact 方法。
重载 onTransact 方法的主要内容就是onTransact 函数的参数转为服务函数的参数,而onTransact 的参数来自客户端 调用的 transact 方法;因此,如果 transact 参数确定了,那么 onTransact 的参数也就确定了。

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

客户端: 客户端想要访问远程服务,必须要获取远程服务在Binder 对象中对应的 mRemote 引用;怎么获取呢?下面介绍。获取到 mRemote 后就可以调用 transact 方法了,在 Binder 驱动中,也重载了transact 方法,重载的内容主要包括下面几项:
● 以线程间通信的模式,向服务端发送客户端传递过来的参数
● 挂起当前线程,当前线程就是客户端线程,并等待服务端执行完 指定的 服务函数后通知(notify)
● 接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区。

Binder的结构图如下:
在这里插入图片描述

二、如何设计Binder

2.1设计Binder 服务端

从代码角度来说很简单,就是继承自 Binder 然后重写 onTransact 方法,以下为IMediaPlayerService 的例子:

public class IMediaPlayerService extends Binder {
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        return super.onTransact(code, data, reply, flags);
    }

    public void start(String path){

    }

    public void stop(){

    }
}

当要启动该服务,只需要在Activity中 new 一个IMediaPlayerService 对象即可;
重写onTransact 方法 并从data中获取客户端传递过来的参数,比如start 方法中传递过来的 path ; 然而,这里有个问题,就是服务端如何确定客户端传递过来 path 在data 中的位置?因此,这里需要和客户端约定好。
这里假设客户端在传入包裹data中放入的第一个数据就是 path,那么 onTransact 的代码可以如下写:

protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    switch (code){
        case 1001:
            data.enforceInterface("IMediaPlayerService");
            String path = data.readString();
            start(path);
            reply.writeString("我是执行完返回的结果");
            break;
        case 1002:
            stop();
            break;
    }

    return super.onTransact(code, data, reply, flags);
}

onTransact 中的 code 表示 客户端希望调用服务端的哪个函数,所以,客户端和服务端要约定好 一组 int 值,不同的值表示想要调用不同的服务端函数;例如这里的1001表示start,1002表示要调用stop;

enforceInterface :是某种校验,和客户端的writeInterfaceToken 是对应的,等下做具体说明;

readString: 表示从 包裹 data中取出一个字符串path 供start调用;

如果想要返回客户端执行的结果就可以在 reply 中调用Parcel提供的 相关函数来写入相应的结果,比如上面的reply.writeString(“我是执行完返回的结果”);

2.2 Binder 客户端设计

想要使用服务端,就得获得Binder 驱动中对应的 mRemote 的引用;获取方法下面详解。然后调用 mRemote 的transact方法;transact 方法原型如下:

public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)throws RemoteException;

其中data 表示要传递给服务端的包裹(Parcel),远程服务端需要的数据都需要放入这个包裹中;包裹只支持原子类型:String ,int ,long 等,以及 实现Parcelable接口的对象;
客户端调用的代码可以写成类似下面这样的:

IBinder mRemote = null;
String path = "/sdcard/media/xxx.mp4";
int code = 1001;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("IMediaPlayerService"); //和服务端enforceInterfac一一对应
data.writeString(path);
mRemote.transact(code, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();

看到上面的代码感觉是不是很熟悉,是不是和使用aidl进行IPC中自动生成的代码很像,哈哈。

上面分析可知,data和reply不是new出来的,而是调用Parcel.obtain()申请的;就和邮局一样,你只能使用邮局使用的信封。其中 date和reply都是由客户端提供的,data 提供服务端需要的数据,reply 是给服务端将返回结果放入其中的;

writeInterfaceToken; 标注远程服务的名称,理论上不是必须的,因为客户端已经获取了远程服务的 mRemote 引用,那么就不会调用了其他的远程服务;该名称是Binder驱动确保客户端想调用的是指定的服务端。

writeString: 用于向包裹中写入一条String类型的数据,注意,包裹中添加的内容是有序的,这个顺序必须是客户端和服务端之前约定好的。在服务端的 onTransact方法中会按照指定的顺序取出数据。

最后调用 transact 方法;调用该方法后,客户端线程进入 Binder驱动,Binder驱动会挂起当前的线程,并向远程服务中发送一个消息,该消息包含客户端传进来的包裹,服务端拿到包裹后,进行数据解析,然后调用相应的服务函数,最后将返回结果写入reply中。然后向Binder驱动发送一个通知(notify)唤醒客户端线程,从而使得客户端线程从Binder驱动代码区返回到客户端代码区。

tansact 方法中最后一个参数 flag表示IPC的调用模式,0 表示服务端执行完后会返回执行结果,1 表示单向的,服务端不会返回执行结果;

注意:
上面设计的服务端和客户端存在2个问题:
1.客户端如何获取Binder 对象的引用
2.客户端和服务端必须事先约定好2件事情
✔ 服务端函数在包裹中的顺序
✔ 服务端不同函数的int型标识

如何获取Binder对象

使用过AIDL技术的同学应该都能想到,那就是使用Service; 调用bindService即可,bindService函数原型如下:

public boolean bindService(Intent service, ServiceConnection conn,int flags);

最关键的就是其中的ServiceConnection ,ServiceConnection 中包含这个函数:

void onServiceConnected(ComponentName name, IBinder service);

请注意onServiceConnected第二次参数 service,当客户端调用AMS启动某个Service后,如果Service正常启动,那么AMS就会调用ActivityThread中的ApplicationThread对象,调用参数中就包含Binder对象的引用,然后在 ApplicationThread 中会回调 bindService中的 conn接口。因此,客户端就可以在onServiceConnected 方法中将service 参数保存为一个全局变量,以供随时调用。这就解决了第一个问题,客户端如何获得Binder对象的引用。

保证包裹类的参数顺序

Android SDK中提供了aidl工具,该工具可以把一个aidl文件转换为一个java文件,在该Java类文件中,同时重载了 onTransact 和 transact方法,统一了存入包裹和读取包裹的参数。

Aidl工具不是必须的,有经验的程序员完全可以自己写出参数统一的包裹存入和包裹读出的代码。

下面我们看看aidl文件自动生成的java文件是什么样的?

我们先定义一个aidl文件,如下:

interface IBookManager {
    List<Book> getAllBooks();
    void addBook(in Book book);
}

注意,aidl文件只支持原子类型和实现了Parcelable接口的类。上面的Book类就实现了Parcelable。
对应的 Java文件如下:


```java
package com.example.za_zhujiangtao.zhupro;
// Declare any non-default types here with import statements

public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.za_zhujiangtao.zhupro.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.example.za_zhujiangtao.zhupro.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.example.za_zhujiangtao.zhupro.IBookManager interface,
 * generating a proxy if needed.
 */
public static com.example.za_zhujiangtao.zhupro.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.za_zhujiangtao.zhupro.IBookManager))) {
return ((com.example.za_zhujiangtao.zhupro.IBookManager)iin);
}
return new com.example.za_zhujiangtao.zhupro.IBookManager.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getAllBooks:
{
data.enforceInterface(descriptor);
java.util.List<com.example.za_zhujiangtao.zhupro.Book> _result = this.getAllBooks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(descriptor);
com.example.za_zhujiangtao.zhupro.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.za_zhujiangtao.zhupro.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.za_zhujiangtao.zhupro.IBookManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List<com.example.za_zhujiangtao.zhupro.Book> getAllBooks() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.za_zhujiangtao.zhupro.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getAllBooks, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.za_zhujiangtao.zhupro.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.example.za_zhujiangtao.zhupro.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getAllBooks = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
}
public java.util.List<com.example.za_zhujiangtao.zhupro.Book> getAllBooks() throws android.os.RemoteException;
public void addBook(com.example.za_zhujiangtao.zhupro.Book book) throws android.os.RemoteException;
}
```java

这些代码主要完成了一下的3个任务:

1. 定义了一个 interface IBookManager,内部包含aidl文件声明的所有方法,并且继承了IInterface,即该interface 的实现类需要提供一个asBinder()函数。

2. 定义一个Proxy类,该类实现了IBookManager,该类作为客户端访问服务端的代理,所谓代理就是为了前面提到的第二个问题—统一包裹的输入和读取参数。

3. 定义一个Sub类,他是一个抽象类,继承了Binder类且实现了IBookManager接口;之所以是抽象类是因为具体的服务函数需要程序员自己在Service类中实现。例如上面的onTransact方法中的 addBook方法最终调用的是程序员自己在Service类中实现的

private Binder mBinder = new IBookManager.Stub() {
    
    
    @Override
    public List<Book> getAllBooks() throws RemoteException {
    
    
        return mBookList;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
    
    
        mBookList.add(book);
    }

};

***这个就是我在Service 类中实现的。 ***

同时,在Sub类中重载了 onTransact方法,由于transact 方法内部给包裹类写入顺序是由aidl工具决定的,因此,在onTransact 方法中,aidl工具自然知道按照何种顺序从包裹中取出数据。

在Sub类中还定义了一些 int 型参数,如TRANSACTION_getAllBooks, TRANSACTION_addBook, 这些常量与服务函数对应,onTransact 和 transact方法的第一个参数就是code的值就来源于此。

在Sub类中还定义了一个方法,asInterface:提供这个函数的原因是服务端提供的服务除了其他进程可以调用之外,在本服务进程内部的其他类也可以调用,对于后者则不需要经过IPC调用,而直接在进程内部调用;Bindern内部有一个queryLocalInterface
的方法,该函数是通过输入字符串来判断来判断该Binder对象是不是本地Binder对象的引用。

总结下来说就是,当创建一个Binder对象时,服务端进程内部会创建一个Binder对象,Binder驱动中也会创建一个Binder对象。如果从远程获取服务端的Binder,则只会返回Binder驱动中的Binder对象;而如果从服务端进程内部获取Binder对象,则会返回服务端本身的Binder对象。如下图:
在这里插入图片描述
因此,asInterface 函数正是利用了queryLocalInterface 方法,提供了一个统一接口。无论是本地服客户端还是远程客户端,当获取了Binder对象后,都可以把该Binder对象作为asInterface的参数,来返回一个IBookManager接口。

猜你喜欢

转载自blog.csdn.net/zhujiangtaotaise/article/details/101678091