AIDL中的in,out,inout源码解析

2020-06-08  本文已影响0人  一叶知秋yi

前言

最近研究Android系统源码,难免接触到很多aidl接口。突然发现自己总是将in,out,inout这几个关键字的功能记混了,所以这次从源码层面好好分析下这几个关键字的不同,然后总结下它们具体的作用。

正文

由于在aidl接口的基本类型的参数模式默认都是in的,所以为了更好的实验,我们自己定义了一个类来做试验的载体,类很简单就是一个User包含一个成员变量name

public class User implements Parcelable {
    public String name;
    //省略其他代码
}

aidl的定义就一个接口addUser参数和返回值都是User类型

parcelable User;

interface IUserInterface {
    User addUser(/*这个关键字就是我们今天要研究的主角*/inout User user);
}

测试代码如下

//客户端代码
private val mServiceConnection = object : ServiceConnection {
    override fun onServiceDisconnected(name: ComponentName?) {
        // nothing
    }

    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        Log.e(TAG, "connected")

        mUserInterface = IUserInterface.Stub.asInterface(service)

        val user = User()
        user.name = "abc"
        //输出client段发送的user
        Log.e(TAG, "client send user: $user")

        val returnUser = mUserInterface.addUser(user)

        //输出调用方法后之后的参数user
        Log.e(TAG, "client receive user: $user")
        //输出方法返回的user
        Log.e(TAG, "returnUser: $returnUser")
    }
}
//服务端代码
private var mUserInterface = object : IUserInterface.Stub() {
    override fun addUser(user: User?) : User? {
        //输出服务端收到的user
        Log.e(TAG, "service receive user: $user")
        user!!.name = "bcd"

        //输出服务端修改后的user
        Log.e(TAG, "service send user: $user")
        return user
    }
}

非常简单的一个远程接口调用,那么接下来我们看下再使用in,out,inout来修改入参user时各个场景下的输出情况。首先是修饰为inout,结果如下:

2020-01-07 16:39:15.472 5505-5505/com.demo.aidlclient E/AidlClient: connected
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: client send user: abc
//服务端能正常收到user参数
2020-01-07 16:39:15.473 4086-4112/com.demo.aidlservice E/AidlService: service receive user: abc
2020-01-07 16:39:15.473 4086-4112/com.demo.aidlservice E/AidlService: service send user: bcd
//客户端能收到修改后的user参数
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: client receive user: bcd
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: returnUser: bcd

接下来将修饰符改为in,结果如下:

2020-01-07 16:39:15.472 5505-5505/com.demo.aidlclient E/AidlClient: connected
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: client send user: abc
//服务端能正常收到user参数
2020-01-07 16:39:15.473 4086-4112/com.demo.aidlservice E/AidlService: service receive user: abc
2020-01-07 16:39:15.473 4086-4112/com.demo.aidlservice E/AidlService: service send user: bcd
//这里客户端没有收到服务端修改后的user
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: client receive user: abc
//远程方法的返回值仍能正常收到
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: returnUser: bcd

接下来将修饰符改为out,结果如下:

2020-01-07 16:39:15.472 5505-5505/com.demo.aidlclient E/AidlClient: connected
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: client send user: abc
//服务端不能正常收到user参数
2020-01-07 16:39:15.473 4086-4112/com.demo.aidlservice E/AidlService: service receive user: null
2020-01-07 16:39:15.473 4086-4112/com.demo.aidlservice E/AidlService: service send user: bcd
//客户端能收到修改后的user参数
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: client receive user: bcd
//远程方法的返回值仍能正常收到
2020-01-07 16:39:15.473 5505-5505/com.demo.aidlclient E/AidlClient: returnUser: bcd

结论

根据上面的实验结果我们可以得出如下结论:

  1. in, out, inout只作用于方法入参,对方法的返回值不受影响
  2. in的作用为保证入参的值只能从客户端流向服务端out的作用为保证入参的值只能从服务端流向客户端inout可以保证入参能在服务端和服务端双向流动

源码分析

