阅读 579

AIDL中inout的本质

本文源码分析基于Android Q

这是一篇短文,用于阐述一个简单却又容易被人忽略的知识点。

毛主席在实践论中告诉我们:“你要知道梨子的滋味,你就得变革梨子,亲口吃一吃。”这句话翻译成程序员的行话就是:“Read the fucking source code.”

AIDL中inout概念的理解同样如此,很多人认识不清晰的直接原因就是没有亲自看过经过aidl-gen生成的Java文件。我想了想,可能有两个原因阻碍了人们去直接“变革”AIDL:

  1. 该Java文件需要编译生成,并不在源码中。
  2. 有些程序员写惯了简单的Java类,就会觉得aidl-gen生成的Java文件中抽象类,静态内部类和接口之间的关系有些复杂(选择使用抽象类,静态内部类其实都是有原因的)。

理解in/out/inout最简单的方式不是旁征博引、大段阐述,而是找一个AIDL生成的java文件来直接感受它。

这里我们选择的AIDL文件是IAidlTest.aidl。

frameworks/base/core/tests/coretests/src/android/os/IAidlTest.aidl

18  package android.os;
19  
20  import android.os.AidlTest;
21  
22  interface IAidlTest {
23      int intMethod(int a);
24  
25      AidlTest.TestParcelable parcelableIn(in AidlTest.TestParcelable p);
26      AidlTest.TestParcelable parcelableOut(out AidlTest.TestParcelable p);
27      AidlTest.TestParcelable parcelableInOut(inout AidlTest.TestParcelable p);
28  
29      AidlTest.TestParcelable listParcelableLonger(
30                      inout List<AidlTest.TestParcelable> list, int index);
31      int listParcelableShorter(
32                      inout List<AidlTest.TestParcelable> list, int index);
33  
34      boolean[] booleanArray(in boolean[] a0, out boolean[] a1, inout boolean[] a2);
35      char[] charArray(in char[] a0, out char[] a1, inout char[] a2);
36      int[] intArray(in int[] a0, out int[] a1, inout int[] a2);
37      long[] longArray(in long[] a0, out long[] a1, inout long[] a2);
38      float[] floatArray(in float[] a0, out float[] a1, inout float[] a2);
39      double[] doubleArray(in double[] a0, out double[] a1, inout double[] a2);
40      String[] stringArray(in String[] a0, out String[] a1, inout String[] a2);
41      AidlTest.TestParcelable[] parcelableArray(in AidlTest.TestParcelable[] a0,
42                                            out AidlTest.TestParcelable[] a1,
43                                            inout AidlTest.TestParcelable[] a2);
44                                            
45      void voidSecurityException();
46      int intSecurityException();
47  }
复制代码

这里我们只关注三个方法:

  • parcelableIn
  • parcelableOut
  • parcelableInOut

AidlTest.TestParcelable是一个继承了Parcelable接口的类,该类中的方法主要为了完成两件事:

  1. 将类中结构化的数据转换成序列化的数据,写入Parcel对象中便于后续Binder驱动将其拷贝给其他进程。
  2. 从其他进程中拷贝回一个Parcel对象,利用其中序列化的数据重新构建一个结构化的Java对象。

1. parcelableIn

所有的跨进程通信都是由client端发起的,因此我们需要重点关注Java文件中生成的Proxy类,它是client进程获取到的用于通信的Java对象所属的类。

out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frameworks/base/core/tests/coretests/src/android/os/IAidlTest.java

463      @Override public android.os.AidlTest.TestParcelable parcelableIn(android.os.AidlTest.TestParcelable p) throws android.os.RemoteException
464      {
465        android.os.Parcel _data = android.os.Parcel.obtain();
466        android.os.Parcel _reply = android.os.Parcel.obtain();
470        android.os.AidlTest.TestParcelable _result;
468        try {
469          _data.writeInterfaceToken(DESCRIPTOR);
470          if ((p!=null)) {
471            _data.writeInt(1);
472            p.writeToParcel(_data, 0);
473          }
474          else {
475            _data.writeInt(0);
476          }
477          boolean _status = mRemote.transact(Stub.TRANSACTION_parcelableIn, _data, _reply, 0);
478          if (!_status && getDefaultImpl() != null) {
479            return getDefaultImpl().parcelableIn(p);
480          }
481          _reply.readException();
482          if ((0!=_reply.readInt())) {
483            _result = android.os.AidlTest.TestParcelable.CREATOR.createFromParcel(_reply);
484          }
485          else {
486            _result = null;
487          }
488        }
489        finally {
490          _reply.recycle();
491          _data.recycle();
492        }
493        return _result;
494      }
复制代码

