AIDL中的數據流向

文章已同步github博客:AIDL中的數據流向

1、AIDL文件

1.1、文件類型

文件後綴名爲.aidl;

1.2、數據類型

1.2.1、默認支持類型

默認支持的類型不需要導包;

  • Java八種基本類型:byte、short、int、long、float、double、boolean、char;
  • String類型;
  • CharSequence類型;
  • List類型:List中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable,List可以使用泛型;
  • Map類型:Map中的所有元素必須是AIDL支持的類型之一,或者是一個其他AIDL生成的接口,或者是定義的parcelable,Map是不支持泛型的,使用時需要顯示導包;

1.2.2、parcelable對象

如自定義實體類實現Parcelable接口,使用時需要顯示導包,即使目標文件與當前正在編寫的.aidl文件在同一個包下;

1.3、定向tag

  • in:單向數據流,客戶端到服務端;
  • out:單向數據流,服務端到客戶端;
  • inout:雙向數據流,客戶端與服務端雙向流通;

Java中的基本類型和String、CharSequence的定向tag默認且只能是in;

1.3.1、in

客戶端數據流向服務端,即客戶端的值(實體類)即時同步到服務端,服務端可以拿到客戶端傳遞的實體類,此時在服務端對接收到的客戶端傳來的數據做修改,服務端發生變化,客戶端不發生變化,因爲單向流動,除非調用接口方法,返回服務端修改的數據,客戶端才能拿到服務端修改後的數據;

1.3.2、out

服務端數據流向客戶端,即客戶端的數據信息不向服務端同步(傳遞對象,對象不爲空,但是沒有數據),但是服務端拿到傳遞的對象後修改數據,能及時同步到客戶端;

1.3.3、inout

雙向流向,即服務端能拿到客戶端傳遞的數據,服務端修改後客戶端能及時同步到數據;

1.4、兩種aidl文件

  • 一類是用來定義parcelable對象,以供其他AIDL文件使用AIDL中非默認支持的數據類型的;
  • 一類是用來定義方法接口,以供系統使用來完成跨進程通信的;

2、AIDL的使用

2.1、定義.aidl文件

2.1.1、實體類定義

定義實體類,作爲進程間交互的數據,實現Parcelable接口;

public class ImageData implements Parcelable {
  private String mImageName;
  private int mImageSize;
  private byte[] mImageArray;

  public ImageData() {}

  protected ImageData(Parcel in) {
    readFromParcel(in);
  }

  public static final Creator<ImageData> CREATOR = new Creator<ImageData>() {
    @Override
    public ImageData createFromParcel(Parcel in) {
      return new ImageData(in);
    }

		@Override
    public ImageData[] newArray(int size) {
      return new ImageData[size];
    }
  };

	// 省略getter和setter方法
	...
    
  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(mImageName);
    dest.writeInt(mImageSize);
    dest.writeByteArray(mImageArray);
  }

  public void readFromParcel(Parcel dest) {
    this.mImageName = dest.readString();
    this.mImageSize = dest.readInt();
    this.mImageArray = dest.createByteArray();
  }
  
	// 省略toString方法
  ...
}

2.1.2、.aidl接口定義

定義.aidl接口,這裏爲了驗證客戶端與服務端的數據變化,定義了三個接口方法;

// 顯示導包
interface ImageShareInterface {
  List<ImageData> getImages();
  // flag爲in
  ImageData showImageIn(in ImageData data);
  
  // flag爲out
  ImageData showImageOut(out ImageData data);
  
  // flag爲inout
  ImageData showImageInOut(inout ImageData data);
}

定義實體類的aidl文件;

// 該文件需要與ImageData實體類文件的包名相同
import com.xxx.ImageData;

parcelable ImageData;

此時完成之後,build工程,會生將.aidl文件接口生成對應的java文件,生成目錄如下:

build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/<package>/

