深入理解AIDL

在上一篇文章中讲了如何使用AIDL来进行跨进程通信,还没有看过的可以先看看《Binder机制-AIDL的使用》。
继续使用上一篇文章中的示例,我们来看看aidl文件生成的java类。

package com.aidl.server;

public interface ILoginManager extends android.os.IInterface {

    public static abstract class Stub extends android.os.Binder implements com.aidl.server.ILoginManager {
        private static final java.lang.String DESCRIPTOR = "com.aidl.server.ILoginManager";

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

        public static com.aidl.server.ILoginManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.aidl.server.ILoginManager))) {
                return ((com.aidl.server.ILoginManager) iin);
            }
            return new com.aidl.server.ILoginManager.Stub.Proxy(obj);
        }
a
        @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 {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_login: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    com.aidl.server.LoginCallback _arg2;
                    _arg2 = com.aidl.server.LoginCallback.Stub.asInterface(data.readStrongBinder());
                    this.login(_arg0, _arg1, _arg2);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.aidl.server.ILoginManager {
            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 void login(java.lang.String name, java.lang.String pwd, com.aidl.server.LoginCallback callback) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(name);
                    _data.writeString(pwd);
                    _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public void login(java.lang.String name, java.lang.String pwd, com.aidl.server.LoginCallback callback) throws android.os.RemoteException;
}

该类的大致有如下几个特点:

  • 实际上是一个接口,继承了android.os.IInterface。接口中的方法实际上就是我们在aidl文件中定义的方法。
  • 在接口的内部有一个Stub的抽象类,该Stub继承Binder类同时实现了我们生成的接口。
  • 在Stub类的内部有3个固定的方法,asInterface(android.os.IBinder)asBinder()onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
  • 在Stub类的内部还有一个Proxy代理类,该代理类也实现了我们生成的接口。

我们先看看Stub的构造方法做了什么

//Stub
public Stub() {
    this.attachInterface(this, DESCRIPTOR);
}
//Binder
public void attachInterface(IInterface owner, String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}

在Stub的构造方法中,调用了父类的attachInterface()方法,在该方法内部,实际上就保存了stub对象本身和一个DESCRIPTOR描述符。

我们在client绑定远程服务后,会调用Stub.asInterface(IBinder obj)来将一个IBinder对象转为自动生成的接口对象。

public static com.aidl.server.ILoginManager asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.aidl.server.ILoginManager))) {
        return ((com.aidl.server.ILoginManager) iin);
    }
    return new com.aidl.server.ILoginManager.Stub.Proxy(obj);
}

在该方法中会首先调用queryLocalInterface来查询本地进程是否存在一个binder对象,如果存在就返回,不存在就返回一个proxy对象。实际上,如果client和server在同一个进程,那么就直接返回了该stub对象了,我们在server返回的IBinder对象就是一个Stub对象;如果是在不同的进程,那么返回一个proxy对象,在该proxy对象中会持有这个IBinder对象。

其实如果是跨进程,在我们bindService成功后返回的IBinder实际上是一个BinderProxy对象,BinderProxy是Binder的内部类,它也实现了IBinder接口,所以我们在调用asInterface()的时候,在其内部obj.queryLocalInterface(DESCRIPTOR)实际返回null。

final class BinderProxy implements IBinder {
    // Assume the process-wide default value when created
    volatile boolean mWarnOnBlocking = Binder.sWarnOnBlocking;

    public native boolean pingBinder();
    public native boolean isBinderAlive();

    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }
    ...
}

可以看到在BinderProxy中,queryLocalInterface()返回的就是null。所以通过asInterface()后我们拿到了一个Stub$Proxy对象。

这里又出现了一个BinderProxy类,从这里我们能够看到Binder的一些身影。实际上android中的跨进程通信在很多地方都是通过Binder驱动来实现的,Binder工作在内核区,在client进程中,Binder驱动返回给我们的并不是一个Binder对象,而是一个BinderProxy对象,我们并不能真正的拿到server端的binder对象,至于Binder是怎么工作的,因为比较复杂,而且涉及到framework的native层,binder是用c和c++实现的,所以这里就先不讲Binder的实现机制了。

继续上面的话题,在client端我们拿到的是一个Stub$Proxy对象,我们来看看Proxy类中的内容:

@Override
public void login(java.lang.String name, java.lang.String pwd, com.aidl.server.LoginCallback callback) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeString(name);
        _data.writeString(pwd);
        _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null)));
        mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
        _reply.readException();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

