AIDL 定向tag IPC Parcelable【综合案例】
2017-11-02 09:56
239 查看
Demo地址客户端:https://github.com/baiqiantao/AIDLTagDemo.git
服务端:https://github.com/baiqiantao/AIDLTagDemo2.git
定向tag官方描述
Android官网上在讲到AIDL的地方关于定向tag是这样介绍的:
首先,数据流通的方式是指什么?
其次, in , out , inout 分别代表了什么,它们有什么区别?
官方文档并没有把这些东西交代清楚,那么接下来我就只能自己把这些问题搞清楚了。
有这样的猜测很合情合理,但是这样的猜测很不合情合理,原因如下:
文档里说了,基本参数的定向tag默认且只能是 in ,但是很显然,基本参数既有可能是方法的传参,也有可能是方法的返回值,所以这个猜测本身就站不住脚。
如果 in 表示输入, out 表示输出,那 inout 应该表示什么?
经过实测,定向tag只能用来修饰AIDL中方法的输入参数,并不能修饰其返回值。
综合以上几点考虑,完全可以排除这种猜测的可能性。
如果按照道路理解,那么官网的译文就应当是:...都需要一个定向tag来指出数据的流向...。如果按照这个意思的话,似乎它们的含义就很清晰了:in 与 out 分别表示客户端与服务端之间的两条单向的数据流向,而 inout 则表示两端可双向流通数据。
基于这种猜测,我设计了下面这个实验来验证它。
Book.aidl
服务端
服务端结构
服务端的逻辑是,首先接受客户端连接的请求,并把服务端处理好的BookManager.Stub的IBinder接口回传给客户端。在BookManager.Stub里面实现的方法里面,主要是接收客户端传过来的Book对象,并试图对其进行修改,然后把修改过的对象再传回去。
服务端的代码
客户端
客户端结构
客户端这边的思路是,先连接上服务端再说,连接上之后再分别的调用AIDL中定义的三个方法,然后观察返回值的变化。
客户端代码
首先把这两个应用都装到手机上,然后都打开,并且客户端依次执行 getBooks(),addBookIn() , addBookOut() ,addBookInout() 方法,最后得到的两端的 log 信息。
服务端的 log 信息:
可以看到,服务端的 log 信息是符合预期的。观察当客户端在调用addBookIn(),addBookOut(),addBookInout()方法时服务端接收到的数据,可以发现:虽然三个方法都传递了一个 Book 对象过来,在用 in 和 inout 作为定向 tag 的方法里,服务端能够正常的接收到客户端传过来的数据;但是在用 out 作为定向 tag 的方法里,服务端接收到的是一个参数为空的 Book 对象!
通过服务端的数据,结合之前的猜测,我们基本可以确定,之前的猜测是正确的。
如果是这样的话,那么客户端的 log 信息我们也可以有一些猜测了:既然 in 表示数据只能由客户端流向服务端,那么客户端里它的返回值应当是一个参数为空的 Book 对象了;而在用 out 和 inout 作为定向 tag 的方法里,它们的返回值则应当是与服务端里一样的。
那么是不是这样的呢?看一下客户端的 log 信息:
既然数据出现了问题,那么很显然的,前面的推测也肯定是有问题。
出在怎样看待数据流向这件事情上。
之前我把数据流向简单的看作了方法的参数输入和返回值输出,现在想来是有些问题的:
如果将数据从服务端流向客户端看成是方法将返回值传回客户端,那么为什么不将 out 设计成写在返回值前面呢?还要一个根本没有用的输入干嘛?设计这门语言的那些人那么腻害,没可能没想到这一点吧?
AIDL里面的默认类型的定向 tag 默认且只能是 in ,难道它们只能作为参数输入,不能成为返回值?
所以,问题应该就出在把方法的返回值当作是数据从服务端流向客户端这件事上,虽然在某种意义上方法的返回值也可以说成是数据从服务端流向客户端,但是不是我们这里说的数据从服务端流向客户端,虽然有点绕,但是看到这里的读者应该是能明白我的意思的。那么到底应该如何理解数据从服务端流向客户端呢?现在方法的返回值已经被否决了,那么数据流回去的载体是什么呢?既然载体不是方法返回来的对象,那么必然是在调用方法之前就已经存在的对象。
你可能感觉很不可思议:我这个对象在客户端,而方法的实现是在服务端,那么它怎么能变动这个对象?但是既然是推导出来的结果,那么就做个试验看看不就清楚了。
要验证这个很简单,直接在客户端里面 log 输出服务端返回信息那里,把原本的输出 returnBook.toString() 改为 book.toString() 就可以了。
接下来再看下实际的 log 值:
其中,数据流向是针对在客户端中的那个传入方法的对象而言的。
in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;
out 的话表现为服务端将会接收到那个对象的参数为空的对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;
inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
首先我找到了 as 根据 BookManager.aidl 文件生成的 BookManager.java 文件,然后从中抽取了相关的代码片段:
先从 Proxy 类中调用对应的方法
然后在这些方法中调用 transact() 方法
然后 Stub 中的 onTransact() 方法就会被调用
然后在这个方法里面再调用具体的业务逻辑的方法
当然,在这几个方法调用的过程中,总是会有一些关于数据的写入读出的操作,因为这些是跨进程操作,必须将数据序列化传输。
通过分析源码,我们可以很轻易的得出和之前分析的时候一样的结论,这样一来,基本上 AIDL 中定向 tag 是什么,in , out , inout 它们分别表示什么,有些什么区别这些问题,也就迎刃而解了。
确实如此。
但是一方面,在通过探究得出一个结论之后去看源码,和直接去看源码,难度是不一样的。一开始就去看源码,未必看得懂,即算看得懂,未必能得出正确的结论。这话听起来似乎有些匪夷所思,但是那些经常看源码的同学应该是会有相同的体悟的。
另一方面,源码,已经是成品了,我们去看它也许能够得出结论,但是很难得到那种作者在设计这个东西的时候的心路历程,那种在不同方案中取舍,最后选择了最优方案的心路历程。没有感受到这个,那么我觉得也许我们还需要在这个东西上面再多花些功夫来静下心的研究。
2017-11-2
来自为知笔记(Wiz)
服务端:https://github.com/baiqiantao/AIDLTagDemo2.git
定向tag官方描述
Android官网上在讲到AIDL的地方关于定向tag是这样介绍的:All non-primitive parameters require a directional tag indicating which way the data goes . //所有的非基本数据类型的参数都需要一个定向tag来指出数据流通的方式(数据的流向) Either in , out , or inout . //可以是 in , out ,或者 inout Primitives are in by default , and connot be otherwise . //基本数据类型参数的定向tag默认是,并且只能是 in33 1
All non-primitive parameters require a directional tag indicating which way the data goes . //所有的非基本数据类型的参数都需要一个定向tag来指出数据流通的方式(数据的流向)2
Either in , out , or inout . //可以是 in , out ,或者 inout3
Primitives are in by default , and connot be otherwise . //基本数据类型参数的定向tag默认是,并且只能是 in对于这个定向tag我的心里是有一些疑问的:
首先,数据流通的方式是指什么?
其次, in , out , inout 分别代表了什么,它们有什么区别?
官方文档并没有把这些东西交代清楚,那么接下来我就只能自己把这些问题搞清楚了。
研究过程
揣测1、输入输出?NO!
首先第一个跑到我脑海里的猜测就是:in表示输入,也即方法的传参,out表示输出,也即方法的返回值。有这样的猜测很合情合理,但是这样的猜测很不合情合理,原因如下:
文档里说了,基本参数的定向tag默认且只能是 in ,但是很显然,基本参数既有可能是方法的传参,也有可能是方法的返回值,所以这个猜测本身就站不住脚。
如果 in 表示输入, out 表示输出,那 inout 应该表示什么?
经过实测,定向tag只能用来修饰AIDL中方法的输入参数,并不能修饰其返回值。
综合以上几点考虑,完全可以排除这种猜测的可能性。
揣测2、way方法?way道路!
排除掉上面的想法后,我开始进一步猜测 in , out ,inout 可能代表的意义,在某一个瞬间我灵光一闪:除了输入输出,in ,out 还总是被用来表示数据的流向!同时我惊觉,似乎我对官方文档的理解有一些偏差:way有方法的意思,但是它也有道路的意思!如果按照道路理解,那么官网的译文就应当是:...都需要一个定向tag来指出数据的流向...。如果按照这个意思的话,似乎它们的含义就很清晰了:in 与 out 分别表示客户端与服务端之间的两条单向的数据流向,而 inout 则表示两端可双向流通数据。
基于这种猜测,我设计了下面这个实验来验证它。
针对揣测2的测试过程
AIDL相关的文件
AIDL文件是这样的:Book.aidl
package com.bqt.aidl; parcelable Book;22 1
package com.bqt.aidl;2
parcelable Book;BookManager.aidl
package com.bqt.aidl; import com.bqt.aidl.Book; interface BookManager { /*保证客户端与服务端是连接上的且数据传输正常*/ List<Book> getBooks(); /*通过三种定位tag做对比试验,观察输出的结果*/ Book addBookIn(in Book book); Book addBookOut(out Book book); Book addBookInout(inout Book book); }1212 1
package com.bqt.aidl;2
import com.bqt.aidl.Book;34
interface BookManager {5
/*保证客户端与服务端是连接上的且数据传输正常*/6
List<Book> getBooks();78
/*通过三种定位tag做对比试验,观察输出的结果*/9
Book addBookIn(in Book book);10
Book addBookOut(out Book book);11
Book addBookInout(inout Book book);12
}实体类 Book.java
package com.bqt.aidl; import android.os.Parcel; import android.os.Parcelable; public class Book implements Parcelable { private String name; private int price; @Override public String toString() { return "{name=" + name + ",price=" + price + "}"; } public void readFromParcel(Parcel in) { this.name = in.readString(); this.price = in.readInt(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); dest.writeInt(this.price); } public Book() { } protected Book(Parcel in) { this.name = in.readString(); this.price = in.readInt(); } public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel source) { return new Book(source); } @Override public Book[] newArray(int size) { return new Book[size]; } }; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } }6868 1
package com.bqt.aidl;23
import android.os.Parcel;4
import android.os.Parcelable;56
public class Book implements Parcelable {78
private String name;9
private int price;1011
@Override12
public String toString() {13
return "{name=" + name + ",price=" + price + "}";14
}1516
public void readFromParcel(Parcel in) {17
this.name = in.readString();18
this.price = in.readInt();19
}2021
@Override22
public int describeContents() {23
return 0;24
}2526
@Override27
public void writeToParcel(Parcel dest, int flags) {28
dest.writeString(this.name);29
dest.writeInt(this.price);30
}3132
public Book() {33
}3435
protected Book(Parcel in) {36
this.name = in.readString();37
this.price = in.readInt();38
}3940
public static final Creator<Book> CREATOR = new Creator<Book>() {41
@Override42
public Book createFromParcel(Parcel source) {43
return new Book(source);44
}4546
@Override47
public Book[] newArray(int size) {48
return new Book[size];49
}50
};5152
public String getName() {53
return name;54
}5556
public void setName(String name) {57
this.name = name;58
}5960
public int getPrice() {61
return price;62
}6364
public void setPrice(int price) {65
this.price = price;66
}67
}68
服务端
服务端结构服务端的逻辑是,首先接受客户端连接的请求,并把服务端处理好的BookManager.Stub的IBinder接口回传给客户端。在BookManager.Stub里面实现的方法里面,主要是接收客户端传过来的Book对象,并试图对其进行修改,然后把修改过的对象再传回去。
服务端的代码
public class AIDLService extends Service { private List<Book> mBooks; @Override public void onCreate() { mBooks = new ArrayList<>(); Book book = new Book(); book.setName("你妹"); book.setPrice(28); mBooks.add(book); super.onCreate(); } @Override public IBinder onBind(Intent intent) { Log.i("bqt", "onBind"); return new MyBind(); } private class MyBind extends BookManager.Stub { @Override public List<Book> getBooks() throws RemoteException { synchronized (this) { Log.i("bqt", "【getBooks】" + mBooks.toString()); return mBooks; } } @Override public Book addBookIn(Book book) throws RemoteException { synchronized (this) { if (book == null) book = new Book(); //尝试修改book的参数,主要是为了观察其到客户端的反馈 book.setPrice(1111); if (!mBooks.contains(book)) mBooks.add(book); //打印mBooks列表,观察客户端传过来的值 Log.i("bqt", "【addBookIn】" + mBooks.toString()); return book; } } @Override public Book addBookOut(Book book) throws RemoteException { synchronized (this) { if (book == null) book = new Book(); book.setPrice(2222); if (!mBooks.contains(book)) mBooks.add(book); Log.i("bqt", "【addBookOut】 " + mBooks.toString()); return book; } } @Override public Book addBookInout(Book book) throws RemoteException { synchronized (this) { if (book == null) book = new Book(); book.setPrice(3333); if (!mBooks.contains(book)) mBooks.add(book); Log.i("bqt", "【addBookInout】" + mBooks.toString()); return book; } } } }6767 1
public class AIDLService extends Service {23
private List<Book> mBooks;45
@Override6
public void onCreate() {7
mBooks = new ArrayList<>();8
Book book = new Book();9
book.setName("你妹");10
book.setPrice(28);11
mBooks.add(book);12
super.onCreate();13
}1415
@Override16
public IBinder onBind(Intent intent) {17
Log.i("bqt", "onBind");18
return new MyBind();19
}2021
private class MyBind extends BookManager.Stub {22
@Override23
public List<Book> getBooks() throws RemoteException {24
synchronized (this) {25
Log.i("bqt", "【getBooks】" + mBooks.toString());26
return mBooks;27
}28
}2930
@Override31
public Book addBookIn(Book book) throws RemoteException {32
synchronized (this) {33
if (book == null) book = new Book();3435
//尝试修改book的参数,主要是为了观察其到客户端的反馈36
book.setPrice(1111);37
if (!mBooks.contains(book)) mBooks.add(book);3839
//打印mBooks列表,观察客户端传过来的值40
Log.i("bqt", "【addBookIn】" + mBooks.toString());41
return book;42
}43
}4445
@Override46
public Book addBookOut(Book book) throws RemoteException {47
synchronized (this) {48
if (book == null) book = new Book();49
book.setPrice(2222);50
if (!mBooks.contains(book)) mBooks.add(book);51
Log.i("bqt", "【addBookOut】 " + mBooks.toString());52
return book;53
}54
}5556
@Override57
public Book addBookInout(Book book) throws RemoteException {58
synchronized (this) {59
if (book == null) book = new Book();60
book.setPrice(3333);61
if (!mBooks.contains(book)) mBooks.add(book);62
Log.i("bqt", "【addBookInout】" + mBooks.toString());63
return book;64
}65
}66
}67
}
客户端
客户端结构客户端这边的思路是,先连接上服务端再说,连接上之后再分别的调用AIDL中定义的三个方法,然后观察返回值的变化。
客户端代码
public class MainActivity extends ListActivity { private BookManager mBookManager; private MyServiceConnection mServiceConnection; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String[] array = {"绑定服务", "解绑服务", "getBooks", "addBookIn", "addBookOut", "addBookInout", "",}; setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array)))); mServiceConnection = new MyServiceConnection(); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { switch (position) { case 0: Intent intent = new Intent(); intent.setAction("com.bqt.service.aidl"); intent.setPackage("com.bqt.aidl2"); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); break; case 1: if (mServiceConnection != null) unbindService(mServiceConnection); else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show(); mBookManager = null; break; case 2: getBooks(); break; case 3: addBookIn(); break; case 4: addBookOut(); break; case 5: addBookInout(); break; } } private void getBooks() { if (mBookManager != null) { try { Log.i("bqt", "【客户端getBooks】" + mBookManager.getBooks().toString()); } catch (RemoteException e) { e.printStackTrace(); } } else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show(); } public void addBookIn() { if (mBookManager != null) { Book book = new Book(); book.setName("包青天In"); book.setPrice(10); try { //获得服务端执行方法的返回值,并打印输出 Book returnBook = mBookManager.addBookIn(book); Log.i("bqt", "【客户端addBookIn】" + returnBook.toString()); } catch (RemoteException e) { e.printStackTrace(); } } else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show(); } public void addBookOut() { if (mBookManager != null) { Book book = new Book(); book.setName("包青天Out"); book.setPrice(20); try { Book returnBook = mBookManager.addBookOut(book); Log.i("bqt", "【客户端addBookOut】" + returnBook.toString()); } catch (RemoteException e) { e.printStackTrace(); } } else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show(); } public void addBookInout() { if (mBookManager != null) { Book book = new Book(); book.setName("包青天Inout"); book.setPrice(30); try { Book returnBook = mBookManager.addBookInout(book); Log.i("bqt", "【客户端addBookInout】" + returnBook.toString()); } catch (RemoteException e) { e.printStackTrace(); } } else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show(); } private class MyServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { Toast.makeText(MainActivity.this, "服务已连接", Toast.LENGTH_SHORT).show(); mBookManager = BookManager.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { Toast.makeText(MainActivity.this, "服务已断开", Toast.LENGTH_SHORT).show(); mBookManager = null; } } }114114 1
public class MainActivity extends ListActivity {2
private BookManager mBookManager;3
private MyServiceConnection mServiceConnection;45
protected void onCreate(Bundle savedInstanceState) {6
super.onCreate(savedInstanceState);7
String[] array = {"绑定服务",8
"解绑服务",9
"getBooks",10
"addBookIn",11
"addBookOut",12
"addBookInout",13
"",};14
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));15
mServiceConnection = new MyServiceConnection();16
}1718
@Override19
protected void onListItemClick(ListView l, View v, int position, long id) {20
switch (position) {21
case 0:22
Intent intent = new Intent();23
intent.setAction("com.bqt.service.aidl");24
intent.setPackage("com.bqt.aidl2");25
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);26
break;27
case 1:28
if (mServiceConnection != null) unbindService(mServiceConnection);29
else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show();30
mBookManager = null;31
break;32
case 2:33
getBooks();34
break;35
case 3:36
addBookIn();37
break;38
case 4:39
addBookOut();40
break;41
case 5:42
addBookInout();43
break;44
}45
}4647
private void getBooks() {48
if (mBookManager != null) {49
try {50
Log.i("bqt", "【客户端getBooks】" + mBookManager.getBooks().toString());51
} catch (RemoteException e) {52
e.printStackTrace();53
}54
} else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show();5556
}5758
public void addBookIn() {59
if (mBookManager != null) {60
Book book = new Book();61
book.setName("包青天In");62
book.setPrice(10);63
try {64
//获得服务端执行方法的返回值,并打印输出65
Book returnBook = mBookManager.addBookIn(book);66
Log.i("bqt", "【客户端addBookIn】" + returnBook.toString());67
} catch (RemoteException e) {68
e.printStackTrace();69
}70
} else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show();71
}7273
public void addBookOut() {74
if (mBookManager != null) {75
Book book = new Book();76
book.setName("包青天Out");77
book.setPrice(20);78
try {79
Book returnBook = mBookManager.addBookOut(book);80
Log.i("bqt", "【客户端addBookOut】" + returnBook.toString());81
} catch (RemoteException e) {82
e.printStackTrace();83
}84
} else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show();85
}8687
public void addBookInout() {88
if (mBookManager != null) {89
Book book = new Book();90
book.setName("包青天Inout");91
book.setPrice(30);92
try {93
Book returnBook = mBookManager.addBookInout(book);94
Log.i("bqt", "【客户端addBookInout】" + returnBook.toString());95
} catch (RemoteException e) {96
e.printStackTrace();97
}98
} else Toast.makeText(this, "还没绑定服务", Toast.LENGTH_SHORT).show();99
}100101
private class MyServiceConnection implements ServiceConnection {102
@Override103
public void onServiceConnected(ComponentName name, IBinder service) {104
Toast.makeText(MainActivity.this, "服务已连接", Toast.LENGTH_SHORT).show();105
mBookManager = BookManager.Stub.asInterface(service);106
}107108
@Override109
public void onServiceDisconnected(ComponentName name) {110
Toast.makeText(MainActivity.this, "服务已断开", Toast.LENGTH_SHORT).show();111
mBookManager = null;112
}113
}114
}
测试过程
根据我们的猜测,in , out , inout ,实际上是标志数据的流向的,那么这样的话用 in 或者 out 标志的数据应该只能单向传输,反向无效,而 inout 的数据则可以双向传输。结果数据是不是显示这样的特征的呢?首先把这两个应用都装到手机上,然后都打开,并且客户端依次执行 getBooks(),addBookIn() , addBookOut() ,addBookInout() 方法,最后得到的两端的 log 信息。
服务端的 log 信息:
com.bqt.aidl2 I/bqt: 【getBooks】[{name=你妹,price=28}]44 1
com.bqt.aidl2 I/bqt: 【addBookIn】[{name=你妹,price=28}, {name=包青天In,price=1111}]
com.bqt.aidl2 I/bqt: 【addBookOut】 [{name=你妹,price=28}, {name=包青天In,price=1111}, {name=null,price=2222}]//注意:新增加元素的name=null
com.bqt.aidl2 I/bqt: 【addBookInout】[{name=你妹,price=28}, {name=包青天In,price=1111}, {name=null,price=2222}, {name=包青天Inout,price=3333}]
com.bqt.aidl2 I/bqt: 【getBooks】[{name=你妹,price=28}]2
com.bqt.aidl2 I/bqt: 【addBookIn】[{name=你妹,price=28}, {name=包青天In,price=1111}]3
com.bqt.aidl2 I/bqt: 【addBookOut】 [{name=你妹,price=28}, {name=包青天In,price=1111}, {name=null,price=2222}]//注意:新增加元素的name=null4
com.bqt.aidl2 I/bqt: 【addBookInout】[{name=你妹,price=28}, {name=包青天In,price=1111}, {name=null,price=2222}, {name=包青天Inout,price=3333}]
可以看到,服务端的 log 信息是符合预期的。观察当客户端在调用addBookIn(),addBookOut(),addBookInout()方法时服务端接收到的数据,可以发现:虽然三个方法都传递了一个 Book 对象过来,在用 in 和 inout 作为定向 tag 的方法里,服务端能够正常的接收到客户端传过来的数据;但是在用 out 作为定向 tag 的方法里,服务端接收到的是一个参数为空的 Book 对象!
通过服务端的数据,结合之前的猜测,我们基本可以确定,之前的猜测是正确的。
如果是这样的话,那么客户端的 log 信息我们也可以有一些猜测了:既然 in 表示数据只能由客户端流向服务端,那么客户端里它的返回值应当是一个参数为空的 Book 对象了;而在用 out 和 inout 作为定向 tag 的方法里,它们的返回值则应当是与服务端里一样的。
那么是不是这样的呢?看一下客户端的 log 信息:
com.bqt.aidl I/bqt: 【客户端getBooks】[{name=你妹,price=28}]44 1
com.bqt.aidl I/bqt: 【客户端addBookIn】{name=包青天In,price=1111}//这里竟然不为空
com.bqt.aidl I/bqt: 【客户端addBookOut】{name=null,price=2222}//这里竟然为空
com.bqt.aidl I/bqt: 【客户端addBookInout】{name=包青天Inout,price=3333}
com.bqt.aidl I/bqt: 【客户端getBooks】[{name=你妹,price=28}]2
com.bqt.aidl I/bqt: 【客户端addBookIn】{name=包青天In,price=1111}//这里竟然不为空3
com.bqt.aidl I/bqt: 【客户端addBookOut】{name=null,price=2222}//这里竟然为空4
com.bqt.aidl I/bqt: 【客户端addBookInout】{name=包青天Inout,price=3333}卧槽!这些数据竟然和服务端的数据一模一样!这不对啊!
既然数据出现了问题,那么很显然的,前面的推测也肯定是有问题。
重新梳理揣测2
我们再来捋一捋思路。首先,从服务端得到的数据来看,确实用 out 作为定向 tag 的方法他的数据是不能够从客户端流向服务端的,这时服务端收到的是一个空的对象!这说明定向 tag 与数据的流向有关系这个大的方向是没有问题的,那么问题出在哪里呢?出在怎样看待数据流向这件事情上。
之前我把数据流向简单的看作了方法的参数输入和返回值输出,现在想来是有些问题的:
如果将数据从服务端流向客户端看成是方法将返回值传回客户端,那么为什么不将 out 设计成写在返回值前面呢?还要一个根本没有用的输入干嘛?设计这门语言的那些人那么腻害,没可能没想到这一点吧?
AIDL里面的默认类型的定向 tag 默认且只能是 in ,难道它们只能作为参数输入,不能成为返回值?
所以,问题应该就出在把方法的返回值当作是数据从服务端流向客户端这件事上,虽然在某种意义上方法的返回值也可以说成是数据从服务端流向客户端,但是不是我们这里说的数据从服务端流向客户端,虽然有点绕,但是看到这里的读者应该是能明白我的意思的。那么到底应该如何理解数据从服务端流向客户端呢?现在方法的返回值已经被否决了,那么数据流回去的载体是什么呢?既然载体不是方法返回来的对象,那么必然是在调用方法之前就已经存在的对象。
你可能感觉很不可思议:我这个对象在客户端,而方法的实现是在服务端,那么它怎么能变动这个对象?但是既然是推导出来的结果,那么就做个试验看看不就清楚了。
要验证这个很简单,直接在客户端里面 log 输出服务端返回信息那里,把原本的输出 returnBook.toString() 改为 book.toString() 就可以了。
Log.i("bqt", "【客户端addBookIn】" + returnBook.toString()); //returnBook是方法返回值 Log.i("bqt", "【客户端addBookIn:传进去的参数】" + book.toString()); //book 对象是方法的传参22 1
Log.i("bqt", "【客户端addBookIn】" + returnBook.toString()); //returnBook是方法返回值2
Log.i("bqt", "【客户端addBookIn:传进去的参数】" + book.toString()); //book 对象是方法的传参如果上面的猜测是正确的,那么输出的结果应当是:在用 in 为定向 tag 的方法处,book 对象的参数不变;而在用 out ,inout 为定向 tag 的方法处,book 对象的参数与在服务端的参数一致。
接下来再看下实际的 log 值:
com.bqt.aidl I/bqt: 【客户端addBookIn:传进去的参数】{name=包青天In,price=10}//没有变 com.bqt.aidl I/bqt: 【客户端addBookOut:传进去的参数】{name=null,price=2222}//有变 com.bqt.aidl I/bqt: 【客户端addBookInout:传进去的参数】{name=包青天Inout,price=3333}//有变3 1
com.bqt.aidl I/bqt: 【客户端addBookIn:传进去的参数】{name=包青天In,price=10}//没有变2
com.bqt.aidl I/bqt: 【客户端addBookOut:传进去的参数】{name=null,price=2222}//有变3
com.bqt.aidl I/bqt: 【客户端addBookInout:传进去的参数】{name=包青天Inout,price=3333}//有变可以看到,输出的 log 信息终于和我前面预计的结果一致了!这说明前面的猜测是正确的!
【结论】
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。
in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;
out 的话表现为服务端将会接收到那个对象的参数为空的对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;
inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
源码分析
上面我们通过猜测分析,设计实验等等手段得到了一个结论,那么接下来我们将进行源码分析,来看看在理论上能不能为我们的结论提供证明。首先我找到了 as 根据 BookManager.aidl 文件生成的 BookManager.java 文件,然后从中抽取了相关的代码片段:
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_getBooks: { data.enforceInterface(DESCRIPTOR); java.util.List<com.lypeer.ipcclient.Book> _result = this.getBooks(); reply.writeNoException(); reply.writeTypedList(_result); return true; } case TRANSACTION_addBookIn: { data.enforceInterface(DESCRIPTOR); com.lypeer.ipcclient.Book _arg0;//变量_arg0就是输入的book对象 //从输入的data流中读取book数据,并将其赋值给_arg0 if ((0 != data.readInt())) _arg0 = com.lypeer.ipcclient.Book.CREATOR.createFromParcel(data); else _arg0 = null; this.addBookIn(_arg0);//在这里才是真正的开始执行实际的逻辑,调用服务端写好的实现 reply.writeNoException(); //执行完方法之后就结束了,没有针对reply流的操作,所以客户端不会同步服务端的变化 return true; } case TRANSACTION_addBookOut: { data.enforceInterface(DESCRIPTOR); com.lypeer.ipcclient.Book _arg0;//同样,变量_arg0就是输入的book对象 //可以看到,此时没有从data里读取book对象的操作,而是直接new了一个book对象,这就是为什么服务端收不到客户端传过来的数据 _arg0 = new com.lypeer.ipcclient.Book(); this.addBookOut(_arg0);//同样,这里开始执行具体的逻辑 reply.writeNoException(); if ((_arg0 != null)) {//如果传入的参数不为空 reply.writeInt(1); //服务端的实现里对传参做出的任何修改,都会在_arg0中有所体现,将其写入_reply流,就有了将这些修改传回客户端的前提 _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else reply.writeInt(0); return true; } case TRANSACTION_addBookInout: { data.enforceInterface(DESCRIPTOR); com.lypeer.ipcclient.Book _arg0; //inout兼具上两个方法中的细节 if ((0 != data.readInt())) _arg0 = com.lypeer.ipcclient.Book.CREATOR.createFromParcel(data); else _arg0 = null; this.addBookInout(_arg0); reply.writeNoException(); if ((_arg0 != null)) { reply.writeInt(1); _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); } else reply.writeInt(0); return true; } } return super.onTransact(code, data, reply, flags); }5656 1
@Override2
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {3
switch (code) {4
case INTERFACE_TRANSACTION: {5
reply.writeString(DESCRIPTOR);6
return true;7
}8
case TRANSACTION_getBooks: {9
data.enforceInterface(DESCRIPTOR);10
java.util.List<com.lypeer.ipcclient.Book> _result = this.getBooks();11
reply.writeNoException();12
reply.writeTypedList(_result);13
return true;14
}15
case TRANSACTION_addBookIn: {16
data.enforceInterface(DESCRIPTOR);17
com.lypeer.ipcclient.Book _arg0;//变量_arg0就是输入的book对象18
//从输入的data流中读取book数据,并将其赋值给_arg019
if ((0 != data.readInt())) _arg0 = com.lypeer.ipcclient.Book.CREATOR.createFromParcel(data);20
else _arg0 = null;21
this.addBookIn(_arg0);//在这里才是真正的开始执行实际的逻辑,调用服务端写好的实现22
reply.writeNoException();23
//执行完方法之后就结束了,没有针对reply流的操作,所以客户端不会同步服务端的变化24
return true;25
}26
case TRANSACTION_addBookOut: {27
data.enforceInterface(DESCRIPTOR);28
com.lypeer.ipcclient.Book _arg0;//同样,变量_arg0就是输入的book对象29
//可以看到,此时没有从data里读取book对象的操作,而是直接new了一个book对象,这就是为什么服务端收不到客户端传过来的数据30
_arg0 = new com.lypeer.ipcclient.Book();31
this.addBookOut(_arg0);//同样,这里开始执行具体的逻辑32
reply.writeNoException();33
if ((_arg0 != null)) {//如果传入的参数不为空34
reply.writeInt(1);35
//服务端的实现里对传参做出的任何修改,都会在_arg0中有所体现,将其写入_reply流,就有了将这些修改传回客户端的前提36
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);37
} else reply.writeInt(0);38
return true;39
}40
case TRANSACTION_addBookInout: {41
data.enforceInterface(DESCRIPTOR);42
com.lypeer.ipcclient.Book _arg0;43
//inout兼具上两个方法中的细节44
if ((0 != data.readInt())) _arg0 = com.lypeer.ipcclient.Book.CREATOR.createFromParcel(data);45
else _arg0 = null;46
this.addBookInout(_arg0);47
reply.writeNoException();48
if ((_arg0 != null)) {49
reply.writeInt(1);50
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);51
} else reply.writeInt(0);52
return true;53
}54
}55
return super.onTransact(code, data, reply, flags);56
}
private static class Proxy implements com.lypeer.ipcclient.BookManager { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public java.util.List<com.lypeer.ipcclient.Book> getBooks() throws android.os.RemoteException { //_data为包含从客户端流向服务端的book流,_reply为包含从服务端流向客户端的数据流 android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List<com.lypeer.ipcclient.Book> _result; try { _data.writeInterfaceToken(DESCRIPTOR); mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArrayList(com.lypeer.ipcclient.Book.CREATOR); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public void addBookIn(com.lypeer.ipcclient.Book book) throws android.os.RemoteException { //_data为包含从客户端流向服务端的book流,_reply为包含从服务端流向客户端的数据流 android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); if ((book != null)) {//如果book不为空 _data.writeInt(1);//则_data写入int值1 book.writeToParcel(_data, 0);//并将book写入_data中 } else _data.writeInt(0);//如果book为空,则_data写入int值0 //调用transact()方法,将方法的code,_data,_reply传入 mRemote.transact(Stub.TRANSACTION_addBookIn, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } } @Override public void addBookOut(com.lypeer.ipcclient.Book book) throws android.os.RemoteException { //_data为包含从客户端流向服务端的book流,_reply为包含从服务端流向客户端的数据流 android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); //可以看到,在定向tag为out的方法里,没有将book对象写入_data流的操作,直接就开始调用transact()方法了 mRemote.transact(Stub.TRANSACTION_addBookOut, _data, _reply, 0); _reply.readException(); //与定向tag为in的方法不同的是,在执行transact方法之后,还有针对_reply的操作 if ((0 != _reply.readInt())) book.readFromParcel(_reply);//将book赋值为_reply流中的数据 } finally { _reply.recycle(); _data.recycle(); } } @Override public void addBookInout(com.lypeer.ipcclient.Book book) throws android.os.RemoteException { //_data为包含从客户端流向服务端的book流,_reply为包含从服务端流向客户端的数据流 android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); //定向tag为inout的方法里综合了上两个方法里的操作 if ((book != null)) { _data.writeInt(1); book.writeToParcel(_data, 0); } else _data.writeInt(0); mRemote.transact(Stub.TRANSACTION_addBookInout, _data, _reply, 0); _reply.readException(); if ((0 != _reply.readInt())) book.readFromParcel(_reply); } finally { _reply.recycle(); _data.recycle(); } } }x93 1
private static class Proxy implements com.lypeer.ipcclient.BookManager {2
private android.os.IBinder mRemote;34
Proxy(android.os.IBinder remote) {5
mRemote = remote;6
}78
@Override9
public android.os.IBinder asBinder() {10
return mRemote;11
}1213
public java.lang.String getInterfaceDescriptor() {14
return DESCRIPTOR;15
}1617
@Override18
public java.util.List<com.lypeer.ipcclient.Book> getBooks() throws android.os.RemoteException {19
//_data为包含从客户端流向服务端的book流,_reply为包含从服务端流向客户端的数据流20
android.os.Parcel _data = android.os.Parcel.obtain();21
android.os.Parcel _reply = android.os.Parcel.obtain();22
java.util.List<com.lypeer.ipcclient.Book> _result;23
try {24
_data.writeInterfaceToken(DESCRIPTOR);25
mRemote.transact(Stub.TRANSACTION_getBooks, _data, _reply, 0);26
_reply.readException();27
_result = _reply.createTypedArrayList(com.lypeer.ipcclient.Book.CREATOR);28
} finally {29
_reply.recycle();30
_data.recycle();31
}32
return _result;33
}3435
@Override36
public void addBookIn(com.lypeer.ipcclient.Book book) throws android.os.RemoteException {37
//_data为包含从客户端流向服务端的book流,_reply为包含从服务端流向客户端的数据流38
android.os.Parcel _data = android.os.Parcel.obtain();39
android.os.Parcel _reply = android.os.Parcel.obtain();40
try {41
_data.writeInterfaceToken(DESCRIPTOR);42
if ((book != null)) {//如果book不为空43
_data.writeInt(1);//则_data写入int值144
book.writeToParcel(_data, 0);//并将book写入_data中45
} else _data.writeInt(0);//如果book为空,则_data写入int值046
//调用transact()方法,将方法的code,_data,_reply传入47
mRemote.transact(Stub.TRANSACTION_addBookIn, _data, _reply, 0);48
_reply.readException();49
} finally {50
_reply.recycle();51
_data.recycle();52
}53
}5455
@Override56
public void addBookOut(com.lypeer.ipcclient.Book book) throws android.os.RemoteException {57
//_data为包含从客户端流向服务端的book流,_reply为包含从服务端流向客户端的数据流58
android.os.Parcel _data = android.os.Parcel.obtain();59
android.os.Parcel _reply = android.os.Parcel.obtain();60
try {61
_data.writeInterfaceToken(DESCRIPTOR);62
//可以看到,在定向tag为out的方法里,没有将book对象写入_data流的操作,直接就开始调用transact()方法了63
mRemote.transact(Stub.TRANSACTION_addBookOut, _data, _reply, 0);64
_reply.readException();65
//与定向tag为in的方法不同的是,在执行transact方法之后,还有针对_reply的操作66
if ((0 != _reply.readInt())) book.readFromParcel(_reply);//将book赋值为_reply流中的数据67
} finally {68
_reply.recycle();69
_data.recycle();70
}71
}7273
@Override74
public void addBookInout(com.lypeer.ipcclient.Book book) throws android.os.RemoteException {75
//_data为包含从客户端流向服务端的book流,_reply为包含从服务端流向客户端的数据流76
android.os.Parcel _data = android.os.Parcel.obtain();77
android.os.Parcel _reply = android.os.Parcel.obtain();78
try {79
_data.writeInterfaceToken(DESCRIPTOR);80
//定向tag为inout的方法里综合了上两个方法里的操作81
if ((book != null)) {82
_data.writeInt(1);83
book.writeToParcel(_data, 0);84
} else _data.writeInt(0);85
mRemote.transact(Stub.TRANSACTION_addBookInout, _data, _reply, 0);86
_reply.readException();87
if ((0 != _reply.readInt())) book.readFromParcel(_reply);88
} finally {89
_reply.recycle();90
_data.recycle();91
}92
}93
}在 AIDL 文件生成的 java 文件中,在进行远程调用的时候基本的调用顺序是:
先从 Proxy 类中调用对应的方法
然后在这些方法中调用 transact() 方法
然后 Stub 中的 onTransact() 方法就会被调用
然后在这个方法里面再调用具体的业务逻辑的方法
当然,在这几个方法调用的过程中,总是会有一些关于数据的写入读出的操作,因为这些是跨进程操作,必须将数据序列化传输。
通过分析源码,我们可以很轻易的得出和之前分析的时候一样的结论,这样一来,基本上 AIDL 中定向 tag 是什么,in , out , inout 它们分别表示什么,有些什么区别这些问题,也就迎刃而解了。
感悟
可能有些读者不太明白,为什么不一上来就看源码?那样得出的结论必然是对的!确实如此。
但是一方面,在通过探究得出一个结论之后去看源码,和直接去看源码,难度是不一样的。一开始就去看源码,未必看得懂,即算看得懂,未必能得出正确的结论。这话听起来似乎有些匪夷所思,但是那些经常看源码的同学应该是会有相同的体悟的。
另一方面,源码,已经是成品了,我们去看它也许能够得出结论,但是很难得到那种作者在设计这个东西的时候的心路历程,那种在不同方案中取舍,最后选择了最优方案的心路历程。没有感受到这个,那么我觉得也许我们还需要在这个东西上面再多花些功夫来静下心的研究。
2017-11-2
来自为知笔记(Wiz)
相关文章推荐
- IPC机制之AIDL传递Parcelable
- AIDL/IPC Android AIDL/IPC 进程通信机制——超具体解说及使用方法案例剖析(播放器)
- Using self-defined Parcelable objects during an Android AIDL RPC / IPC call
- AIDL/IPC Android AIDL/IPC 进程通信机制——超详细讲解及用法案例剖析(播放器)
- AIDL/IPC Android AIDL/IPC 进程通信机制——超详细讲解及用法案例剖析(播放器)
- AIDL中的定向tag
- 18_Android中Service的生命周期,远程服务,绑定远程服务,aidl服务调用,综合服务案例,编写一个应用程序调用远程支付宝远程服务场景
- IOS基础UI之(六)综合小案例-应用管理
- AIDL学习(一)---IPC通讯
- Android Binder机制的Native应用&&Android系统RPC与Binder && Android Service学习之AIDL, Parcelable和远程服务
- DOM综合案例、SAX解析、StAX解析、DOM4J解析
- android跨进程通信(IPC):使用AIDL
- IPC进程间通信 - AIDL+Binder
- android IPC : 告别手写parcelable
- IPC通信之AIDL
- IPC之AIDL(3)系统为我们做了什么
- Android IPC机制之AIDL的使用
- PHP 文件编程综合案例-文件上传的实现
- 我的Android进阶之旅------>Android Service学习之AIDL, Parcelable和远程服务
- xml学习笔记④PHP DOM--增删改查综合案例演示