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

前言

最近研究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代码片段来实现数据流向的控制。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342