Proxy类同样实现了自动生成的接口。在实现的方法中,会先创建一个_data_reply对象。这两个对象都是Parcel类型的,一个用来传递跨进程中的方法参数,一个用来接收方法的返回值。

  • 首先:_data对象写入了一个Token值,就是自动生成的DESCRIPTOR
  • 接着:写入了我们的方法中的参数
  • 再接着:调用mRemote(IBinder)的transact()方法
  • 最后:通过reply获取异常,如果有返回值还会获取返回值。

在这里我们先写入了一个DESCRIPTOR标识符,它是IDE自动生成的,默认就是生成的接口的全名,使用该DESCRIPTOR会和server端的IBinder对象进行匹配,如果匹配不到我们后续的操作就无法进行,所以要求我们在client端的aidl文件的路径要和server端一致就是这个原因。

可以看到proxy也只是组装了数据而已,它实际上调用的还是IBinder对象的方法,此处的IBinder对象是一个BinderProxy类型的。transact的具体实现是在framework的native中,是用c++实现的,代码在frameworks\native\libs\binder\IPCThreadState.cpp中,其中也有一个transact()方法。

通过在proxy中调用BinderProxy的transact()方法,最终Binder驱动会回调到Stub类中的onTransact()方法。我们可以发现transact()方法和onTransact()是一一对应的。在transact()有固定的4个参数:

  • 第一个参数:方法的ID标识,通过该标识能够知道client端需要调用server端的哪个方法,它是自动生成的,static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);一般我们不用关心它,server端和client端针对同一个方法生成的ID都是一样的,格式通常都是TRANSACTION_+方法名
  • 第二个参数:是一个Parcel对象,用于跨进程传递方法中的参数,它相当于一个容器,它所传递的参数必须是可序列化的。
  • 第三个参数:同第二个参数一样,它用来接收server端的返回值。
  • 第四个参数:RPC的通信模式,默认都是0,表示可以双向传输,_reply可以返回数据;如果是1那么将收不到返回数据,_reply中将没有数据。

接着我们看onTransact()的实现,它会被Binder驱动回调:

public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    switch (code) {
        case INTERFACE_TRANSACTION: {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_login: {
            data.enforceInterface(DESCRIPTOR);
            java.lang.String _arg0;
            _arg0 = data.readString();
            java.lang.String _arg1;
            _arg1 = data.readString();
            com.aidl.server.LoginCallback _arg2;
            _arg2 = com.aidl.server.LoginCallback.Stub.asInterface(data.readStrongBinder());
            this.login(_arg0, _arg1, _arg2);
            reply.writeNoException();
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}

在switch中通过code来判断执行响应的代码,最终都会在server端执行我们在aidl中声明的方法。在这里我们可以看到从data中读取我们在client中传递过来的参数,然后调用具体的aidl中声明的方法,然后在reply中写入结果并返回。

在上一篇中我们提到了定向tag这个东西,使用不同的tag(int、out、inout),我们生成的代码会有一些不同,这里我们从源码来看看它们的不同点。
这里我们重新定义一个aidl问价,内容如下所示:

// ITest.aidl
package com.aidl.server;
import java.util.List;
interface ITest {
    String getInfoIn(in List info);
    String getInfoOut(out List info);
    String getInfoInOut(inout List info);
}

生成的ITest.java如下

package com.aidl.server;

public interface ITest extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.aidl.server.ITest {
        private static final java.lang.String DESCRIPTOR = "com.aidl.server.ITest";

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

        public static com.aidl.server.ITest asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.aidl.server.ITest))) {
                return ((com.aidl.server.ITest) iin);
            }
            return new com.aidl.server.ITest.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 {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getInfoIn: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List _arg0;
                    _arg0 = data.createStringArrayList();
                    java.lang.String _result = this.getInfoIn(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
                case TRANSACTION_getInfoOut: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List _arg0;
                    _arg0 = new java.util.ArrayList();
                    java.lang.String _result = this.getInfoOut(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    reply.writeStringList(_arg0);
                    return true;
                }
                case TRANSACTION_getInfoInOut: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List _arg0;
                    _arg0 = data.createStringArrayList();
                    java.lang.String _result = this.getInfoInOut(_arg0);
                    reply.writeNoException();
                    reply.writeString(_result);
                    reply.writeStringList(_arg0);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.aidl.server.ITest {
            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.lang.String getInfoIn(java.util.List info) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeStringList(info);
                    mRemote.transact(Stub.TRANSACTION_getInfoIn, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public java.lang.String getInfoOut(java.util.List info) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getInfoOut, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                    _reply.readStringList(info);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public java.lang.String getInfoInOut(java.util.List info) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeStringList(info);
                    mRemote.transact(Stub.TRANSACTION_getInfoInOut, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                    _reply.readStringList(info);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_getInfoIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getInfoOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_getInfoInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }

    public java.lang.String getInfoIn(java.util.List info) throws android.os.RemoteException;

    public java.lang.String getInfoOut(java.util.List info) throws android.os.RemoteException;

    public java.lang.String getInfoInOut(java.util.List info) throws android.os.RemoteException;
}

有了之前的分析,这里再看这些生成的代码就很简单了,我们还是从Proxy这个类说起。

先看getInfoIn()

public java.lang.String getInfoIn(java.util.List info) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.lang.String _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeStringList(info);
        mRemote.transact(Stub.TRANSACTION_getInfoIn, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readString();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

老样子,先通过_data写入方法参数,然后调用IBinder.transact()方法,这个时候client调用该方法的线程会挂起,直到有结果返回,然后从_reply获取返回值。这里需要注意一点:在通过_data写入参数的时候,直接就将方法中的参数写入到了_data中了。

与之对应onTransact()部分:

case TRANSACTION_getInfoIn: {
    data.enforceInterface(DESCRIPTOR);
    java.util.List _arg0;
    _arg0 = data.createStringArrayList();
    java.lang.String _result = this.getInfoIn(_arg0);
    reply.writeNoException();
    reply.writeString(_result);
    return true;
}

从data中读取方法参数,然后调用getInfoIn()方法,最后通过reply返回结果。

再看getInfoOut()

public java.lang.String getInfoInOut(java.util.List info) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.lang.String _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeStringList(info);
        mRemote.transact(Stub.TRANSACTION_getInfoInOut, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readString();
        _reply.readStringList(info);
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

这里可以看到和getInfoIn()有一点区别,就是在获取返回值的时候,如果定向tag是out的话,我们还会通过_reply向我们的方法参数对象上写入一些返回数据。

再来看看与之对应的onTransact()部分:

case TRANSACTION_getInfoOut: {
    data.enforceInterface(DESCRIPTOR);
    java.util.List _arg0;
    _arg0 = new java.util.ArrayList();
    java.lang.String _result = this.getInfoOut(_arg0);
    reply.writeNoException();
    reply.writeString(_result);
    reply.writeStringList(_arg0);
    return true;
}

这里就和定向tag为in的时候大不一样了,这里的方法参数是server端直接new出来的一个空对象,并没有使用client传过来的参数,在server端处理完后会将new出来的这个参数通过reply返回给client。

这就和我们在上一篇说的一样,重复一遍这个结论:在使用out定向tag的时候,我们在实现server端的接口方法中,方法参数中的数据总是空的,原因就在这里,server端并没有使用client传过来的数据,而是自己new的一个新对象,里面没有数据。在处理返回数据时server会将这个自己创建的参数给返回。

最后再来看看getInfoInout()方法:

public java.lang.String getInfoInOut(java.util.List info) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.lang.String _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeStringList(info);
        mRemote.transact(Stub.TRANSACTION_getInfoInOut, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readString();
        //此处会更新方法参数
        _reply.readStringList(info);
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

case TRANSACTION_getInfoInOut: {
    data.enforceInterface(DESCRIPTOR);
    java.util.List _arg0;
    //使用的是client传递过来的参数对象
    _arg0 = data.createStringArrayList();
    java.lang.String _result = this.getInfoInOut(_arg0);
    reply.writeNoException();
    reply.writeString(_result);
    //将方法参数写回client端
    reply.writeStringList(_arg0);
    return true;
}

可以看到inout方式就是in和out的结合,在client端也会从reply中读取数据写入方法参数对象中,在server端用的是client传递过来的参数对象,同时也会将该参数对象写回client端。

对in、out、inout做一个总结:

  • in:在跨进程中,client中传递的参数会通过data传递到server端,server端会直接使用这个参数来进行处理。在server通过reply返回数据时,不会将这个参数写入到reply中。因此这里可以看做是数据是单向的从client到server端传递。
  • out:在跨进程中,client端也会将参数通过data传递到server端,不过在server端不会使用这个传递过来的参数,而是自己创建了一个新的参数对象来使用,所以这个参数对象里面的数据总是空的,在reply结果的时候,server端会将这个新创建的参数对象返回给client的。因此可以看做是数据单向的从server端传递到client端。
  • inout:inout就是in和out的结合,client端传递到server端的方法参数,server端会拿来直接使用,同时server在通过reply返回数据的时候,也会将传递过来的方法参数给写回去。因此可以看做是数据从client端到server端,然后从server端到client端是双向的。

你可能感兴趣的:(android)