2.2、server端實現

  • 自定義Service繼承Service類,實現onBind()方法;

    @Override
    public IBinder onBind(Intent intent) {
      return imageShareMgr;
    }
    
  • 聲明2.1.2中生成的java接口文件中的Stub類,並實現定義的方法,最後將該對象在onBind()方法中返回,用於將該IBinder對象傳遞給客戶端使用;

    private final ImageShareInterface.Stub imageShareMgr = new ImageShareInterface.Stub() {
      @Override
      public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {}
    
      @Override
      public List<ImageData> getImages() throws RemoteException {
        synchronized (this) {
          if (imageDatas != null) {
            return imageDatas;
          }
        }
        return new ArrayList<>();
      }
    
      @Override
      public ImageData showImageIn(ImageData data) throws RemoteException {
        synchronized (this) {
          if (imageDatas == null) {
            imageDatas = new ArrayList<>();
          }
          if (data == null) {
            data = new ImageData();
          }
          data.setmImageArray(new byte[2]);
          if (!imageDatas.contains(data)) {
            imageDatas.add(data);
          }
          Log.e("tag", "service in : " + imageDatas.toString());
          return data;
        }
      }
    
      @Override
      public ImageData showImageOut(ImageData data) throws RemoteException {
        synchronized (this) {
          if (imageDatas == null) {
            imageDatas = new ArrayList<>();
          }
          if (data == null) {
            data = new ImageData();
          }
          data.setmImageArray(new byte[2]);
          if (!imageDatas.contains(data)) {
            imageDatas.add(data);
          }
          Log.e("tag", "service out : " + imageDatas.toString());
          return data;
        }
      }
    
      @Override
      public ImageData showImageInOut(ImageData data) throws RemoteException {
        synchronized (this) {
          if (imageDatas == null) {
            imageDatas = new ArrayList<>();
          }
          if (data == null) {
            data = new ImageData();
          }
          data.setmImageArray(new byte[2]);
          if (!imageDatas.contains(data)) {
            imageDatas.add(data);
          }
          Log.e("tag", "service inout : " + imageDatas.toString());
          return data;
        }
      }
    };
    

最後Service類需要在Manifest文件中聲明;

2.3、client端實現

2.3.1、綁定服務

Intent intent =new Intent();
intent.setAction("xxx");
intent.setPackage("xxx");
bindService(intent, connection, Context.BIND_AUTO_CREATE);

private ServiceConnection connection = new ServiceConnection() {
  @Override
  public void onServiceConnected(ComponentName name, IBinder service) {
    imageShareInterface = ImageShareInterface.Stub.asInterface(service);
    if (imageShareInterface != null) {
      try {
        List<ImageData> imageData = imageShareInterface.getImages();
        Log.e("tag", "conn: " + imageData.toString());
      } catch (RemoteException e) {
        e.printStackTrace();
      }
    }
  }

  @Override
  public void onServiceDisconnected(ComponentName name) {}
};

2.3.2、調用服務

客戶端使用三個TextView來打印客戶端本地對象數據和服務端返回的對象來驗證數據流向同步,TextView上綁定點擊事件,點擊TextView時,分別調用服務端的三個對應方法in、out、inout;

// flag爲in時
ImageData imageData = new ImageData();
imageData.setmImageName("client in");
imageData.setmImageArray(new byte[3]);
try {
  ImageData re = imageShareInterface.showImageIn(imageData);
  // 可以爲空
  // ImageData re = imageShareInterface.showImageIn(null);
  Log.e("tag", "client in " + imageData.toString());
  Log.e("tag", "client in2 " + re.toString());
  textIn.setText(re.getmImageName() + "-----" + re.getmImageSize() + "\n" + imageData.getmImageName() + "---" + imageData.getmImageSize());
} catch (RemoteException e) {
  e.printStackTrace();
}

// flag爲out時
ImageData imageData2 = new ImageData();
imageData2.setmImageName("client out");
imageData2.setmImageArray(new byte[4]);
try {
  ImageData re = imageShareInterface.showImageOut(imageData2);
  // 不可以爲空,服務端數據無法流回
  // ImageData re = imageShareInterface.showImageOut(null);
  Log.e("tag", "client out " + imageData2.toString());
  Log.e("tag", "client out2 " + re.toString());
  textOut.setText(re.getmImageName() + "-----" + re.getmImageSize() + "\n" + imageData2.getmImageName() + "---" + imageData2.getmImageSize());
} catch (RemoteException e) {
  e.printStackTrace();
}

// flag爲inout時
ImageData imageData3 = new ImageData();
imageData3.setmImageName("client inout");
imageData3.setmImageArray(new byte[1]);
try {
  ImageData re = imageShareInterface.showImageInOut(imageData3);
  Log.e("tag", "client inout " + imageData3.toString());
  Log.e("tag", "client inout2 " + re.toString());
  textInOut.setText(re.getmImageName() + "-----" + re.getmImageSize() + "\n" + imageData3.getmImageName() + "---" + imageData3.getmImageSize());
} catch (RemoteException e) {
  e.printStackTrace();
}

