AIDL中inout的本質

本文源碼分析基於Android Qjava

這是一篇短文,用於闡述一個簡單卻又容易被人忽略的知識點。android

毛主席在實踐論中告訴咱們:「你要知道梨子的滋味,你就得變革梨子,親口吃一吃。」這句話翻譯成程序員的行話就是:「Read the fucking source code.」程序員

AIDL中inout概念的理解一樣如此,不少人認識不清晰的直接緣由就是沒有親自看過通過aidl-gen生成的Java文件。我想了想,可能有兩個緣由阻礙了人們去直接「變革」AIDL:bash

  1. 該Java文件須要編譯生成,並不在源碼中。
  2. 有些程序員寫慣了簡單的Java類,就會以爲aidl-gen生成的Java文件中抽象類,靜態內部類和接口之間的關係有些複雜(選擇使用抽象類,靜態內部類其實都是有緣由的)。

理解in/out/inout最簡單的方式不是旁徵博引、大段闡述,而是找一個AIDL生成的java文件來直接感覺它。ide

這裏咱們選擇的AIDL文件是IAidlTest.aidl。源碼分析

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

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  }
複製代碼

這裏咱們只關注三個方法:spa

  • parcelableIn
  • parcelableOut
  • parcelableInOut

AidlTest.TestParcelable是一個繼承了Parcelable接口的類,該類中的方法主要爲了完成兩件事:翻譯

  1. 將類中結構化的數據轉換成序列化的數據,寫入Parcel對象中便於後續Binder驅動將其拷貝給其餘進程。
  2. 從其餘進程中拷貝回一個Parcel對象,利用其中序列化的數據從新構建一個結構化的Java對象。

1. parcelableIn

全部的跨進程通訊都是由client端發起的,所以咱們須要重點關注Java文件中生成的Proxy類,它是client進程獲取到的用於通訊的Java對象所屬的類。code

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中的數據即會傳遞給對端數據,又會被對端返回的數據給更新。這和本地調用傳入引用參數的狀況是一致的:傳入的參數既可以讓方法體內感知到它的數據,方法體內對於傳入參數內部數據的改動也可讓外界知道。

相關文章
相關標籤/搜索