上面的结论是从现象看出来的,那么接下来我们从源码的角度看下是这几个修饰符到底是如何作用的。远程调用的参数处理都是在onTransact方法中,我们直接查看aidl生成的代码。

//in场景的onTransact代码
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
  java.lang.String descriptor = DESCRIPTOR;
  switch (code)
  {
    case INTERFACE_TRANSACTION:
    {
      reply.writeString(descriptor);
      return true;
    }
    case TRANSACTION_addUser:
    {
      data.enforceInterface(descriptor);
      com.demo.aidl.User _arg0;
      if ((0!=data.readInt())) {
        //远程接口传入的参数是由接口传入
        _arg0 = com.demo.aidl.User.CREATOR.createFromParcel(data);
      }
      else {
        _arg0 = null;
      }
      com.demo.aidl.User _result = this.addUser(_arg0);
      //调用玩addUser接口之后,并未将reply对_arg0赋值,因此服务端修改的值并不会回传给客户端
      reply.writeNoException();
      if ((_result!=null)) {
        reply.writeInt(1);
        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
      }
      else {
        reply.writeInt(0);
      }
      return true;
    }
    default:
    {
      return super.onTransact(code, data, reply, flags);
    }
  }
}

//out场景的onTransact代码
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
  java.lang.String descriptor = DESCRIPTOR;
  switch (code)
  {
    case INTERFACE_TRANSACTION:
    {
      reply.writeString(descriptor);
      return true;
    }
    case TRANSACTION_addUser:
    {
      data.enforceInterface(descriptor);
      com.demo.aidl.User _arg0;
      //这里直接new了一个User对象,并未使用接口传入的参数
      _arg0 = new com.demo.aidl.User();
      com.demo.aidl.User _result = this.addUser(_arg0);
      reply.writeNoException();
      if ((_result!=null)) {
        reply.writeInt(1);
        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
      }
      else {
        reply.writeInt(0);
      }
      if ((_arg0!=null)) {
        reply.writeInt(1);
        //这里将服务端修改过的值赋值给_arg0,所以客户端能收到服务端的修改值
        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
      }
      else {
        reply.writeInt(0);
      }
      return true;
    }
    default:
    {
      return super.onTransact(code, data, reply, flags);
    }
  }
}
//inout场景的onTransact代码
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
  java.lang.String descriptor = DESCRIPTOR;
  switch (code)
  {
    case INTERFACE_TRANSACTION:
    {
      reply.writeString(descriptor);
      return true;
    }
    case TRANSACTION_addUser:
    {
      data.enforceInterface(descriptor);
      com.demo.aidl.User _arg0;
      if ((0!=data.readInt())) {
        //从接口读取入参,传给服务端
        _arg0 = com.demo.aidl.User.CREATOR.createFromParcel(data);
      }
      else {
        _arg0 = null;
      }
      com.demo.aidl.User _result = this.addUser(_arg0);
      reply.writeNoException();
      if ((_result!=null)) {
        reply.writeInt(1);
        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
      }
      else {
        reply.writeInt(0);
      }
      if ((_arg0!=null)) {
        reply.writeInt(1);
        //写入服务端修改后的参数
        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
      }
      else {
        reply.writeInt(0);
      }
      return true;
    }
    default:
    {
      return super.onTransact(code, data, reply, flags);
    }
  }
}

从上面的代码我们可以看出,这几个关键字通过控制生成的代码片段来控制参数的流向,设计还是非常巧妙的

结语

我们将前面的结论最后总结一下

  1. in, out, inout只作用于方法入参,对方法的返回值不受影响
  2. in的作用为保证入参的值只能从客户端流向服务端out的作用为保证入参的值只能从服务端流向客户端inout可以保证入参能在服务端和服务端双向流动
  3. 这几个关键字通过控制aidl生成java代码片段来实现数据流向的控制。

明白了其根源之后,对于这个问题的理解也就容易都了,看了这篇文章的你是否也向我一样完全理解了这几个关键字了呢?如果还没有,自己动手实践一下吧。

上一篇下一篇

猜你喜欢

热点阅读