2.4、結果日誌

按順序點擊三個TextView(in、out、inout順序),分別打印服務端與客戶端日誌

2.4.1、服務端日誌

// flag爲in的方法
E/tag: service in : [ImageData{mImageName='service default', mImageSize=1, mImageArray=[0]}, ImageData{mImageName='client in', mImageSize=2, mImageArray=[0, 0]}]

// flag爲out的方法
E/tag: service out : [ImageData{mImageName='service default', mImageSize=1, mImageArray=[0]}, ImageData{mImageName='client in', mImageSize=2, mImageArray=[0, 0]}, ImageData{mImageName='null', mImageSize=2, mImageArray=[0, 0]}]

// flag爲inout的方法
E/tag: service inout : [ImageData{mImageName='service default', mImageSize=1, mImageArray=[0]}, ImageData{mImageName='client in', mImageSize=2, mImageArray=[0, 0]}, ImageData{mImageName='null', mImageSize=2, mImageArray=[0, 0]}, ImageData{mImageName='client inout', mImageSize=2, mImageArray=[0, 0]}]

服務端默認向集合中添加一個實體類作爲參考,並且接受客戶端傳遞的參數,只修改其屬性size大小,不修改屬性name的值,對應三個方法:

  • in:接收到客戶端傳來的對象,name屬性的值不變,size屬性的值爲服務端修改後的;
  • out:接受到客戶端傳來的對象,name屬性的值爲null,size屬性的值爲服務端修改後的;
  • inout:接受到客戶端傳來的對象,name屬性的值不變,size屬性的值爲服務端修改後的;

2.4.2、客戶端日誌

// 成功連接到服務時打印的日誌
E/tag: conn: [ImageData{mImageName='service default', mImageSize=1, mImageArray=[0]}]

// flag爲in的方法
E/tag: client in ImageData{mImageName='client in', mImageSize=3, mImageArray=[0, 0, 0]}
    client in2 ImageData{mImageName='client in', mImageSize=2, mImageArray=[0, 0]}

// flag爲out的方法
E/tag: client out ImageData{mImageName='null', mImageSize=2, mImageArray=[0, 0]}
    client out2 ImageData{mImageName='null', mImageSize=2, mImageArray=[0, 0]}

// flag爲inout的方法
E/tag: client inout ImageData{mImageName='client inout', mImageSize=2, mImageArray=[0, 0]}
    client inout2 ImageData{mImageName='client inout', mImageSize=2, mImageArray=[0, 0]}

客戶端向服務端傳遞對象,在客戶端設置name屬性的值以及size屬性的值;

  • in:客戶端聲明的對象爲客戶端自己設置的值,調用接口服務方法返回的對象爲服務端修改後的值;
  • out:客戶端聲明的對象以及服務端返回的對象,name屬性的值均變成了null,size屬性均變成了服務端設置的;
  • innout:客戶端聲明的對象和服務端返回的對象,name屬性的值均爲客戶端設置的值,size屬性值爲服務端修改的值;

以上變化有些不解,爲何有的調用服務接口方法之後,本地聲明的對象值跟着修改,有些值是原來聲明的,有些值會變成null?

3、數據流向源碼分析

通過查看源碼以及編譯後生成的java接口文件來解釋;

3.1、server端接口對象

服務端聲明一個ImageShareInterface.Stub對象,並作爲onBind()方法的返回值傳遞給與之綁定的客戶端,Stub類由aidl文件編譯之後產生(省略部分代碼):

public static abstract class Stub extends android.os.Binder implements cn.com.ultrapower.aidl_service.ImageShareInterface {
  private static final java.lang.String DESCRIPTOR = "xxx.ImageShareInterface";
  public Stub() { this.attachInterface(this, DESCRIPTOR); }

  @Override public android.os.IBinder asBinder() { return this; }
  
  @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    ...
  }
  
  private static class Proxy implements cn.com.ultrapower.aidl_service.ImageShareInterface {
    ...
  }
}

Service中聲明Stub對象,調用其無參構造方法,其無參構造方法調用其父類Binder類的attachInterface()方法,將本身owner傳遞過去,以及傳遞了一個descriptor作爲進程的唯一的標識:

public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
  mOwner = owner;
  mDescriptor = descriptor;
}

3.2、client端代理對象

客戶端連接服務端之後,在connection中利用服務端返回的IBinder對象,拿到這個對象在生成的java接口文件中的代理對象:

imageShareInterface = ImageShareInterface.Stub.asInterface(service);

查看ImageShareInterface.Stub的asInterface()方法:

public static cn.com.ultrapower.aidl_service.ImageShareInterface asInterface(android.os.IBinder obj) {
  if ((obj==null)) { return null; }
  android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
  if (((iin!=null)&&(iin instanceof cn.com.ultrapower.aidl_service.ImageShareInterface))) {
    return ((cn.com.ultrapower.aidl_service.ImageShareInterface)iin);
  }
  return new cn.com.ultrapower.aidl_service.ImageShareInterface.Stub.Proxy(obj);
}

這裏通過DESCRIPTOR來判斷是否在同一個進程,客戶端與服務端不在同一個進程內,所以最終返回的是Stub的代理對象Proxy對象;

private static class Proxy implements cn.com.ultrapower.aidl_service.ImageShareInterface {
	// 這裏具體實現了客戶端調用接口文件中的方法
}

所以調用的流程是:客戶端調用接口文件的Stub代理類的方法,由代理類去中調用Stub中的接口方法,即實現在Service中的接口方法,之間交互的媒介就是new代理時傳入的IBinder對象,也就是服務端爲客戶端返回的IBinder對象;

3.3、數據流向

3.3.1、in

查看flag爲in的接口方法的具體實現,從客戶端調用開始:

ImageData imageData = new ImageData();
imageData.setmImageName("client in");
imageData.setmImageArray(new byte[3]);
// 通過Proxy代理對象調用遠程服務
ImageData re = imageShareInterface.showImageIn(imageData);

查看代理類Proxy的showImageIn()方法實現:

@Override public cn.com.ultrapower.aidl_service.bean.ImageData showImageIn(cn.com.ultrapower.aidl_service.bean.ImageData data) throws android.os.RemoteException {
  android.os.Parcel _data = android.os.Parcel.obtain();
  android.os.Parcel _reply = android.os.Parcel.obtain();
  cn.com.ultrapower.aidl_service.bean.ImageData _result;
  try {
    _data.writeInterfaceToken(DESCRIPTOR);
    // 參數data由客戶端傳入,不爲null
    if ((data!=null)) {
      // 標誌位
      _data.writeInt(1);
      // 寫入序列化對象
      data.writeToParcel(_data, 0);
    } else {
      _data.writeInt(0);
    }
    // 調動服務端Stub的transact()方法
    boolean _status = mRemote.transact(Stub.TRANSACTION_showImageIn, _data, _reply, 0);
    if (!_status && getDefaultImpl() != null) {
      return getDefaultImpl().showImageIn(data);
    }
    _reply.readException();
    if ((0!=_reply.readInt())) {
      // 將序列化內容寫入結果
      _result = cn.com.ultrapower.aidl_service.bean.ImageData.CREATOR.createFromParcel(_reply);
    } else {
      _result = null;
    }
  } finally {
    _reply.recycle();
    _data.recycle();
  }
  // 返回
  return _result;
}

由上面代碼可知,代理類的對應方法,拿到客戶端傳入的對象參數,設置相關標誌位,寫入序列化對象,調用遠程服務的transact()方法,最後將結果從序列化對象中拿出並返回,此時客戶端就可以拿到服務端返回給客戶端的對象;

查看Stub的transact()方法:

@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 TRANSACTION_showImageIn: {
      data.enforceInterface(descriptor);
      // _arg0即客戶端傳入的參數對象
      cn.com.ultrapower.aidl_service.bean.ImageData _arg0;
      if ((0!=data.readInt())) {
        _arg0 = cn.com.ultrapower.aidl_service.bean.ImageData.CREATOR.createFromParcel(data);
      } else {
        _arg0 = null;
      }
      // 調用服務端對應的方法,拿到結果值
      cn.com.ultrapower.aidl_service.bean.ImageData _result = this.showImageIn(_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;
    }  
  }
}

有上面代碼可知,在調用Stub的transact()方法時,通過判斷code(即對應方法的標識),拿到客戶端傳入的參數,寫入序列化,調用服務端的具體實現,將客戶端參數傳入,拿到結果值,將結果值寫入序列化,並返回爲true;

代理類Proxy中從序列化中讀出服務端修改的值,即_result,返回給客戶端,所以此時客戶端拿到服務端傳遞的值;

所以對應客戶端本地設置的值危改變,服務端返回的對象爲服務端修改過的值;

