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代碼片段來實現數據流向的控制。

明白了其根源之後,對於這個問題的理解也就容易都了,看了這篇文章的你是否也向我一樣完全理解了這幾個關鍵字了呢?如果還沒有,自己動手實踐一下吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章