Android Binder实战开发指南之创建Java层Binder系统服务

                    Android Binder实战开发指南之创建Java层Binder系统服务

Android Binder实战开发指南目录:

Android Binder实战开发指南Native层获取Process进程名和调用者进程名

Android Binder实战开发指南之Java层直接调用Native Binder Service

Android Binder开发实战指南之创建Native Binder Service

Android Binder实战开发指南之Native层使用Java层Binder服务

Android Binder实战开发指南之创建Java层Binder系统服务

 

        通常我们在做Java层Binder服务开发的时,实现进程间通信用的最多的就是AIDL。当我们定义好xxxxx.aidl文件,在IDE工具的帮助下会自动生成代码帮我们实现IPC通信。借助 AIDL 编译以后的代码能帮助我们进一步理解 Binder IPC 的通信原理。但是IDE工具帮我们生成的代码从可读性和理解性来看,对开发者并不是非常友好。比如一个 IBinderFunManager.aidl 文件对应会生成一个 IBinderFunManager.java 文件,这个 java 文件包含了一个 IBinderFunManager接口、一个 Stub 静态的抽象类和一个 Proxy 静态类。Proxy 是 Stub 的静态内部类,Stub 又是 IBinderFunManager的静态内部类,这就造成了可读性和可理解性的问题。

Android为什么会这么设计呢?个人认为Android 之所以这样设计其实是有道理的,因为当有多个 AIDL 文件的时候把 IBinderFunManager、
Stub、Proxy 放在同一个文件里能有效避免 Stub 和 Proxy 重名的问题。

因此为了大家便于理解Binder和更加得深入了解aidl文件转化的一个过程,下面我们手动来编写代码实现跨进程调用。

 

Java层Binder服务名词解释

在正式编码实现跨进程调用之前,先介绍下实现过程中用到的一些类。了解了这些类的职责,有助于我们更好的理解和实现跨进程通信。

  • IBinder : IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个借口,这个对象就能跨进程传输。

  • IInterface : IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口)

  • Binder : Java 层的 Binder 类,代表的其实就是 Binder 本地对象。BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。

  • Stub : AIDL 的时候,编译工具会给我们生成一个名为 Stub 的静态内部类;这个类继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。

  • Proxy AIDL的时候,编译工具会给我们生成一个名为Proxy的静态内部类,它是本地服务的远程代理对象,它实现了 IInterface 接口,然后内部包含一个mRemote用来实现跨进程调用。

 

实现步骤详解

既然通过Binder实现跨进程调用,那么必然牵涉到两个进程,在这个列子中BinderFunManagerService作为远程服务端进程,提供服务,MainActivity作为客户端进程,使用 BinderFunManagerService提供的服务,具体如下图

定义接口

那么服务端进程具备什么样的能力?能为客户端提供什么样的服务呢?还记得我们前面介绍过的 IInterface 吗,它代表的就是服务端进程具体什么样的能力。因此我们需要定义一个 IBinderFunManager接口,IBinderFunManager继承自 IIterface,表明服务端具备什么样的能力。 

/*
 * 这个类用来定义服务端RemoteService具备什么样的能力
 */
public interface IBinderFunManager extends IInterface {
    void FUN(String str) throws RemoteException;
}

编写Stub

只定义服务端具备什么要的能力是不够的,既然是跨进程调用,那么接下来我们得实现一个跨进程调用对象 Stub。Stub 继承 Binder, 说明它是一个 Binder 本地对象;实现 IInterface 接口,表明具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要调用方自己实现。

package com.xxx.binderapifun.server;

import com.xxx.binderapifun.client.Proxy;

import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;

public abstract class Stub extends Binder implements IBinderFunManager {
    private static final String DESCRIPTOR = "com.xxx.binderapifun.server.BinderFunManager";

    public static IBinderFunManager asInterface(IBinder binder) {
        if (binder == null)
            return null;
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        if (iin != null && iin instanceof IBinderFunManager)
            return (IBinderFunManager) iin;
        return new Proxy(binder);
    }

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        // TODO Auto-generated method stub
        switch (code) {
        case INTERFACE_TRANSACTION:
            reply.writeString(DESCRIPTOR);
            return true;

        case TRANSAVTION_FUN:
            data.enforceInterface(DESCRIPTOR);
            String str = data.readString();
            this.FUN(str);
            reply.writeNoException();
            return true;

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

    public static final int TRANSAVTION_FUN = IBinder.FIRST_CALL_TRANSACTION;

    @Override
    public IBinder asBinder() {
        // TODO Auto-generated method stub
        return this;
    }

    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

}

Stub 类中我们重点介绍下 asInterface 和 onTransact

先说说 asInterface,当 Client 端在创建和服务端的连接,调用 bindService 时需要创建一个 ServiceConnection 对象作为入参。在 ServiceConnection 的回调方法 onServiceConnected 中 会通过这个 asInterface(IBinder binder) 拿到 IBinderFunManager对象,这个 IBinder 类型的入参 binder 是驱动传给我们的,正如你在代码中看到的一样,方法中会去调用 binder.queryLocalInterface() 去查找 Binder 本地对象,如果找到了就说明 Client 和 Server 在同一进程,那么这个 binder 本身就是 Binder 本地对象,可以直接使用。否则说明是 binder 是个远程对象,也就是 BinderProxy。因此需要我们创建一个代理对象 Proxy,通过这个代理对象来是实现远程访问。

 

编写Proxy