为了将参数传输给对端进程,需要将其先写入一个Parcel对象,也就是上面代码的472行。之后通过mRemote.transact进行真正的跨进程通信。

Binder通过内存拷贝的方式传输数据,因此对端进程拿到的并不是此进程中的TestParcelable对象,而是照着它的模子拷贝的一份“镜像”。被“in”修饰的参数p只有一个作用:在对端进程中产生一个p的“镜像”。此后该“镜像”的数据无论如何变化也影响不到p。

2. parcelableOut

out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frameworks/base/core/tests/coretests/src/android/os/IAidlTest.java

495      @Override public android.os.AidlTest.TestParcelable parcelableOut(android.os.AidlTest.TestParcelable p) throws android.os.RemoteException
496      {
497        android.os.Parcel _data = android.os.Parcel.obtain();
498        android.os.Parcel _reply = android.os.Parcel.obtain();
499        android.os.AidlTest.TestParcelable _result;
500        try {
501          _data.writeInterfaceToken(DESCRIPTOR);
502          boolean _status = mRemote.transact(Stub.TRANSACTION_parcelableOut, _data, _reply, 0);
503          if (!_status && getDefaultImpl() != null) {
504            return getDefaultImpl().parcelableOut(p);
505          }
506          _reply.readException();
507          if ((0!=_reply.readInt())) {
508            _result = android.os.AidlTest.TestParcelable.CREATOR.createFromParcel(_reply);
509          }
510          else {
511            _result = null;
512          }
513          if ((0!=_reply.readInt())) {
514            p.readFromParcel(_reply);
515          }
516        }
517        finally {
518          _reply.recycle();
519          _data.recycle();
520        }
521        return _result;
522      }
复制代码

可以看到502行之前_data中只写入了DESCRIPTOR(用于对端进程接收数据后进行校验),因此被“out”修饰的参数p并不会传输给对端进程。

而当对端进程返回数据时,所有返回的数据都会序列化地排列在_reply中。首先是508行从_reply中读取数据并创建返回对象(该对象是方法返回值)。接着是514行从_reply中读取数据并保存到参数p中,这就相当于对端进程来更新本进程的数据。

p中原本的数据并不会传递给对端进程,但从该方法返回后,它里面的数据将会被对端进程更新。因此你可以把它看作是第二个返回值,这也是之所以用“out”修饰的原因。

3. parcelableInOut

out/soong/.intermediates/frameworks/base/core/tests/coretests/FrameworksCoreTests/android_common/xref/srcjars.xref/frameworks/base/core/tests/coretests/src/android/os/IAidlTest.java

523      @Override public android.os.AidlTest.TestParcelable parcelableInOut(android.os.AidlTest.TestParcelable p) throws android.os.RemoteException
524      {
525        android.os.Parcel _data = android.os.Parcel.obtain();
526        android.os.Parcel _reply = android.os.Parcel.obtain();
527        android.os.AidlTest.TestParcelable _result;
528        try {
529          _data.writeInterfaceToken(DESCRIPTOR);
530          if ((p!=null)) {
531            _data.writeInt(1);
532            p.writeToParcel(_data, 0);
533          }
534          else {
535            _data.writeInt(0);
536          }
537          boolean _status = mRemote.transact(Stub.TRANSACTION_parcelableInOut, _data, _reply, 0);
538          if (!_status && getDefaultImpl() != null) {
539            return getDefaultImpl().parcelableInOut(p);
540          }
541          _reply.readException();
542          if ((0!=_reply.readInt())) {
543            _result = android.os.AidlTest.TestParcelable.CREATOR.createFromParcel(_reply);
544          }
545          else {
546            _result = null;
547          }
548          if ((0!=_reply.readInt())) {
549            p.readFromParcel(_reply);
550          }
551        }
552        finally {
553          _reply.recycle();
554          _data.recycle();
555        }
556        return _result;
557      }
复制代码

看完了“in”和“out”各自的含义,“inout”的含义也就不言自明了。参数p中的数据即会传递给对端数据,又会被对端返回的数据给更新。这和本地调用传入引用参数的情况是一致的:传入的参数既能够让方法体内感知到它的数据,方法体内对于传入参数内部数据的改动也可以让外界知道。