(转载)你真的理解Android AIDL中的in,out,inout么?
前言
这其实是一个很小的知识点,大部分人在使用AIDL的过程中也基本没有因为这个出现过错误,正因为它小,所以在大部分的网上关于AIDL的文章中,它都被忽视了——或者并没有,但所占篇幅甚小,且基本上都是官方文档的译文,译者读者其实都不知其然。这几天在研究AIDL,偏偏我又是个执拗的性子,遇着不清不楚的东西就是想把它捋清楚,就下了些功夫研究了下AIDL中的定向tag,研究了下它的 in , out , inout 。
整理而成此博文。
1、概述
首先要说的是定向tag是AIDL语法的一部分,而 in , out , inout 是三个定向tag,所以读者要有一定的对于Android中AIDL的了解,关于AIDL相关的知识大家可以参考这篇博文:Android:学习AIDL,这一篇文章就够了(上) 。另外,这篇文章基本上可以说是我研究这个东西的心路历程,可能会有些絮叨,请各位看官见谅。
2、官方文档
Android官网上在讲到AIDL的地方关于定向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 。基本参数的定向tag默认是并且只能是 in 。对于这个定向tag我的心里是有一些疑问的。首先,数据流通的方式是指什么?其次, in , out , inout 分别代表了什么,它们有什么区别?很显然,官方文档并没有把这些东西交代清楚。那么接下来我就只能自己把这些问题搞清楚了。
本着不重复造轮子的原则,我首先在 Google 上查找了一下这方面的资料,看能不能有比较好的答案,结果确实找到了一些说法——但是进过论证,似乎都有一些漏洞。所以没办法,只能自己开始着手研究它是怎么回事了。
3、开始研究
3.1、输入输出?NO!
首先第一个跑到我脑海里的猜测就是:in表示输入,也即方法的传参,out表示输出,也即方法的返回值。有这样的猜测很合情合理,但是这样的猜测很不合情合理。原因如下:
- 文档里说了,基本参数的定向tag默认且只能是 in ,但是很显然,基本参数既有可能是方法的传参,也有可能是方法的返回值,所以这个猜测本身就站不住脚。
- 如果 in 表示输入, out 表示输出,那 inout 应该表示什么?
- 进过实测,定向tag只能用来修饰AIDL中方法的输入参数,并不能修饰其返回值。
综合以上几点考虑,基本可以排除这种猜测的可能性。
3.2、way?way!
排除掉上面的想法后,我开始进一步猜测 in , out ,inout 可能代表的意义——在某一个瞬间我灵光一闪:除了输入输出,in ,out 还总是被用来表示数据的流向!同时我惊觉,似乎我对官方文档的理解有一些偏差:way有方法的意思,但是它也有道路的意思!如果按照道路理解,那么官网的译文就应当是:所有的非基本参数都需要一个定向tag来指出数据的流向,不管是 in , out , 还是 inout 。基本参数的定向tag默认是并且只能是 in 。
如果按照这个意思的话,似乎它们的含义就很清晰了:in 与 out 分别表示客户端与服务端之间的两条单向的数据流向,而 inout 则表示两端可双向流通数据。基于这种猜测,我设计了一个实验来验证它,AIDL文件是这样的:
// Book.aidl
package com.lypeer.ipcclient;
parcelable Book;
// BookManager.aidl
package com.lypeer.ipcclient;
import com.lypeer.ipcclient.Book;
interface BookManager {
//保证客户端与服务端是连接上的且数据传输正常
List<Book> getBooks();
//通过三种定位tag做对比试验,观察输出的结果
Book addBookIn(in Book book);
Book addBookOut(out Book book);
Book addBookInout(inout Book book);
}
对其中的AIDL的语法不熟悉的,可以再去温故一下:Android:学习AIDL,这一篇文章就够了(上) ,我这里就不赘叙了。我主要讲一下实验的思路。可以看到,有定位tag的那三个方法它们的传参和返回值都是 Book 对象(Book是我自定义的一个实现了Parcelable的类,里面只有两个参数,String name 和 int price),并且它们的定位tag都不一样,接下来我会在客户端调用这三个方法,然后分别在客户端和服务端打印相关信息,从而验证我的猜想。下面贴上客户端和服务端的代码:
/**
* 客户端的AIDLActivity.java
* 由于测试机的无用debug信息太多,故log都是用的e
*
* Created by lypeer on 2016/7/17.
*/
public class AIDLActivity extends AppCompatActivity {
//由AIDL文件生成的Java类
private BookManager mBookManager = null;
//标志当前与服务端连接状况的布尔值,false为未连接,true为连接中
private boolean mBound = false;
//包含Book对象的list
private List<Book> mBooks;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}
/**
* 按钮的点击事件,点击之后调用服务端的addBookIn方法
*
* @param view
*/
public void addBookIn(View view) {
//如果与服务端的连接处于未连接状态,则尝试连接
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研发录In");
book.setPrice(30);
try {
//获得服务端执行方法的返回值,并打印输出
Book returnBook = mBookManager.addBookIn(book);
Log.e(getLocalClassName(), returnBook.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void addBookOut(View view) {
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研发录Out");
book.setPrice(30);
try {
Book returnBook = mBookManager.addBookOut(book);
Log.e(getLocalClassName(), returnBook.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void addBookInout(View view) {
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "当前与服务端处于未连接状态,正在尝试重连,请稍后再试", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研发录Inout");
book.setPrice(30);
try {
Book returnBook = mBookManager.addBookInout(book);
Log.e(getLocalClassName(), returnBook.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 尝试与服务端建立连接
*/
private void attemptToBindService() {
Intent intent = new Intent();
intent.setAction("com.lypeer.aidl");
intent.setPackage("com.lypeer.ipcserver");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "service connected");
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
mBound = false;
}
};
}
客户端这边的思路是很清晰的,不管怎么样,先连接上服务端再说,连接上之后再分别的调用AIDL中定义的三个方法,然后观察返回值的变化。接下来贴上服务端:
/**
* 服务端的AIDLService.java
*
* Created by lypeer on 2016/7/17.
*/
public class AIDLService extends Service {
public final String TAG = this.getClass().getSimpleName();
//包含Book对象的list
private List<Book> mBooks = new ArrayList<>();
//由AIDL文件生成的BookManager
private final BookManager.Stub mBookManager = new BookManager.Stub() {
@Override
public List<Book> getBooks() throws RemoteException {
synchronized (this) {
Log.e(TAG, "invoking getBooks() method , now the list is : " + mBooks.toString());
if (mBooks != null) {
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public Book addBookIn(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if(book == null){
Log.e(TAG , "Book is null in In");
book = new Book();
}
//尝试修改book的参数,主要是为了观察其到客户端的反馈
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,观察客户端传过来的值
Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
return book;
}
}
@Override
public Book addBookOut(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if(book == null){
Log.e(TAG , "Book is null in Out");
book = new Book();
}
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
return book;
}
}
@Override
public Book addBookInout(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if(book == null){
Log.e(TAG , "Book is null in Inout");
book = new Book();
}
book.setPrice(2333);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
Log.e(TAG, "invoking addBooks() method , now the list is : " + mBooks.toString());
return book;
}
}
};
@Override
public void onCreate() {
Book book = new Book();
book.setName("Android开发艺术探索");
book.setPrice(28);
mBooks.add(book);
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format("on bind,intent = %s", intent.toString()));
return mBookManager;
}
}
同样脉络是很清楚的,首先接受客户端连接的请求,并把服务端处理好的BookManager.Stub的IBinder接口回传给客户端。在BookManager.Stub里面实现的方法里面,主要是接收客户端传过来的Book对象,并试图对其进行修改,然后把修改过的对象再传回去。
通过这样的实验,我们可以监测到客户端的数据流向服务端的情况,也可以监测到服务端的数据流向客户端的情况。根据这些数据,就可以验证上面我们的猜测的正确与否。根据我们的猜测,in , out , inout ,实际上是标志数据的流向的,那么这样的话用 in 或者 out 标志的数据应该只能单向传输,反向无效,而 inout 的数据则可以双向传输。结果数据是不是显示这样的特征的呢?
首先把这两个应用都装到手机上,然后都打开,并且客户端依次执行 addBookIn() , addBookOut() , addBookInout() 的点击事件,最后得到的两端的 log 信息分别是这样的:
//服务端的 log 信息,我把无用的信息头去掉了,然后给它编了个号
1,on bind,intent = Intent { act=com.lypeer.aidl pkg=com.lypeer.ipcserver }
2,invoking getBooks() method , now the list is : [name : Android开发艺术探索 , price : 28]
3,on bind,intent = Intent { act=com.lypeer.aidl pkg=com.lypeer.ipcserver }
4,invoking getBooks() method , now the list is : [name : Android开发艺术探索 , price : 28]
5,invoking addBooks() method , now the list is : [name : Android开发艺术探索 , price : 28, name : APP研发录In , price : 2333]
6,invoking addBooks() method , now the list is : [name : Android开发艺术探索 , price : 28, name : APP研发录In , price : 2333, name : null , price : 2333]
7,invoking addBooks() method , now the list is : [name : Android开发艺术探索 , price : 28, name : APP研发录In , price : 2333, name : null , price : 2333, name : APP研发录Inout , price : 2333]
可以看到,服务端的 log 信息基本上是符合预期的。前四行是客户端在绑定服务端,list里面的那个元素是初始化进去的,可以不用管它。后三行分别体现了当客户端在调用 addBookIn() , addBookOut() , addBookInout() 方法的时候服务端接收到的数据:在 in 和 inout 作为定向 tag 的方法里,服务端能够正常的接收到客户端传过来的数据,但是在用 out 作为定向 tag 的方法里,服务端受到的是一个空的 Book 对象!可是明明三个方法都是传的具有相同参数的 Book 对象过来!通过服务端的数据,结合之前的猜测,我们基本可以确定,之前的猜测是正确的,并且 in 作为定向 tag 表示数据只能由客户端流向服务端,out 反之,inout 则为数据可以双向流通。如果是这样的话,那么客户端的 log 信息我们也可以有一些猜测了:既然 in 表示数据只能由客户端流向服务端,那么客户端里它的返回值应当是一个空的 Book 对象了;而 out 和 inout 作为定向 tag 的方法里它们的返回值则应当是与服务端里一样的。那么是不是这样的呢?看一下:
//客户端的 log 信息,我把无用的信息头去掉了,然后给它编了个号
1,service connected
2,[name : Android开发艺术探索 , price : 28]
3,service connected
4,[name : Android开发艺术探索 , price : 28]
5,APP研发录In , price : 2333
6,name : null , price : 2333
7,name : APP研发录Inout , price : 2333
同样,前四行是连接的信息,后面三行则是调用addBookIn() , addBookOut() , addBookInout() 方法的时候服务端返回的数据。结果这三行数据都和服务端的数据一模一样!既然数据出现了问题,那么很显然的,前面的推测也肯定有问题。
3.3、数据流向!
我们再来捋一捋思路。首先,从服务端得到的数据来看,确实用 out 作为定向 tag 的方法他的数据是不能够从客户端流向服务端的——服务端收到的是一个空的对象!这说明定向 tag 与数据的流向有关系这个大的方向是没有问题的。那么问题出在哪里呢?出在怎样看待数据流向这件事情上。之前我把数据流向简单的看作了方法的参数输入和返回值输出,现在想来是有些问题的:
- 如果将数据从服务端流向客户端看成是方法将返回值传回客户端,那么为什么不将 out 设计成写在返回值前面呢?还要一个根本没有用的输入干嘛?设计这门语言的那些人那么腻害,没可能没想到这一点吧?
- AIDL里面的默认类型的定向 tag 默认且只能是 in ,难道它们只能作为参数输入,不能成为返回值?
所以,问题应该就处在把方法的返回值当作是数据从服务端流向客户端这件事上——虽然在某种意义上方法的返回值也可以说成是数据从服务端流向客户端,但是不是我们这里说的数据从服务端流向客户端——虽然有点绕,但是看到这里的读者应该是能明白我的意思的。那么到底应该如何理解数据从服务端流向客户端呢?现在方法的返回值已经被否决了,那么数据流回去的载体是什么呢——不管怎么样,数据总是要有一个载体的,不然我们怎么知道它已经回来了?既然载体不是方法返回来的对象,那么必然是在调用方法之前就已经存在的对象。虽然感觉很不可思议:我这个对象在客户端,而方法的实现是在服务端,那么它怎么能变动这个对象?但是既然是推导出来的结果,那么就做个试验看看不就清楚了。
要验证这个很简单,直接在客户端里面 log 输出服务端返回信息那里,把原本的输出 returnBook.toString() 改为 book.toString() 就可以了( book 对象是方法的传参),具体的代码就不贴了,只有一点点变动。如果上面的猜测是正确的,那么输出的结果应当是 in 为定向 tag 的方法处 book 对象的参数不变,而 out ,inout 为定向 tag 的方法处 book 对象的参数与在服务端的参数一致。接下来再看下实际的 log 值:
1,service connected
2,[name : Android开发艺术探索 , price : 28]
3,name : APP研发录In , price : 30
4, name : null , price : 2333
5,name : APP研发录Inout , price : 2333
同样后三行表示了调用addBookIn() , addBookOut() , addBookInout() 方法之后 book 对象的参数。可以看到,输出的 log 信息终于和我前面预计的结果一致了!这说明前面的猜测是正确的!
3.4、得出结论
到这里基本上就可以下结论了:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
4、源码分析
上面我们通过猜测分析,设计实验等等手段得到了一个结论,那么接下来我们将进行源码分析,来看看在理论上能不能为我们的结论提供证明。首先我找到了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);
//很容易看出来,_arg0就是输入的book对象
com.lypeer.ipcclient.Book _arg0;
//从输入的_data流中读取book数据,并将其赋值给_arg0
if ((0 != data.readInt())) {
_arg0 = com.lypeer.ipcclient.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//在这里才是真正的开始执行实际的逻辑,调用服务端写好的实现
this.addBookIn(_arg0);
//执行完方法之后就结束了,没有针对_reply流的操作,所以客户端不会同步服务端的变化
reply.writeNoException();
return true;
}
case TRANSACTION_addBookOut: {
data.enforceInterface(DESCRIPTOR);
com.lypeer.ipcclient.Book _arg0;
//可以看到,用out作为定向tag的方法里,根本没有从_data里读取book对象的操作,
//而是直接new了一个book对象,这就是为什么服务端收不到客户端传过来的数据
_arg0 = new com.lypeer.ipcclient.Book();
//执行具体的事物逻辑
this.addBookOut(_arg0);
reply.writeNoException();
//在这里,_arg0是方法的传入参数,故服务端的实现里对传参做出的任何修改,
//都会在_arg0中有所体现,将其写入_reply流,就有了将这些修改传回客户端的前提
if ((_arg0 != null)) {
reply.writeInt(1);
_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);
}
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 {
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;
}
//通过三种定位tag做对比试验,观察输出的结果
@Override
public void addBookIn(com.lypeer.ipcclient.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是否为空,
// 如果为空,则_data写入int值1,将其book写入_data中
// 如果不为空,则_data写入int值0
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//之后直接调用transact()方法,将方法的编码,
// _data(包含从客户端流向服务端的book流),
// _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 {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
//在定向tag为out的方法里,没有将book对象写入_data流的操作
mRemote.transact(Stub.TRANSACTION_addBookOut, _data, _reply, 0);
_reply.readException();
//与tag为in的方法里面不同的是,在执行transact方法之后,
//还有针对_reply的操作,并且将book赋值为_reply流中的数据
if ((0 != _reply.readInt())) {
book.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void addBookInout(com.lypeer.ipcclient.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);
//定向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();
}
}
}
在 AIDL 文件生成的 java 文件中,在进行远程调用的时候基本的调用顺序是先从 Proxy 类中调用相关方法,然后在这些方法中调用 transact() 方法,这个时候 Stub 中的 onTransact() 方法就会被调用,然后在这个方法里面再调用具体的业务逻辑的方法——当然,在这几个方法调用的过程中,总是会有一些关于数据的写入读出的操作,因为这些是跨线程操作,必须将数据序列化传输。读者看代码以及看注释的时候,最好跟着这条方法调用的线来,这样的话对于这整体数据的流向会清晰很多,也更加简明易读。
通过分析源码,我们可以很轻易的得出和之前分析的时候一样的结论,这样一来,基本上 AIDL 中定向 tag 是什么,in , out , inout 它们分别表示什么,有些什么区别这些问题,也就迎刃而解了。
结语
首先还是对于复述一遍得出的结论:AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。(没错,这就是从上面复制粘粘的:))
最后,再说两个问题。
一个是可能有些读者不太明白,为什么不一上来就看源码?那样得出的结论必然是对的!确实如此。但是一方面,在通过探究得出一个结论之后去看源码,和直接去看源码,难度是不一样的——一开始就去看源码,未必看得懂,即算看得懂,未必能得出正确的结论——这话听起来似乎有些匪夷所思,但是那些经常看源码的同学应该是会有相同的体悟的。另一方面,源码,已经是成品了,我们去看它也许能够得出结论,但是很难得到那种作者在设计这个东西的时候的心路历程,那种在不同方案中取舍,最后选择了最优方案的心路历程——没有感受到这个,那么我觉得也许我们还需要在这个东西上面再多花些功夫来静下心的研究。
再就是这篇文章其实更多的想呈现的是那种对于技术的探究的态度。这个点只是一个很小的点,但是我找了很多的文章,很多的网站都没有找到一个很好的答案,这是为什么?是因为大家都没有去好好的静下心来研究它。花点时间,每个人都可以得出相同的答案,但是大多数人匆匆忙忙的看见了它,又匆匆忙忙的忽略了它——或者随意的翻查一下,似乎网上大家都说的挺有道理的,那么就这样了吧。这样是很没道理的。
文中相关代码可点击 传送门 下载。
来自:http://www.jianshu.com/p/ddbb40c7a251
扩展阅读
Android:学习AIDL,这一篇文章就够了(上)
Android:学习AIDL,这一篇文章就够了(下)
Android的Service深入分析(全)
使用AIDL实现Android的跨进程通信
Android IPC机制(二):AIDL的基本使用方法
为您推荐
Android:学习AIDL,这一篇文章就够了(上)
Android:学习AIDL,这一篇文章就够了(下)
Struts2-Spring和Hibernate整合
高质量Node.js微服务的编写和部署
JSP知识点总结
更多
(转载)你真的理解Android AIDL中的in,out,inout么?的更多相关文章
- 深入理解Android开发中的CoordinatorLayout Behavior
在使用Android设计支持库(Android Design Support Library)时,很难避开CoordinatorLayout:设计库中有很多视图都需要CoordinatorLayout ...
- 【转载】快速理解android View的测量onMeasure()与MeasureSpec
笔者之前有一篇文章已经使用onMeasure()解决了listview与scollview的显示冲突问题,博客地址如下: onMeasure简单方法 完美解决ListView与ScollView冲突问 ...
- 理解Android系统的进程间通信原理(二)----RPC机制
理解Android系统中的轻量级解决方案RPC的原理,需要先回顾一下JAVA中的RMI(Remote Method Invocation)这个易于使用的纯JAVA方案(用来实现分布式应用).有关RMI ...
- [转载] 深入理解Android之Java虚拟机Dalvik
本文转载自: http://blog.csdn.net/innost/article/details/50377905 一.背景 这个选题很大,但并不是一开始就有这么高大上的追求.最初之时,只是源于对 ...
- Android菜鸟的成长笔记(4)——你真的理解了吗?
原文:Android菜鸟的成长笔记(4)--你真的理解了吗? 在上一篇中我们查看了QQ的apk源文件中的布局结构,并仿照QQ完成了我们第一个应用的界面,详细请看<Android菜鸟的成长笔记&g ...
- 深入理解Android中View
文章目录 [隐藏] 一.View是什么? 二.View创建的一个概述: 三.View的标志(Flag)系统 四.MeasureSpec 五.几个重要方法简介 5.1 onFinishInflate ...
- AIDL在android系统中的作用
AIDL,Android Interface definition language的缩写,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口.最近看了下AIDL在A ...
- 【转】你真的理解Python中MRO算法吗?
你真的理解Python中MRO算法吗? MRO(Method Resolution Order):方法解析顺序. Python语言包含了很多优秀的特性,其中多重继承就是其中之一,但是多重继承会引发很多 ...
- 理解Android系统的进程间通信原理(一)----RPC中的代理模式
Android系统中的进程间通信是通过一个轻量级的RPC(Remote Procedure Call远程进程调用)和AIDL(Android Interface Definination Langua ...
随机推荐
- Javascript 正则表达式简述
本文地址:http://www.cnblogs.com/blackmanba/p/regular-expressions-introduce.html 或者 http://forkme.info/re ...
- dp入门—数塔
在讲述DP算法的时候,一个经典的例子就是数塔问题,它是这样描述的: 有如下所示的数塔,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少? 已经告诉你了,这是个DP的题 ...
- android handler传递数据
起因:在android使用get请求获取验证码时需要重开一个线程,这就造成了我无法获取到从服务器后台返回的数据 解决方法:创建全局变量,将返回的数据解析后返回给handler,再在handler中将数 ...
- Web Service 实践之 REST vs RPC
作者:朱涛 出处:http://www.cnblogs.com/mindsbook/archive/2009/11/17/web_service_RESTvsRPC.html Web Service ...
- 【洛谷4941】War2 状压Dp
简单的状压DP,和NOIP2017 Day2 找宝藏 代码几乎一样.(比那个稍微简单一点) f[i][j] ,i代表点的状态,j是当前选择的点,枚举上一个选到的点k 然后从f[i-(1<< ...
- cf掉分记——Avito Code Challenge 2018
再次作死的打了一次cf的修仙比赛感觉有点迷.. 还好掉的分不多(原本就太低没法掉了QAQ) 把会做的前三道水题记录在这.. A: Antipalindrome emmmm...直接暴力枚举 code: ...
- 好文应该收藏-----redis 配置自启动
话不多说,请看原作者笔记,亲测,可用 https://blog.csdn.net/qq_42810276/article/details/81296012
- pytorch 5 classification 分类
import torch from torch.autograd import Variable import torch.nn.functional as F import matplotlib.p ...
- NOIP2018提高组省一冲奖班模测训练(四)
NOIP2018提高组省一冲奖班模测训练(四) 这次比赛只AC了第一题,而且花了40多分钟,貌似是A掉第一题里面最晚的 而且还有一个半小时我就放弃了…… 下次即使想不出也要坚持到最后 第二题没思路 第 ...
- 可编辑div,将光标定位到文本之后
类似qq回复一样,某人评论之后,在对评论进行回复之后,将光标定位到文本之后: function set_focus() { el=document.getElementById('guestbook_ ...