手撸一个 aidl

先看一下效果图:源码在这里
在这里插入图片描述

安卓中跨进程通信是通过Binder。而我们在代码中使用Binder的方式就是定义一个 aidl 文件编译后会自动生成相应的 java 文件。本篇抛开 aidl 文件,动手写一个可以实现跨进程通信的demo。在开始之前先看几个问题:

1、aidl接口文件是 java 用于跨进程通信的工具,那如果是在同一个进程中 aidl 还能用吗?

2、transact 和 onTransact 两个方法有什么关系?

3、Service中的 onBind 方法需要返回的 IBinder 对象和 ServiceConnection 的回调方法 onServiceConnected 中的 IBinder 有什么关系?

带着上面的问题下面开始码代码,其实整个自己写的过程就是将系统为我们的aidl文件编译生成的java文件的一个拆解过程。在 aidl 中我们都是创建一个以 .aidl 结尾的接口文件,现在我们直接自己定义一个普通的 java 接口文件。

/**
 * 相当于我门定义的aidl文件, 只不过这个文件是 .java 结尾的普通java文件
 */
public interface AddInterface extends IInterface {
	 //全类名
    String DESCRIBE = "com.mrh.aidl.AddInterface";

    int add(int a, int b);
}

这个 AddInterface 接口 与我们写 aidl 时最大的区别就是继承了 IInterface ,这事系统提供的接口,来看看IInterface:

public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder();
}

接口很简单,只是要我们返回一个 IBinder 对象即可,IBinder 代表着跨进程的 “能力”,实现了这个接口就表示具有了跨进程通信的资质。安卓系统为我们提供了一个 Binder 对象,看一下他的类结构:

public class Binder implements IBinder {
	//其他代码省略	
	final class BinderProxy implements IBinder {
	}
}

Binder 实现了 IBinder 接口。BinderProxy 是 Binder 的内部类同样也实现了 IBinder。这两个类之间有什么关系呢?此处先给出结论,下边通过实例再去验证:当我们使用aidl通信时,如果是跨进程通信实际上是传递的 BinderProxy(系统会转化Binder 与 BinderProxy)。如果是在同一个进程中通信传递的是Binder。这也就解释了第一个问题。

下边继续撸码。既然有了系统提供的 Binder 我们就可以跨进程通信了。但是到这里似乎我们开头写的 AddInterface 接口还没有用上,那么如何让一个对象既有跨进程的能力又能实现我们自己定义的功能呢?

public abstract class Stub extends Binder implements AddInterface {

}

我们定义一个叫 Stub 的抽象类继承 Binder,并且实现我们自己定义的 AddInterface 接口,这样一来 Stub 的对象就同时具有了跨进程和自定义功能的能力了。这样在 Service 中的 onBind 方法中我们就可以定义一个 Stub 类的对象并返回了。在 aidl 中会有一个系统为我们自动生成的方法:asInterface,那现在就模仿也写一个这样的方法:

public abstract class Stub extends Binder implements AddInterface {

	public static AddInterface asInterface(IBinder iBinder) {
        if (iBinder == null) {
            return null;
        }
        //当Client和Service处于同一进程的时候,此时的iBinder对象就是Service中创建的Stub对象
        if (iBinder != null
                && iBinder instanceof AddInterface) {
            return (AddInterface) iBinder;
        }
        // 当client 和 Service 处于不同的进程,iBinder的类型是 BinderProxy(Binder的内部类)
        //重要代码
        return new RemoteBinderProxy(iBinder);
    }
}

这个方法通常是在 ServiceConnection 类的 onServiceConnected(ComponentName name, IBinder iBinder) 回调方法中使用,当我们在 Service 的 onBind 方法中返回了 Stub 的对象后,如果Client 和 Service是在同一个进程,那么onServiceConnected回调方法携带的参数 iBinder 其实就是 Stub 对象本身。如果是跨进程那么此时的 iBinder 对象其实是BinderProxy 对象。这里其实又解释了一次第一个问题。

