IPC机制:AIDL调用流程分析和常见问题处理

当发起一次AIDL调用时,是如何进行进程间切换的?都经过了哪些步骤?有哪些重要方法?
下面我们用一个例子来具体看一下


这里假定我们声明了一个aidl方法,如下:

interface IMyAidlInterface {
    WeatherEntity queryWeather(in RequestData request, in ICallBack callback);
}

下面看一下,一次完整的IPC过程是怎样的,中间都发生了哪些事情

第一步:绑定服务,获取服务端binder引用

Client绑定了service后,首先会拿到server的一个Binder引用:

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 我们这里拿到的对象其实就是其Stub的内部类Proxy对象
        myAidlInterface = IMyAidlInterface.Stub.asInterface(service);

上面这步转化,会根据当前client和server是否为同一个进程,如果是不同进程,则返回其内部Proxy代理对象


第二步:客户端发起发起调用

result = myAidlInterface.queryWeather(request, callback);

调用目标方法queryWeather()之后,会按照上面所述走到Proxy.queryWeather()中,逻辑如下:
IMyAidlInterface.Stub.Proxy.queryWeather():

@Override 
public com.example.myapplication.WeatherEntity queryWeather(com.example.myapplication.RequestData request, 
com.example.myapplication.ICallback callback) throws android.os.RemoteException {
    // 获取两个新的Parcel对象,_data是入参,_reply是返回值
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();

    com.example.myapplication.WeatherEntity _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        if ((request!=null)) {
            _data.writeInt(1);
            // 序列化参数1 RequestData进_data中
            request.writeToParcel(_data, 0);
        } else {
            _data.writeInt(0);
        }

        // 序列化参数2 Callback进_data中
        _data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));

        // 发起远程调用,remote其实就是我们在onServiceConnected中,获取到的IBinder对象,
        // 也就是server端的Binder对象,也就是说在这里将请求转交给了server去处理
        mRemote.transact(Stub.TRANSACTION_queryWeather, _data, _reply, 0);

        _reply.readException();
        // 这里readInt判断是否为0,确认请求是否成功,因为在server端,会判断返回值result是否为null,
        // 如果为null则writeInt(0),否则写1
        if ((0!=_reply.readInt())) {
            _result = com.example.myapplication.WeatherEntity.CREATOR.createFromParcel(_reply);
        } else {
            _result = null;
        }
        
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}
  1. 首先初始化两个parcel:传入参数_data和返回参数_reply,
    将方法中所有的参数都写入_data中,普通对象调用的是它的writeToParcel方法,
    如果参数是一个aidl接口即继承了IInterface接口,则调用的是Parcel.writeStrongBinder方法。

  2. 将所有参数都写入后,就开始调用mRemote.transact()方法,这里的mRemote其实就是我们在onServiceConnected中, 获取到的IBinder对象,即在server端的Service.onBind方法中返回的Binder对象。在这一步将请求转交给了server去处理


第三步:Binder.transact()

下面看下Binder的transact(),这个方法携带code(也就是目标方法标示),入参data和返回值reply:

/**
 * Default implementation rewinds the parcels and calls onTransact.  On
 * the remote side, transact calls into the binder to do the IPC.
 */
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
        int flags) throws RemoteException {
    if (false) Log.v("Binder", "Transact: " + code + " to " + this);

    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

这里做了两件事:
在调用onTransact()发起调用前后,对参数data和返回值reply重置position,然后调用onTransact(),这下就到了我们服务端中.aidl自己实现的onTransact()方法了。
这个方法如果返回false,表示服务端没有目标方法,原因下面会分析


第四步:服务端onTransact()

服务端的onTransact()方法逻辑如下:

// 根据传入的code参数确定需要调用哪个方法
case TRANSACTION_queryWeather: {
    data.enforceInterface(descriptor);
    com.example.myapplication.RequestData _arg0;
    if ((0!=data.readInt())) {
        // 反序列化参数1 RequestData
        _arg0 = com.example.myapplication.RequestData.CREATOR.createFromParcel(data);
    } else {
        _arg0 = null;
    }

    com.example.myapplication.ICallback _arg1;
    // 反序列化参数2 Callback
    _arg1 = com.example.myapplication.ICallback.Stub.asInterface(data.readStrongBinder());
    
    // 然后调用我们在服务端service中实现的目标方法,得到result
    com.example.myapplication.WeatherEntity _result = this.queryWeather(_arg0, _arg1);
    
    reply.writeNoException();
    if ((_result!=null)) {
        reply.writeInt(1);
        // 将result序列化进reply中
        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
    } else {
        reply.writeInt(0);
    }
    return true;
}

在服务端的onTransact()方法中,会根据传入的code参数调用对应方法,如果没有code相对应,则调用父类也就是Binder.onTransact()方法。
Binder.onTransact()中,判断code如果为预置的几种,返回true,否则返回false,那我们传入的code肯定不是预置的,所以这时候会返回false。
这时,可以明确一点:
如果在调用时,mRemote.transact()返回false,则表示服务端没有目标方法。

第五步,service中IMyAidlInterface.Stub子类实现的各个方法

经过上面的调用,逻辑就已经被我们在服务端service处理完了,拿到结果reply后,通过Binder将其返回给client端。到这里为止,一次IPC调用就结束了。


上面的流程可以用下图来说明:

IPC机制:AIDL调用流程分析和常见问题处理_第1张图片
Binder流程.png


IPC调用时,序列化和反序列化不匹配的问题

这里有一个问题需要注意:从上面的过程可以看出,在一次IPC调用中,一个方法的所有参数,都是写在同一个Parcel中的, 这就要求我们必须同步更新client和server端的序列化字段,保证二者一致,否则就会出现因为序列化和反序列化不匹配,导致空指针等问题。

为了理解上面的问题,我们需要对Parcel有一个简单的认识。

Parcel可以用于跨进程传输数据,它提供了一套机制,可以将序列化后的数据写入一个共享内存,其他进程通过Parcel从共享内存中读出字节流并反序列化成对象。
Parcel是一块连续的内存,并且会根据需要自动扩展其大小。也就是说写入数据时,如果系统发现已经超出了Parcel的存储空间,它会自动申请所需内存并扩展dataCapacity。
在我们向parcel里读写值的时候,parcel内部会维护一个dataPosition,类似于数组的索引,
序列化/反序列化都是严格按照这个position来执行的,我们可以通过setDataPosition()方法来设置该值。

例如上面的例子,我们有两个参数request和callback,假设client的request多了一个字段test, 这两个参数按照定义顺序依次写入parcel中,那在server反序列化时,原本position应该是callback的, 现在变成了新增字段test,这就会导致callback无法正常序列化而变成null,影响正常功能。

如何规避因为序列化和反序列化不匹配导致的问题

  1. 在序列化时,可以通过手动setDataPosition(),通过控制position 将新增字段写进固定的没有使用过的位置,从而使其不影响反序列化
  2. 在写aidl接口时,如果无法做到同时更新client和server,那可以考虑将常改变的参数,例如request定义在后面,例如test(callback, request),从而避免因为request的改变影响callback的序列化。

在AIDL调用中,如何判断server端的目标方法是否存在?

通过上面的分析可以明确,当客户端在调用时,mRemote.transact()返回false,则表示服务端没有目标方法

在系统根据aidl自动生成的java类中,该方法的返回值是被忽略的,如果要处理这种情况,可以选择自己实现该类,判断如果返回值为false,就抛出异常,然后在方法调用处去处理该异常。

修改后的代码如下:

boolean res = mRemote.transact(Stub.TRANSACTION_queryNumberInfoAsync, _data, _reply, 0);
// If mRemote.transact() return false indicates that the method we call is not exist
// in server, so we need to throw an exception to notify the caller.
if (!res) {
    throw new RemoteNoSuchMethodException("The method you are calling queryWeather() is not exist in server!");
}

这里的异常是继承自RuntimeException的自定义异常


上面是对Binder流程的简单分析,如果对AIDL原理不熟悉可以参考我的这篇博客
https://www.jianshu.com/p/a9bf2e513751

你可能感兴趣的:(IPC机制:AIDL调用流程分析和常见问题处理)