你真的理解AIDL中的in,out,inout么?

你真的理解AIDL中的in,out,inout么?前言这其实是一个很小的知识点 大部分人在使用 AIDL 的过程中也基本没有因为这个出现过错误 正因为它小 所以在大部分的网上关于 AIDL 的文章中 它都被忽视了 或者并没有 但所占篇幅甚小 且基本上都是官方文档的译文 译者读者其实都不知其然 这几天在研究 AIDL 偏偏我又是个执拗的性子 遇着不清不楚的东西就是想把它捋清楚 就下了些功夫研究了下 AIDL 中的定向 tag 研究了下它的 in out i

前言

这其实是一个很小的知识点,大部分人在使用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 
  
    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 nameint 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 
  
    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 
  
    mBooks = 
   new ArrayList<>(); 
   //由AIDL文件生成的BookManager 
   private 
   final BookManager.Stub mBookManager = 
   new BookManager.Stub() { 
   @Override 
   public List 
    
    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 
  
    _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 
    
    getBooks() 
    throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.util.List 
    
      _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 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。(没错,这就是从上面复制粘粘的:))

最后,再说两个问题。

一个是可能有些读者不太明白,为什么不一上来就看源码?那样得出的结论必然是对的!确实如此。但是一方面,在通过探究得出一个结论之后去看源码,和直接去看源码,难度是不一样的——一开始就去看源码,未必看得懂,即算看得懂,未必能得出正确的结论——这话听起来似乎有些匪夷所思,但是那些经常看源码的同学应该是会有相同的体悟的。另一方面,源码,已经是成品了,我们去看它也许能够得出结论,但是很难得到那种作者在设计这个东西的时候的心路历程,那种在不同方案中取舍,最后选择了最优方案的心路历程——没有感受到这个,那么我觉得也许我们还需要在这个东西上面再多花些功夫来静下心的研究。

再就是这篇文章其实更多的想呈现的是那种对于技术的探究的态度。这个点只是一个很小的点,但是我找了很多的文章,很多的网站都没有找到一个很好的答案,这是为什么?是因为大家都没有去好好的静下心来研究它。花点时间,每个人都可以得出相同的答案,但是大多数人匆匆忙忙的看见了它,又匆匆忙忙的忽略了它——或者随意的翻查一下,似乎网上大家都说的挺有道理的,那么就这样了吧。这样是很没道理的。

文中相关代码可点击 传送门 下载。

谢谢大家。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/198157.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午3:39
下一篇 2026年3月26日 下午3:40


相关推荐

  • vs2019 注册码_vs2019怎么激活密钥

    vs2019 注册码_vs2019怎么激活密钥VS2019正式版注册码秘钥VisualStudio2019EnterpriseBF8Y8-GN2QH-T84XB-QVY3B-RC4DFVisualStudio2019ProfessionalNYWVH-HT4XC-R2WYW-9Y3CM-X4V3Y

    2022年10月13日
    4
  • 大学四年,靠着这些学习网站,我从挂科学渣变成了别人眼中的大神

    大学四年,靠着这些学习网站,我从挂科学渣变成了别人眼中的大神前记毕业合影时,导员笑着对我说“你可是我们系里的风云人物”。我也笑了,思绪却回到了四年前。还记得四年前,我从小县城里走出,来到学校的计算机学院报到,开始面对更大世界。一段时间之后,我见识到同学的眼界,视野,经历。大一学的语言课是C语言,当我还不知道C语言是什么的时候,同学高中时已经拿NOIP的奖牌拿到了手软。我深深惭愧,自愧不如,也很消极,有机会就偷懒,学期结束后C语言挂科了。大一生活是最…

    2022年8月23日
    8
  • Docker删除镜像和容器命令

    Docker删除镜像和容器命令1 先关闭容器 docker 中启动所有的容器命令 dockerstart dockerps a awk print 1 tail n 2 docker 中关闭所有的容器命令 dockerstop dockerps a awk print 1 tail n 2 2 查看当前正在运行的容器 dockerps 已退出的容器 可以使用如下命令进行查看 dockerps a 删除 docker 中的容器可以使用如下命令

    2026年3月18日
    2
  • 一文搞定7大流行后端框架:Spring、Netty、MyBatis、Hibernate、Dubbo…「建议收藏」

    一文搞定7大流行后端框架:Spring、Netty、MyBatis、Hibernate、Dubbo…「建议收藏」框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面而后者是从目的方面给出的定义。可以说,一个框架是一个可复用的设计构件,它规定了应用的体系结构,阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程,表现为一组抽象类以及其实例之间协作的方法,它为构件复用提供了上下文(Context)关系。因此构件库的大规模重用也需要框架。今天给大家分享49篇【后端编程框架】相关精选学习资…

    2022年5月1日
    60
  • java 僵尸进程_僵尸进程ZOMBIE

    java 僵尸进程_僵尸进程ZOMBIE僵尸进程是指它的父进程已经退出 父进程没有等待 调用 wait waitpid 它 而该进程 dead 之后没有进程接受 就成为僵尸进程 也就是 zombie 进程 一个进程在调用 exit 命令结束自己的生命的时候 其实它并没有真正的被销毁 而是留下一个称为僵尸进程 Zombie 的数据结构 系统调用 exit 它的作用是使进程退出 但也仅仅限于将一个正常的进程变成一个僵尸进程 并不能将其完全销毁 在 L

    2026年3月17日
    1
  • JAVA常见知识

    JAVA常见知识JAVA常见知识

    2022年4月24日
    36

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号