注意上边最后一行代码,当跨进程通信时,返回了一个 RemoteBinderProxy 对象。由于 asInterface 方法 最后的返回结果必须是 AddInterface 类型,但是当跨进程的时候, iBinder 其实是 BinderProxy 对象,与 AddInterface 接口没有一毛钱关系,多以这块有定义了一个 RemoteBinderProxy 类, 强行让他们产生关系。

public class RemoteBinderProxy implements AddInterface {
    /**
     * 远程Binder对象,Client 和 Service处于两个进程
     */
    private IBinder mIBinder;

    public RemoteBinderProxy(IBinder IBinder) {
        mIBinder = IBinder;
    }
}

RemoteBinderProxy 实现了 AddInterface 并且缓存了 iBinder(实际上就是 BinderProxy) 对象。这样在 asInterface 方法中就可以返回了。由于实现了 AddInterface 接口,所以我们需要实现 add 方法 以及 asBinder 方法。

public class RemoteBinderProxy implements AddInterface {

    /**
     * add 方法的编号
     */
    public static final int METHOD_ADD_CODE = 111;
    /**
     * 远程Binder对象,Client 和 Service处于两个进程
     */
    private IBinder mIBinder;

    public RemoteBinderProxy(IBinder IBinder) {
        mIBinder = IBinder;
    }

    @Override
    public int add(int a, int b) {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        int result = 0;
        try {
            data.writeInterfaceToken(Stub.DESCRIBE);
            data.writeInt(a);
            data.writeInt(b);
            //调用这个方法后,由于是跨进程的,当前进程会阻塞,等待reply的返回结果
            //所以 Binder 跨进程通信也是引起界面卡顿的一大原因
            mIBinder.transact(METHOD_ADD_CODE, data, reply, 0);
            reply.readException();
            result = reply.readInt();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public IBinder asBinder() {
        return mIBinder;
    }
}

当我们通过 asInterface 方法获取到 RemoteBinderProxy 对象后,我们就可以调用 add 方法进行跨进程通信了。而 add 方法的具体实现就是将要通信的参数序列化,然后调用 iBinder 的 transact 方法真正进行通信。当我们在 client 端调用了 transact 方法发起一次跨进程通信的请求后,Service 端需要作出相应,那么怎么响应呢?再重新回到 Stub 这个类,我们需要重写一下 Binder 类的 onTransact 方法:

public abstract class Stub extends Binder implements AddInterface {
    
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        if (code == RemoteBinderProxy.METHOD_ADD_CODE) {
            data.enforceInterface(AddInterface.DESCRIBE);
            int a = data.readInt();
            int b = data.readInt();
            int result = add(a, b);
            reply.writeNoException();
            reply.writeInt(result);
            return true;
        }
        return super.onTransact(code, data, reply, flags);
    }
}

同样是先通过 序列化的顺序将参数一一解出来,然后调用了 add 方法进行计算。此处需要注意我们的 Stub 类是抽象类,并没有实现 AddInterface 接口的 add 方法, 而是留给后续真正 new 对象的时候再去实现。最后通过 reply 将返回结果在返回去。这样一次跨进程通信就完成了。问题2和问题3应该也就清楚了。

最后再来总结一下。首先我们需要在 Service 中的 onBind 方法中返回一个 IBinder 对象,所以我们定义了一个 Stub 对象并实现了 add 方法。在 client 端,我们调用 bindService 方法绑定一个Service,并在ServiceConnection 的 onServiceConnected 方法中获取到了一个 IBinder 对象。此时如果 Service 和 client 是跨进程的,那么此时的 IBinder 对象就是 BinderProxy。如果是在一个进程,那么这个 IBinder 对象就是 Service 中定义的 Stub 对象。

最后再验证一个问题:当 aidl 用于同一个进程进行通信时,Service中的 onBind 方法需要返回的 IBinder 对象和 ServiceConnection 的回调方法 onServiceConnected 中的 IBinder 是一个对象:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值