你真的理解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;</code></pre>
// 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);
}</code></pre>
對其中的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;
}
};
}</code></pre>
客戶端這邊的思路是很清晰的,不管怎么樣,先連接上服務端再說,連接上之后再分別的調用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;
}
}</code></pre>
同樣脈絡是很清楚的,首先接受客戶端連接的請求,并把服務端處理好的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();
}
}
}</code></pre>
在 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