AIDL中的数据流向

  • Post author:
  • Post category:其他


文章已同步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推导,此处略;



版权声明:本文为freelander_j原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。