前言
這其實(shí)是一個(gè)很小的知識(shí)點(diǎn),大部分人在使用AIDL的過程中也基本沒有因?yàn)檫@個(gè)出現(xiàn)過錯(cuò)誤,正因?yàn)樗?,所以在大部分的網(wǎng)上關(guān)于AIDL的文章中,它都被忽視了——或者并沒有,但所占篇幅甚小,且基本上都是官方文檔的譯文,譯者讀者其實(shí)都不知其然。這幾天在研究AIDL,偏偏我又是個(gè)執(zhí)拗的性子,遇著不清不楚的東西就是想把它捋清楚,就下了些功夫研究了下AIDL中的定向tag,研究了下它的 in , out , inout 。
整理而成此博文。
正文
1,概述
首先要說的是定向tag是AIDL語法的一部分,而 in , out , inout 是三個(gè)定向tag,所以讀者要有一定的對(duì)于Android中AIDL的了解,關(guān)于AIDL相關(guān)的知識(shí)大家可以參考這篇博文:Android:學(xué)習(xí)AIDL,這一篇文章就夠了(上) 。另外,這篇文章基本上可以說是我研究這個(gè)東西的心路歷程,可能會(huì)有些絮叨,請(qǐng)各位看官見諒。
2,官方文檔
Android官網(wǎng)上在講到AIDL的地方關(guān)于定向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 .
直譯過來就是:所有的非基本參數(shù)都需要一個(gè)定向tag來指出數(shù)據(jù)流通的方式,不管是 in , out , 還是 inout ?;緟?shù)的定向tag默認(rèn)是并且只能是 in 。對(duì)于這個(gè)定向tag我的心里是有一些疑問的。首先,數(shù)據(jù)流通的方式是指什么?其次, in , out , inout 分別代表了什么,它們有什么區(qū)別?很顯然,官方文檔并沒有把這些東西交代清楚。那么接下來我就只能自己把這些問題搞清楚了。
本著不重復(fù)造輪子的原則,我首先在 Google 上查找了一下這方面的資料,看能不能有比較好的答案,結(jié)果確實(shí)找到了一些說法——但是進(jìn)過論證,似乎都有一些漏洞。所以沒辦法,只能自己開始著手研究它是怎么回事了。
3,開始研究
3.1,輸入輸出?NO!
首先第一個(gè)跑到我腦海里的猜測(cè)就是:in表示輸入,也即方法的傳參,out表示輸出,也即方法的返回值。有這樣的猜測(cè)很合情合理,但是這樣的猜測(cè)很不合情合理。原因如下:
- 文檔里說了,基本參數(shù)的定向tag默認(rèn)且只能是 in ,但是很顯然,基本參數(shù)既有可能是方法的傳參,也有可能是方法的返回值,所以這個(gè)猜測(cè)本身就站不住腳。
- 如果 in 表示輸入, out 表示輸出,那 inout 應(yīng)該表示什么?
- 進(jìn)過實(shí)測(cè),定向tag只能用來修飾AIDL中方法的輸入?yún)?shù),并不能修飾其返回值。
綜合以上幾點(diǎn)考慮,基本可以排除這種猜測(cè)的可能性。
3.2,way?way!
排除掉上面的想法后,我開始進(jìn)一步猜測(cè) in , out ,inout 可能代表的意義——在某一個(gè)瞬間我靈光一閃:除了輸入輸出,in ,out 還總是被用來表示數(shù)據(jù)的流向!同時(shí)我驚覺,似乎我對(duì)官方文檔的理解有一些偏差:way有方法的意思,但是它也有道路的意思!如果按照道路理解,那么官網(wǎng)的譯文就應(yīng)當(dāng)是:所有的非基本參數(shù)都需要一個(gè)定向tag來指出數(shù)據(jù)的流向,不管是 in , out , 還是 inout ?;緟?shù)的定向tag默認(rèn)是并且只能是 in 。
如果按照這個(gè)意思的話,似乎它們的含義就很清晰了:in 與 out 分別表示客戶端與服務(wù)端之間的兩條單向的數(shù)據(jù)流向,而 inout 則表示兩端可雙向流通數(shù)據(jù)?;谶@種猜測(cè),我設(shè)計(jì)了一個(gè)實(shí)驗(yàn)來驗(yàn)證它,AIDL文件是這樣的:
// Book.aidl
package com.lypeer.ipcclient;
parcelable Book;
// BookManager.aidl
package com.lypeer.ipcclient;
import com.lypeer.ipcclient.Book;
interface BookManager {
//保證客戶端與服務(wù)端是連接上的且數(shù)據(jù)傳輸正常
List<Book> getBooks();
//通過三種定位tag做對(duì)比試驗(yàn),觀察輸出的結(jié)果
Book addBookIn(in Book book);
Book addBookOut(out Book book);
Book addBookInout(inout Book book);
}
對(duì)其中的AIDL的語法不熟悉的,可以再去溫故一下:Android:學(xué)習(xí)AIDL,這一篇文章就夠了(上) ,我這里就不贅敘了。我主要講一下實(shí)驗(yàn)的思路??梢钥吹?,有定位tag的那三個(gè)方法它們的傳參和返回值都是 Book 對(duì)象(Book是我自定義的一個(gè)實(shí)現(xiàn)了Parcelable的類,里面只有兩個(gè)參數(shù),String name 和 int price),并且它們的定位tag都不一樣,接下來我會(huì)在客戶端調(diào)用這三個(gè)方法,然后分別在客戶端和服務(wù)端打印相關(guān)信息,從而驗(yàn)證我的猜想。下面貼上客戶端和服務(wù)端的代碼:
/**
* 客戶端的AIDLActivity.java
* 由于測(cè)試機(jī)的無用debug信息太多,故log都是用的e
*
* Created by lypeer on 2016/7/17.
*/
public class AIDLActivity extends AppCompatActivity {
//由AIDL文件生成的Java類
private BookManager mBookManager = null;
//標(biāo)志當(dāng)前與服務(wù)端連接狀況的布爾值,false為未連接,true為連接中
private boolean mBound = false;
//包含Book對(duì)象的list
private List<Book> mBooks;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_aidl);
}
/**
* 按鈕的點(diǎn)擊事件,點(diǎn)擊之后調(diào)用服務(wù)端的addBookIn方法
*
* @param view
*/
public void addBookIn(View view) {
//如果與服務(wù)端的連接處于未連接狀態(tài),則嘗試連接
if (!mBound) {
attemptToBindService();
Toast.makeText(this, "當(dāng)前與服務(wù)端處于未連接狀態(tài),正在嘗試重連,請(qǐng)稍后再試", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研發(fā)錄In");
book.setPrice(30);
try {
//獲得服務(wù)端執(zhí)行方法的返回值,并打印輸出
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, "當(dāng)前與服務(wù)端處于未連接狀態(tài),正在嘗試重連,請(qǐng)稍后再試", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研發(fā)錄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, "當(dāng)前與服務(wù)端處于未連接狀態(tài),正在嘗試重連,請(qǐng)稍后再試", Toast.LENGTH_SHORT).show();
return;
}
if (mBookManager == null) return;
Book book = new Book();
book.setName("APP研發(fā)錄Inout");
book.setPrice(30);
try {
Book returnBook = mBookManager.addBookInout(book);
Log.e(getLocalClassName(), returnBook.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 嘗試與服務(wù)端建立連接
*/
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;
}
};
}
客戶端這邊的思路是很清晰的,不管怎么樣,先連接上服務(wù)端再說,連接上之后再分別的調(diào)用AIDL中定義的三個(gè)方法,然后觀察返回值的變化。接下來貼上服務(wù)端:
/**
* 服務(wù)端的AIDLService.java
*
* Created by lypeer on 2016/7/17.
*/
public class AIDLService extends Service {
public final String TAG = this.getClass().getSimpleName();
//包含Book對(duì)象的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的參數(shù),主要是為了觀察其到客戶端的反饋
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開發(fā)藝術(shù)探索");
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;
}
}
同樣脈絡(luò)是很清楚的,首先接受客戶端連接的請(qǐng)求,并把服務(wù)端處理好的BookManager.Stub的IBinder接口回傳給客戶端。在BookManager.Stub里面實(shí)現(xiàn)的方法里面,主要是接收客戶端傳過來的Book對(duì)象,并試圖對(duì)其進(jìn)行修改,然后把修改過的對(duì)象再傳回去。
通過這樣的實(shí)驗(yàn),我們可以監(jiān)測(cè)到客戶端的數(shù)據(jù)流向服務(wù)端的情況,也可以監(jiān)測(cè)到服務(wù)端的數(shù)據(jù)流向客戶端的情況。根據(jù)這些數(shù)據(jù),就可以驗(yàn)證上面我們的猜測(cè)的正確與否。根據(jù)我們的猜測(cè),in , out , inout ,實(shí)際上是標(biāo)志數(shù)據(jù)的流向的,那么這樣的話用 in 或者 out 標(biāo)志的數(shù)據(jù)應(yīng)該只能單向傳輸,反向無效,而 inout 的數(shù)據(jù)則可以雙向傳輸。結(jié)果數(shù)據(jù)是不是顯示這樣的特征的呢?
首先把這兩個(gè)應(yīng)用都裝到手機(jī)上,然后都打開,并且客戶端依次執(zhí)行 addBookIn() , addBookOut() , addBookInout() 的點(diǎn)擊事件,最后得到的兩端的 log 信息分別是這樣的:
//服務(wù)端的 log 信息,我把無用的信息頭去掉了,然后給它編了個(gè)號(hào)
1,on bind,intent = Intent { act=com.lypeer.aidl pkg=com.lypeer.ipcserver }
2,invoking getBooks() method , now the list is : [name : Android開發(fā)藝術(shù)探索 , 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開發(fā)藝術(shù)探索 , price : 28]
5,invoking addBooks() method , now the list is : [name : Android開發(fā)藝術(shù)探索 , price : 28, name : APP研發(fā)錄In , price : 2333]
6,invoking addBooks() method , now the list is : [name : Android開發(fā)藝術(shù)探索 , price : 28, name : APP研發(fā)錄In , price : 2333, name : null , price : 2333]
7,invoking addBooks() method , now the list is : [name : Android開發(fā)藝術(shù)探索 , price : 28, name : APP研發(fā)錄In , price : 2333, name : null , price : 2333, name : APP研發(fā)錄Inout , price : 2333]
可以看到,服務(wù)端的 log 信息基本上是符合預(yù)期的。前四行是客戶端在綁定服務(wù)端,list里面的那個(gè)元素是初始化進(jìn)去的,可以不用管它。后三行分別體現(xiàn)了當(dāng)客戶端在調(diào)用 addBookIn() , addBookOut() , addBookInout() 方法的時(shí)候服務(wù)端接收到的數(shù)據(jù):在 in 和 inout 作為定向 tag 的方法里,服務(wù)端能夠正常的接收到客戶端傳過來的數(shù)據(jù),但是在用 out 作為定向 tag 的方法里,服務(wù)端受到的是一個(gè)空的 Book 對(duì)象!可是明明三個(gè)方法都是傳的具有相同參數(shù)的 Book 對(duì)象過來!通過服務(wù)端的數(shù)據(jù),結(jié)合之前的猜測(cè),我們基本可以確定,之前的猜測(cè)是正確的,并且 in 作為定向 tag 表示數(shù)據(jù)只能由客戶端流向服務(wù)端,out 反之,inout 則為數(shù)據(jù)可以雙向流通。如果是這樣的話,那么客戶端的 log 信息我們也可以有一些猜測(cè)了:既然 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端,那么客戶端里它的返回值應(yīng)當(dāng)是一個(gè)空的 Book 對(duì)象了;而 out 和 inout 作為定向 tag 的方法里它們的返回值則應(yīng)當(dāng)是與服務(wù)端里一樣的。那么是不是這樣的呢?看一下:
//客戶端的 log 信息,我把無用的信息頭去掉了,然后給它編了個(gè)號(hào)
1,service connected
2,[name : Android開發(fā)藝術(shù)探索 , price : 28]
3,service connected
4,[name : Android開發(fā)藝術(shù)探索 , price : 28]
5,APP研發(fā)錄In , price : 2333
6,name : null , price : 2333
7,name : APP研發(fā)錄Inout , price : 2333
同樣,前四行是連接的信息,后面三行則是調(diào)用addBookIn() , addBookOut() , addBookInout() 方法的時(shí)候服務(wù)端返回的數(shù)據(jù)。結(jié)果這三行數(shù)據(jù)都和服務(wù)端的數(shù)據(jù)一模一樣!既然數(shù)據(jù)出現(xiàn)了問題,那么很顯然的,前面的推測(cè)也肯定有問題。
3.3,數(shù)據(jù)流向!
我們?cè)賮磙垡晦鬯悸?。首先,從服?wù)端得到的數(shù)據(jù)來看,確實(shí)用 out 作為定向 tag 的方法他的數(shù)據(jù)是不能夠從客戶端流向服務(wù)端的——服務(wù)端收到的是一個(gè)空的對(duì)象!這說明定向 tag 與數(shù)據(jù)的流向有關(guān)系這個(gè)大的方向是沒有問題的。那么問題出在哪里呢?出在怎樣看待數(shù)據(jù)流向這件事情上。之前我把數(shù)據(jù)流向簡單的看作了方法的參數(shù)輸入和返回值輸出,現(xiàn)在想來是有些問題的:
- 如果將數(shù)據(jù)從服務(wù)端流向客戶端看成是方法將返回值傳回客戶端,那么為什么不將 out 設(shè)計(jì)成寫在返回值前面呢?還要一個(gè)根本沒有用的輸入干嘛?設(shè)計(jì)這門語言的那些人那么膩害,沒可能沒想到這一點(diǎn)吧?
- AIDL里面的默認(rèn)類型的定向 tag 默認(rèn)且只能是 in ,難道它們只能作為參數(shù)輸入,不能成為返回值?
所以,問題應(yīng)該就處在把方法的返回值當(dāng)作是數(shù)據(jù)從服務(wù)端流向客戶端這件事上——雖然在某種意義上方法的返回值也可以說成是數(shù)據(jù)從服務(wù)端流向客戶端,但是不是我們這里說的數(shù)據(jù)從服務(wù)端流向客戶端——雖然有點(diǎn)繞,但是看到這里的讀者應(yīng)該是能明白我的意思的。那么到底應(yīng)該如何理解數(shù)據(jù)從服務(wù)端流向客戶端呢?現(xiàn)在方法的返回值已經(jīng)被否決了,那么數(shù)據(jù)流回去的載體是什么呢——不管怎么樣,數(shù)據(jù)總是要有一個(gè)載體的,不然我們?cè)趺粗浪呀?jīng)回來了?既然載體不是方法返回來的對(duì)象,那么必然是在調(diào)用方法之前就已經(jīng)存在的對(duì)象。雖然感覺很不可思議:我這個(gè)對(duì)象在客戶端,而方法的實(shí)現(xiàn)是在服務(wù)端,那么它怎么能變動(dòng)這個(gè)對(duì)象?但是既然是推導(dǎo)出來的結(jié)果,那么就做個(gè)試驗(yàn)看看不就清楚了。
要驗(yàn)證這個(gè)很簡單,直接在客戶端里面 log 輸出服務(wù)端返回信息那里,把原本的輸出 returnBook.toString() 改為 book.toString() 就可以了( book 對(duì)象是方法的傳參),具體的代碼就不貼了,只有一點(diǎn)點(diǎn)變動(dòng)。如果上面的猜測(cè)是正確的,那么輸出的結(jié)果應(yīng)當(dāng)是 in 為定向 tag 的方法處 book 對(duì)象的參數(shù)不變,而 out ,inout 為定向 tag 的方法處 book 對(duì)象的參數(shù)與在服務(wù)端的參數(shù)一致。接下來再看下實(shí)際的 log 值:
1,service connected
2,[name : Android開發(fā)藝術(shù)探索 , price : 28]
3,name : APP研發(fā)錄In , price : 30
4, name : null , price : 2333
5,name : APP研發(fā)錄Inout , price : 2333
同樣后三行表示了調(diào)用addBookIn() , addBookOut() , addBookInout() 方法之后 book 對(duì)象的參數(shù)。可以看到,輸出的 log 信息終于和我前面預(yù)計(jì)的結(jié)果一致了!這說明前面的猜測(cè)是正確的!
3.4,得出結(jié)論
到這里基本上就可以下結(jié)論了:AIDL中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向,其中 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶端,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通。其中,數(shù)據(jù)流向是針對(duì)在客戶端中的那個(gè)傳入方法的對(duì)象而言的。in 為定向 tag 的話表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)那個(gè)對(duì)象的完整數(shù)據(jù),但是客戶端的那個(gè)對(duì)象不會(huì)因?yàn)榉?wù)端對(duì)傳參的修改而發(fā)生變動(dòng);out 的話表現(xiàn)為服務(wù)端將會(huì)接收到那個(gè)對(duì)象的的空對(duì)象,但是在服務(wù)端對(duì)接收到的空對(duì)象有任何修改之后客戶端將會(huì)同步變動(dòng);inout 為定向 tag 的情況下,服務(wù)端將會(huì)接收到客戶端傳來對(duì)象的完整信息,并且客戶端將會(huì)同步服務(wù)端對(duì)該對(duì)象的任何變動(dòng)。
4,源碼分析
上面我們通過猜測(cè)分析,設(shè)計(jì)實(shí)驗(yàn)等等手段得到了一個(gè)結(jié)論,那么接下來我們將進(jìn)行源碼分析,來看看在理論上能不能為我們的結(jié)論提供證明。首先我找到了as根據(jù) BookManager.aidl 文件生成的 BookManager.java 文件,然后從中抽取了相關(guān)的代碼片段:
@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對(duì)象
com.lypeer.ipcclient.Book _arg0;
//從輸入的_data流中讀取book數(shù)據(jù),并將其賦值給_arg0
if ((0 != data.readInt())) {
_arg0 = com.lypeer.ipcclient.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//在這里才是真正的開始執(zhí)行實(shí)際的邏輯,調(diào)用服務(wù)端寫好的實(shí)現(xiàn)
this.addBookIn(_arg0);
//執(zhí)行完方法之后就結(jié)束了,沒有針對(duì)_reply流的操作,所以客戶端不會(huì)同步服務(wù)端的變化
reply.writeNoException();
return true;
}
case TRANSACTION_addBookOut: {
data.enforceInterface(DESCRIPTOR);
com.lypeer.ipcclient.Book _arg0;
//可以看到,用out作為定向tag的方法里,根本沒有從_data里讀取book對(duì)象的操作,
//而是直接new了一個(gè)book對(duì)象,這就是為什么服務(wù)端收不到客戶端傳過來的數(shù)據(jù)
_arg0 = new com.lypeer.ipcclient.Book();
//執(zhí)行具體的事物邏輯
this.addBookOut(_arg0);
reply.writeNoException();
//在這里,_arg0是方法的傳入?yún)?shù),故服務(wù)端的實(shí)現(xiàn)里對(duì)傳參做出的任何修改,
//都會(huì)在_arg0中有所體現(xiàn),將其寫入_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同樣兼具上兩個(gè)方法中的細(xì)節(jié)
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;
}
//保證客戶端與服務(wù)端是連接上的且數(shù)據(jù)傳輸正常
@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做對(duì)比試驗(yàn),觀察輸出的結(jié)果
@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);
//可以看到,這里執(zhí)行的操作很簡單,僅僅是判斷book是否為空,
// 如果為空,則_data寫入int值1,將其book寫入_data中
// 如果不為空,則_data寫入int值0
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//之后直接調(diào)用transact()方法,將方法的編碼,
// _data(包含從客戶端流向服務(wù)端的book流),
// _reply(包含從服務(wù)端流向客戶端的數(shù)據(jù)流)傳入
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對(duì)象寫入_data流的操作
mRemote.transact(Stub.TRANSACTION_addBookOut, _data, _reply, 0);
_reply.readException();
//與tag為in的方法里面不同的是,在執(zhí)行transact方法之后,
//還有針對(duì)_reply的操作,并且將book賦值為_reply流中的數(shù)據(jù)
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的方法里綜合了上兩個(gè)方法里的操作
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 文件中,在進(jìn)行遠(yuǎn)程調(diào)用的時(shí)候基本的調(diào)用順序是先從 Proxy 類中調(diào)用相關(guān)方法,然后在這些方法中調(diào)用 transact() 方法,這個(gè)時(shí)候 Stub 中的 onTransact() 方法就會(huì)被調(diào)用,然后在這個(gè)方法里面再調(diào)用具體的業(yè)務(wù)邏輯的方法——當(dāng)然,在這幾個(gè)方法調(diào)用的過程中,總是會(huì)有一些關(guān)于數(shù)據(jù)的寫入讀出的操作,因?yàn)檫@些是跨線程操作,必須將數(shù)據(jù)序列化傳輸。讀者看代碼以及看注釋的時(shí)候,最好跟著這條方法調(diào)用的線來,這樣的話對(duì)于這整體數(shù)據(jù)的流向會(huì)清晰很多,也更加簡明易讀。
通過分析源碼,我們可以很輕易的得出和之前分析的時(shí)候一樣的結(jié)論,這樣一來,基本上 AIDL 中定向 tag 是什么,in , out , inout 它們分別表示什么,有些什么區(qū)別這些問題,也就迎刃而解了。
結(jié)語
首先還是對(duì)于復(fù)述一遍得出的結(jié)論:AIDL中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向,其中 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶端,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通。其中,數(shù)據(jù)流向是針對(duì)在客戶端中的那個(gè)傳入方法的對(duì)象而言的。in 為定向 tag 的話表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)那個(gè)對(duì)象的完整數(shù)據(jù),但是客戶端的那個(gè)對(duì)象不會(huì)因?yàn)榉?wù)端對(duì)傳參的修改而發(fā)生變動(dòng);out 的話表現(xiàn)為服務(wù)端將會(huì)接收到那個(gè)對(duì)象的的空對(duì)象,但是在服務(wù)端對(duì)接收到的空對(duì)象有任何修改之后客戶端將會(huì)同步變動(dòng);inout 為定向 tag 的情況下,服務(wù)端將會(huì)接收到客戶端傳來對(duì)象的完整信息,并且客戶端將會(huì)同步服務(wù)端對(duì)該對(duì)象的任何變動(dòng)。(沒錯(cuò),這就是從上面復(fù)制粘粘的:))
最后,再說兩個(gè)問題。
一個(gè)是可能有些讀者不太明白,為什么不一上來就看源碼?那樣得出的結(jié)論必然是對(duì)的!確實(shí)如此。但是一方面,在通過探究得出一個(gè)結(jié)論之后去看源碼,和直接去看源碼,難度是不一樣的——一開始就去看源碼,未必看得懂,即算看得懂,未必能得出正確的結(jié)論——這話聽起來似乎有些匪夷所思,但是那些經(jīng)??丛创a的同學(xué)應(yīng)該是會(huì)有相同的體悟的。另一方面,源碼,已經(jīng)是成品了,我們?nèi)タ此苍S能夠得出結(jié)論,但是很難得到那種作者在設(shè)計(jì)這個(gè)東西的時(shí)候的心路歷程,那種在不同方案中取舍,最后選擇了最優(yōu)方案的心路歷程——沒有感受到這個(gè),那么我覺得也許我們還需要在這個(gè)東西上面再多花些功夫來靜下心的研究。
再就是這篇文章其實(shí)更多的想呈現(xiàn)的是那種對(duì)于技術(shù)的探究的態(tài)度。這個(gè)點(diǎn)只是一個(gè)很小的點(diǎn),但是我找了很多的文章,很多的網(wǎng)站都沒有找到一個(gè)很好的答案,這是為什么?是因?yàn)榇蠹叶紱]有去好好的靜下心來研究它。花點(diǎn)時(shí)間,每個(gè)人都可以得出相同的答案,但是大多數(shù)人匆匆忙忙的看見了它,又匆匆忙忙的忽略了它——或者隨意的翻查一下,似乎網(wǎng)上大家都說的挺有道理的,那么就這樣了吧。這樣是很沒道理的。
文中相關(guān)代碼可點(diǎn)擊 傳送門 下載。
謝謝大家。