Android知识

android进阶-AIDL之修饰符in,out,inout

2020-10-12  本文已影响0人  return_toLife

系列文章

AIDL的基本使用
AIDL之自定义数据类型
AIDL之修饰符in,out,inout
AIDL之重连方法
AIDL之接口注册/解注册
AIDL之连接池

知识点

  1. 自定义修饰符的作用以及实现原理
  2. 修饰符作用条件

一、修饰符的作用

在前面AIDL中使用自定义类型数据中,我们要给自定义的类型数据添加一个修饰符,官方理由介绍如下:

所有非原语参数均需要指示数据走向的方向标记。这类标记可以是 in、out 或 inout(见下方示例)。
原语默认为 in,不能是其他方向。
注意:您应将方向限定为真正需要的方向,因为编组参数的开销较大。

而三个修饰符的作用结合网上和个人理解,它们就是负责定义数据在c/s模式中的一种传递规则,规则分别如下:
in: 表示数据只能由客户端流向服务端
out: 表示数据只能由服务端流向客户端
inout: 表示数据能在客户端和服务端双向流通

二、修饰符代码实践

沿用前面的AIDLBook对象,在客户端传递一个book对象到服务端,服务端修改这个book对象的name,最后客户端打印结果,这里我就只贴关键代码

in:

//客户端代码
 AIDLBook inBook=new AIDLBook("book",455);
                    try {
                        iPerson.addBookWithIn(inBook);
                        LogUtil.d("client addBookWithIn="+inBook);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
...
...
//服务端代码
       @Override
        public void addBookWithIn(AIDLBook book) throws RemoteException {
            LogUtil.d("service before addBookWithIn="+book);
            book.name="in tag";
            LogUtil.d("service after addBookWithIn="+book);
        }
...
...

//打印信息如下:
service before addBookWithIn=AIDLBook{name='book', id=455}
service after addBookWithIn=AIDLBook{name='in tag', id=455}
client addBookWithIn=AIDLBook{name='book', id=455}

out:

//客户端代码
  AIDLBook outBook=new AIDLBook("book",455);
                    try {
                        iPerson.addBookWithOut(outBook);
                        LogUtil.d("client addBookWithOut="+outBook);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
...
...
//服务端代码
     @Override
        public void addBookWithOut(AIDLBook book) throws RemoteException {
            LogUtil.d("service before addBookWithOut="+book);
            book.name="out tag";
            LogUtil.d("service after addBookWithOut="+book);
        }
...
...

//打印信息如下:
service before addBookWithOut=AIDLBook{name='null', id=0}
service after addBookWithIn=AIDLBook{name='in tag', id=0}
client addBookWithOut=AIDLBook{name='out tag', id=0}

inout:

//客户端代码
 AIDLBook inOutBook=new AIDLBook("book",455);
                    try {
                        iPerson.addBookWithInOut(inOutBook);
                        LogUtil.d("client addBookWithInOut="+inOutBook);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
...
...
//服务端代码
  @Override
        public void addBookWithInOut(AIDLBook book) throws RemoteException {
            LogUtil.d("service before addBookWithInOut="+book);
            book.name="inout tag";
            LogUtil.d("service after addBookWithInOut="+book);
        }
...
...

//打印信息如下:
service before addBookWithInOut=AIDLBook{name='book', id=455}
service after addBookWithInOut=AIDLBook{name='inout tag', id=455}
client addBookWithInOut=AIDLBook{name='inout tag', id=455}

根据上面的结果可以看出,根据修饰符的类型,数据的变化是会有所有不同,
in修饰的数据,客户端传递给服务端之后,服务端修改不会影响客户端
out修饰的数据,客户端传递给服务端之后,服务端得到的是一个空数据,而对数据修改后,会影响到客户端
inout修饰的数据,客户端传递给服务端之后,服务端能接收到完成的数据,并在修改后作用到客户端

三、修饰符作用原理

找到生成的AIDL文件,也就是IPerson编译后自动生成的文件,主要看里面的onTransact代理类的方法
先说一下onTransact是服务端收到客户端传递的操作
代理类就是客户端获取带远程Binder的时候调用他方法进行数据传递

 //IPerson.Stub()
       @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case TRANSACTION_addBookWithIn: {
                    data.enforceInterface(DESCRIPTOR);
                    com.returntolife.jjcode.mydemolist.bean.AIDLBook _arg0; 
                    //_arg0 就是客户端传递的数据,这里会从_data中获取客户端传递的数据,前提是客户端有传递数据过来
                    if ((0 != data.readInt())) {
                        _arg0 = com.returntolife.jjcode.mydemolist.bean.AIDLBook.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBookWithIn(_arg0);
                    reply.writeNoException();
                    //可以发现,这里执行了对应的方法之后,没有重新将_arg0数据写入到reply中,所以服务端的修改不会影响客户端
                    return true;
                }
                case TRANSACTION_addBookWithOut: {
                    data.enforceInterface(DESCRIPTOR);
                    com.returntolife.jjcode.mydemolist.bean.AIDLBook _arg0;
                     //_arg0 就是传递的数据,但是是重新new了一个空对象,所以服务端接收的数据对象内部都是初始数据,客户端的数据并不会传递过来
                    _arg0 = new com.returntolife.jjcode.mydemolist.bean.AIDLBook();
                    this.addBookWithOut(_arg0);
                    reply.writeNoException();
                   //可以发现,这里执行了对应的方法之后,会重新将_arg0数据写入到reply中,所以客户端接收的数据是服务端修改后的数据
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                case TRANSACTION_addBookWithInOut: {
                    data.enforceInterface(DESCRIPTOR);
                    com.returntolife.jjcode.mydemolist.bean.AIDLBook _arg0;
                  //_arg0 就是客户端传递的数据,这里会从_data中获取客户端传递的数据,前提是客户端有传递数据过来
                    if ((0 != data.readInt())) {
                        _arg0 = com.returntolife.jjcode.mydemolist.bean.AIDLBook.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBookWithInOut(_arg0);
                    reply.writeNoException();
                      //可以发现,这里执行了对应的方法之后,会重新将_arg0数据写入到reply中,所以客户端接收的数据是服务端修改后的数据
                    if ((_arg0 != null)) {
                        reply.writeInt(1);
                        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
          
            return super.onTransact(code, data, reply, flags);
        }


        private static class Proxy implements com.returntolife.jjcode.mydemolist.IPerson {
         ...
         ...
            @Override
            public void addBookWithIn(com.returntolife.jjcode.mydemolist.bean.AIDLBook book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                //注意这里,将传递的book数据写入到_data
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                   //传递_data数据到远程服务端
                    mRemote.transact(Stub.TRANSACTION_addBookWithIn, _data, _reply, 0);
                    _reply.readException();
                  //对比其他可以发现,这里并没有从_reply中重新获取book数据信息
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void addBookWithOut(com.returntolife.jjcode.mydemolist.bean.AIDLBook book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                   //注意这里,并没有将传递的book数据写入到_data
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                      //传递_data数据到远程服务端
                    mRemote.transact(Stub.TRANSACTION_addBookWithOut, _data, _reply, 0);
                    _reply.readException();
                    //这里从_reply中重新获取book数据信息
                    if ((0 != _reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void addBookWithInOut(com.returntolife.jjcode.mydemolist.bean.AIDLBook book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                 //将传递的book数据写入到_data
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                      //传递_data数据到远程服务端
                    mRemote.transact(Stub.TRANSACTION_addBookWithInOut, _data, _reply, 0);
                    _reply.readException();

                    //这里从_reply中重新获取book数据信息
                    if ((0 != _reply.readInt())) {
                        book.readFromParcel(_reply);
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

根据三种类型的代码,不难发现其中的不同点,主要区别就是不同的修饰符执行的读写方式有所不同

四、修饰符作用条件

如果将service的progress去掉,也就是不开启多进程,上面的执行结果会不同,客户端和服务端会共享一个book对象的引用。原因是IBinder的实现不同,那么具体是在哪里实现的,结合实际代码分析一下:
首先在服务连接成功的时候,为了获取到iperson实现类,我们会调用一个asInterface的方法

           @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                LogUtil.d("onServiceConnected");
                iPerson = IPerson.Stub.asInterface(service);
            }

这个方法是AIDL生成的,点进去代码如下

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

可以看出queryLocalInterface是核心方法,查询本地是否有对应的实现类,有则返回,无则创建Proxy远程代理类
那么obj具体是什么?经过个人测试,在开启多进程的时候obj返回的是BinderProxy,不开启的时候返回的是Binder对象
他们的实现分别如下:

//Binder.class
 public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

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

(ps:BinderProxy可能某些原因,本地会找不到这个类,这里给出一个在线查看源码地址BinderProxy(可能需要科学上网)
根据源码可以看到,BinderProxy必然会返回null,然后就会创建IPerson.Stub.Proxy并返回,而Binder会根据mDescriptor对比是否本地已有对应的服务信息,mDescriptor在AIDL生成IPerson的时候会自动生成,实际就是一个路径字符串用于做标志

public interface IPerson extends android.os.IInterface {

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder
        implements com.returntolife.jjcode.mydemolist.IPerson {

        private static final java.lang.String DESCRIPTOR = "com.returntolife.jjcode.mydemolist.IPerson";
  ...
  ...

到这其实就可以看出,修饰符起作用是在BinderProxy的时候也就是返回IPerson.Stub.Proxy(根据上面分析修饰符可以知道Proxy里面的方法会根据修饰符不同而实现不同的读写方式),而Binder查询本地实现之后就会返回,返回的就算自身的一个具体实现,那么在客户端与服务端的通信又有怎样的区别?
经过查阅资料得知IBinder数据传递核心方法是transactonTransact,前者为客户端传递数据,后者为服务端接收到数据
,先看下Binder的实现

  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;
    }

可有明显看到,transact中是直接调用了onTransact,不涉及任何其他数据处理,所以修饰符并不会起到作用,也不需要用到修饰符,因为都运行在同一个进程同一个内存区域,接着看BinderProxy

 public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        ...
        ...
        try {
            return transactNative(code, data, reply, flags);
        } finally {
            AppOpsManager.resumeNotedAppOpsCollection(prevCollection);

            if (transactListener != null) {
                transactListener.onTransactEnded(session);
            }

            if (tracingEnabled) {
                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
            }
        }
    }

可以看到核心方法是一个nativce方法,具体的我就没研究下去了, 而前面分析修饰符的时候看到

mRemote.transact(Stub.TRANSACTION_getData, _data, _reply, 0);

实际就是调用BinderProxy中的transact,然后调用native方法,最终会触发远程端的onTransact,调用对应的远程方法。

到这分析就结束了,经过上面的一些代码,可以了解到IBinder的两个实现类在数据传递的区别,也知道了修饰符起作用的原因和原理

总结

  1. 其实通过测试代码不难发现3个修饰符之间的区别,而难点在于IBinder实现类的以及实现类中对于数据传递的处理
  2. Binder在Android系统中是一个很重要的东西,目前在我个人理解上它还属于一个很庞大不是几天几周就能学完的东西,所以根据上面简单的代码分析也只是知道一些上层实现类的区别,而它真正的通信方式我并没有去做深入了解,功力还不够,还得继续努力
上一篇下一篇

猜你喜欢

热点阅读