E/tag: client in ImageData{mImageName='client in', mImageSize=3, mImageArray=[0, 0, 0]}
    client in2 ImageData{mImageName='client in', mImageSize=2, mImageArray=[0, 0]}

3.3.2、out

out類型方法的客戶端調用:

ImageData imageData2 = new ImageData();
imageData2.setmImageName("client out");
imageData2.setmImageArray(new byte[4]);
// 通過Proxy代理對象調用遠程服務
ImageData re = imageShareInterface.showImageOut(imageData2);

查看代理類Proxy的showImageOut()方法:

@Override public cn.com.ultrapower.aidl_service.bean.ImageData showImageOut(cn.com.ultrapower.aidl_service.bean.ImageData data) throws android.os.RemoteException {
  android.os.Parcel _data = android.os.Parcel.obtain();
  android.os.Parcel _reply = android.os.Parcel.obtain();
  cn.com.ultrapower.aidl_service.bean.ImageData _result;
  try {
    _data.writeInterfaceToken(DESCRIPTOR);
    // 調用Stub的transact()方法
    boolean _status = mRemote.transact(Stub.TRANSACTION_showImageOut, _data, _reply, 0);
    if (!_status && getDefaultImpl() != null) {
      return getDefaultImpl().showImageOut(data);
    }
    _reply.readException();
    if ((0!=_reply.readInt())) {
      // 拿到服務端設置後的對象
      _result = cn.com.ultrapower.aidl_service.bean.ImageData.CREATOR.createFromParcel(_reply);
    } else {
      _result = null;
    }
    if ((0!=_reply.readInt())) {
      // 這裏注意,data對象從序列化從讀取,即服務端操作之後的數據,客戶端的參數隨之變化
   		// 註釋1
      data.readFromParcel(_reply);
    }
  } finally {
    _reply.recycle();
    _data.recycle();
  }
  return _result;
}

調用Stub的transact()方法:

@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 TRANSACTION_showImageOut: {
      data.enforceInterface(descriptor);
      cn.com.ultrapower.aidl_service.bean.ImageData _arg0;
      // new出對象作爲參數
      _arg0 = new cn.com.ultrapower.aidl_service.bean.ImageData();
      // 使用new的新對象作爲參數調用服務端的具體實現方法,拿到結果值
      cn.com.ultrapower.aidl_service.bean.ImageData _result = this.showImageOut(_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);
        // 參數寫入序列化,即服務端修改後的
        // 註釋2
        _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
      } else {
        reply.writeInt(0);
      }
      return true;
    }
  }
}

有上面代碼可知,客戶端傳入的參數並沒有實際傳入的服務端的具體方法中,而是在這裏新new了一個參數對象,調用服務端的具體實現方法,拿到結果值,並寫入序列化,後面將該參數也寫入序列化,即註釋2處,該參數爲服務端修改之後的;

在上面代理類的方法中,拿到服務端寫入序列化的結果值,返回給客戶端,這裏注意註釋1處,data從序列化中讀取_reply,即服務端修改後寫入序列化的值,此時客戶端的參數隨之變化,即服務的修改結果也隨之同步到了客戶端,這也是在客戶端中new的參數對象也跟服務端返回的值是一樣的,如下日誌:

E/tag: client out ImageData{mImageName='null', mImageSize=2, mImageArray=[0, 0]}
    client out2 ImageData{mImageName='null', mImageSize=2, mImageArray=[0, 0]}

注:

這裏還有一個注意的,在論壇之前看到過有人討論,既然out類型的方法,客戶端傳入的值沒有被服務端使用,爲何還需要傳過去,傳一個null不行嗎?

實際是不行的,通過測試打印日誌也可以看出(上面2.3.2中out方法的調用裏面註釋掉的那句就是測試代碼),此時服務端的實現方法中並沒有執行參數爲null後新建對象,因爲在Stub中已經新建傳遞過去,但是客戶端會崩潰調!!!

查看打印出的日誌可知,在接口文件中的代理類Proxy的showImageOut()方法中報錯,即上面註釋1處“ data.readFromParcel(_reply);”這行代碼,如果客戶端傳入null,此時data爲null,無法將序列化中的值讀入到一個空對象中,這裏也說明了服務端修改的值向客戶端的參數的同步;

3.3.3、inout

inout方法結合上面兩者in和out推導,此處略;

發佈了32 篇原創文章 · 獲贊 14 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章