 接下来我们就要实现这个代理类 Proxy 了,既然是代理类自然需要实现 IBinderFunManager接口。

package com.xxx.binderapifun.client;

import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;

import com.xxx.binderapifun.server.IBinderFunManager;
import com.xxx.binderapifun.server.Stub;

public class Proxy implements IBinderFunManager {
    private static final String DESCRIPTOR = "com.xxx.binderapifun.server.BinderFunManager";

    private IBinder remote;

    public Proxy(IBinder remote) {

        this.remote = remote;
    }

    public String getInterfaceDescriptor() {
        return DESCRIPTOR;
    }

    @Override
    public void FUN(String str) throws RemoteException {
        // TODO Auto-generated method stub
        Parcel data = Parcel.obtain();
        Parcel replay = Parcel.obtain();
        try {
            data.writeInterfaceToken(DESCRIPTOR);
            data.writeString(str);
            remote.transact(Stub.TRANSAVTION_FUN, data, replay, 0);
            replay.readException();
        } finally {
            replay.recycle();
            data.recycle();
        }
    }

    @Override
    public IBinder asBinder() {
        // TODO Auto-generated method stub
        return remote;
    }

}

我们看看 FUN() 的实现;在 Stub 类中,FUN(String str) 是一个抽象方法,Client 端需要继承并实现它。

  • 如果 Client 和 Server 在同一个进程,那么直接就是调用这个方法。
  • 如果是远程调用,Client 想要调用 Server 的方法就需要通过 Binder 代理来完成,也就是上面的 Proxy。

在 Proxy 中的 FUN() 方法中首先通过 Parcel 将数据序列化,然后调用 remote.transact()。正如前文所述 Proxy 是在 Stub 的 asInterface 中创建,能走到创建 Proxy 这一步就说明 Proxy 构造函数的入参是 BinderProxy,即这里的 remote 是个 BinderProxy 对象。最终通过一系列的函数调用,Client 进程通过系统调用陷入内核态,Client 进程中执行 FUN() 的线程挂起等待返回;驱动完成一系列的操作之后唤醒 Server 进程,调用 Server 进程本地对象的 onTransact()。最终又走到了 Stub 中的 onTransact() 中,onTransact() 根据函数编号调用相关函数(在 Stub 类中为 IBinderFunManager接口中的每个函数中定义了一个编号,只不过上面的源码中我们简化掉了;在跨进程调用的时候,不会传递函数而是传递编号来指明要调用哪个函数);我们这个例子里面,调用了 Binder 本地对象的 FUN() 并将结果返回给驱动,驱动唤醒 Client 进程里刚刚挂起的线程并将结果返回。

 

添加系统服务

        if (ServiceManager.checkService("BinderFunManager") == null) {
            ServiceManager.addService("BinderFunManager",
                    new BinderFunManagerService());
            Log.d("TAG", "Add BinderFunManager");
        }

添加成功以后可以通过Android系统命令查看是否添加成功,查看结果如下

xxx:/ # service list  | grep BinderFunManager
0       BinderFunManager: [com.xxx.binderapifun.server.BinderFunManager]

使用系统服务

    private void binderFun() {
        IBinder binder = ServiceManager.getService("BinderFunManager");

        try {
            if (binder != null) {
                IBinderFunManager mBinderFunManager = Stub.asInterface(binder);
                mBinderFunManager.FUN("I am client from Java call");
            }

        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

 

这样一次跨进程调用就完成了。下面我们来运行一下看看实际效果

10-22 11:52:01.578 23625 23625 D BinderApiFun: Add BinderFunManager
10-22 11:52:03.852 23625 23625 D BinderApiFun: asInterface
10-22 11:52:03.852 23625 23625 D BinderApiFun: FUN : I am client from Java call

完整的代码我放到 csdn上了,有兴趣的小伙伴可以去看看。源码地址:

Binder实战大荟萃

最后建议大家在不借助 AIDL 的情况下手写实现 Client 和 Server 进程的通信,加深对 Binder 通信过程的理解。

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值