一、概念说明
定向tag是AIDL中语法的一部分,其中in、out、inout是三个定向tag。
在官网上关于Android定向tag的定义是这样的:
All non-primitive parameters require a directional tag indicating which way the data goes . Either in , out , or inout . Primitives are in by default , and connot be otherwise .
意思就是所有非基本类型的参数都需要一个定向tag来表明数据是如何走向的,要不是in,out或者inout。基本数据类型默认是in,而且不能是其他tag。
根据上述的声明,我们大概猜一下,得出这样的结论:
定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可以在服务端与客户端之间双向流通。其中的数据流向是针对在客户端中的那个传入方法的对象而言的。
对于in,服务端将会收到客户端对象的完整数据,但是客户端对象不会因为服务端对传参的修改而发生变动。类似的行为在Java中的表现是,在Java方法中,对传进来的参数进行了深复制,传进来的参数不会受到深复制后的对象的影响。这和in的行为有点类似。
对于out,服务端将会收到客户端对象,该对象不为空,但是它里面的字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动。类似的行为在Java中的表现是,在Java方法中,对传进来的参数进行忽略,并new一个新对象,所有的操作都是围绕着这个新对象进行的,最后将该新对象赋值给传参对象。
对于inout ,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。类似的行为在Java中的表现是,在Java方法中,对传进来的参数进行修改并返回。
二、实验论证
下面写一个demo来验证上面的结论。
aidl文件:(Book.aidl)
// IBook.aidl
package com.example.runningh.mydemo.binder;
import com.example.runningh.mydemo.binder.Book;
// Declare any non-default types here with import statements
parcelable Book;
aidl文件:(IBookManager.aidl)
// IBookManager.aidl
package com.example.runningh.mydemo.binder;
import com.example.runningh.mydemo.binder.Book;
// Declare any non-default types here with import statements
interface IBookManager {
List getBooks();
void addBookWithInTag(in Book book); //测试定向tag in
void addBookWithOutTag(out Book book); //测试定向tag out
void addBookWithInOutTag(inout Book book); //测试定向tag inout
}
Java文件:(Book.java)
package com.example.runningh.mydemo.binder;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by hwldzh on 2018/5/20
* 类描述:
*/
public class Book implements Parcelable {
public String bookName;
public int price;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(bookName);
dest.writeInt(price);
}
public static Creator CREATOR = new Creator() {
@Override
public Book createFromParcel(Parcel source) {
return new Book(source);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in) {
readFromParcel(in);
}
public Book(String name, int price) {
this.bookName = name;
this.price = price;
}
public Book() {
}
public void readFromParcel(Parcel in) {
bookName = in.readString();
price = in.readInt();
}
public String toString() {
return "[bookName=" + bookName + ", bookPrice=" + price + "]";
}
}
编译demo项目,生成IBookManager.java文件。下面看一下客户端的代码:
package com.example.runningh.mydemo.binder;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import com.example.runningh.mydemo.R;
/**
* Created by hwldzh on 2018/5/20
* 类描述:
*/
public class TestTagActivity extends Activity implements View.OnClickListener {
public static final String TAG = "ABC";
private IBookManager bookManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_tag_activity);
findViewById(R.id.test_tag_in).setOnClickListener(this);
findViewById(R.id.test_tag_out).setOnClickListener(this);
findViewById(R.id.test_tag_inout).setOnClickListener(this);
Intent intent = new Intent(this, TestTagService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
bookManager = IBookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
bookManager = null;
}
}, BIND_AUTO_CREATE);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.test_tag_in:
if (bookManager != null) {
try {
Book book = new Book("Android", 30);
bookManager.addBookWithInTag(book);
Log.i(TAG, "Test in tag. book Info: " + book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
case R.id.test_tag_out:
if (bookManager != null) {
try {
Book book = new Book("Android", 30);
bookManager.addBookWithOutTag(book);
Log.i(TAG, "Test out tag. book Info: " + book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
case R.id.test_tag_inout:
if (bookManager != null) {
try {
Book book = new Book("Android", 30);
bookManager.addBookWithInOutTag(book);
Log.i(TAG, "Test inout tag. book Info: " + book.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
客户端的代码比较简单,主要是三个按钮,点击后触发三个定向tag的不同操作,执行完毕后打印传递参数Book对象的信息。
接着我们看一下服务端的代码:
package com.example.runningh.mydemo.binder;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Created by hwldzh on 2018/5/20
* 类描述:
*/
public class TestTagService extends Service {
public static final String TAG = "ABC";
private IBookManager.Stub bookManager;
private List list = new ArrayList<>();
@Override
public void onCreate() {
super.onCreate();
list.add(new Book("FirstBook", 30));
bookManager = new IBookManager.Stub() {
@Override
public List getBooks() throws RemoteException {
return list;
}
@Override
public void addBookWithInTag(Book book) throws RemoteException {
if (book == null) {
Log.i(TAG, "book is null");
book = new Book();
}
book.price = 100;
list.add(book);
Log.i(TAG, "add Book with in tag.list=" + list.toString());
}
@Override
public void addBookWithOutTag(Book book) throws RemoteException {
if (book == null) {
Log.i(TAG, "book is null");
book = new Book();
}
book.price = 100;
list.add(book);
Log.i(TAG, "add Book with out tag.list=" + list.toString());
}
@Override
public void addBookWithInOutTag(Book book) throws RemoteException {
if (book == null) {
Log.i(TAG, "book is null");
book = new Book();
}
book.price = 100;
list.add(book);
Log.i(TAG, "add Book with inout tag.list =" + list.toString());
}
};
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return bookManager;
}
}
服务端的代码也挺简单的,就是初始化一个IBookManager.Stub对象的IBinder接口并返回给客户端。
现在客户端分别点击”测试定向tag in",“测试定向tag out",“测试定向tag inout”按钮,然后得到如下的Log信息:
点击定向tag in。服务端:
origin Book Info=[bookName=Android, bookPrice=30]
add Book with in tag.list=[[bookName=FirstBook, bookPrice=30], [bookName=Android, bookPrice=100]]
点击定向tag in。客户端:
Test in tag. book Info: [bookName=Android, bookPrice=30]
点击定向tag out。服务端:
origin Book Info=[bookName=null, bookPrice=0]
add Book with out tag.list=[[bookName=FirstBook, bookPrice=30], [bookName=Android, bookPrice=100], [bookName=null, bookPrice=100]]
点击定向tag out。客户端:
Test out tag. book Info: [bookName=null, bookPrice=100]
点击定向tag inout。服务端:
origin Book Info=[bookName=Android, bookPrice=30]
add Book with inout tag.list =[[bookName=FirstBook, bookPrice=30], [bookName=Android, bookPrice=100], [bookName=null, bookPrice=100], [bookName=Android, bookPrice=100]]
点击定向tag inout。客户端:
Test inout tag. book Info: [bookName=Android, bookPrice=100]
从上面信息可以得到如下结论:
当我们点击“测试定向tag in”按钮时,服务端接收到了客户端传过来的Book对象的完整信息,即[bookName=Android, bookPrice=30] ,执行完毕后客户端的Book对象的信息没有改变,依然是[bookName=Android, bookPrice=30]。
当我们点击“测试定向tag out”按钮时,服务端接收到了客户端传过来的Book对象,但是对象的字段都是未初始化的,即[bookName=null, bookPrice=0],执行完毕后客户端的Book对象的信息同步改变了,即和服务端的对象信息进行了同步:[bookName=null, bookPrice=100]。
当我们点击“测试定向tag inout”按钮时,服务端接收到了客户端传过来的Book对象的完整信息,同时服务端的对象的改变也会同步到客户端的对象中。
这个结论的得出和我们上述的推论是一致的,下面我们从源码中看一下这三个方法的逻辑流程,进一步论证我们的实验结果。
三、理论支持
首先我们看一下客户端的源码是怎么处理addBookWithInTag
,addBookWithOutTag
,addBookWithInOutTag
这三个方法的(注意三个方法中注释的对比):
@Override
public void addBookWithInTag(com.example.runningh.mydemo.binder.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
//判断book对象是否为空,不为空则将book对象写入到_data中,_data是客户端到服务端的输入流数据
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
//调用远程服务端的方法,将数据流传送过去
mRemote.transact(Stub.TRANSACTION_addBookWithInTag, _data, _reply, 0);
//注意这里并没有对服务端返回的数据流进行读取
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void addBookWithOutTag(com.example.runningh.mydemo.binder.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
//并不关心客户端传递参数的对象,因为都没有将对象写入到_data数据流
mRemote.transact(Stub.TRANSACTION_addBookWithOutTag, _data, _reply, 0);
_reply.readException();
//从服务端的数据流中读取数据并解析成Book对象返回到传参对象
if ((0!=_reply.readInt())) {
book.readFromParcel(_reply);
}
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void addBookWithInOutTag(com.example.runningh.mydemo.binder.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
//判断book对象是否为空,不为空则将book对象写入到_data中,_data是客户端到服务端的输入流数据
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
//调用远程服务端的方法,将数据流传送过去
mRemote.transact(Stub.TRANSACTION_addBookWithInOutTag, _data, _reply, 0);
_reply.readException();
//从服务端的数据流中读取数据并解析成Book对象返回到传参对象
if ((0!=_reply.readInt())) {
book.readFromParcel(_reply);
}
}
finally {
_reply.recycle();
_data.recycle();
}
}
结论:
定向 tag in 表示数据只能由客户端流向服务端,服务端将会收到客户端对象的完整数据,但是客户端对象不会因为服务端对传参的修改而发生变动。
out 表示数据只能由服务端流向客户端,服务端将会收到客户端对象,该对象不为空,但是它里面的字段为空,但是在服务端对该对象作任何修改之后客户端的传参对象都会同步改动。
inout 则表示数据可以在服务端与客户端之间双向流通。其中的数据流向是针对在客户端中的那个传入方法的对象而言的。服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
四、参考文章:
这篇文章的其实是对AIDL的in、out、inout这三个定向tag的一个总结,之前一直不了解这三个定向tag的意思,知道看到你真的理解AIDL中的in,out,inout么?这篇文章后,顺着作者的思路下来,并且自己做一遍实验和看一遍源码,才对这三个定向tag有一个清晰的了解。