AIDL學(xué)習(xí)筆記
AIDL是一門接口定義語(yǔ)言添吗,用于Android進(jìn)程間交互使用;
1瞬哼、為什么用aidl
2疆股、它相對(duì)其他IPC方案有什么優(yōu)劣
3、未來(lái)有更好的方案嗎
因?yàn)锳ndroid系統(tǒng)中每一個(gè)進(jìn)程都有獨(dú)立Dalvik VM實(shí)例倒槐,有自己的內(nèi)存區(qū)域旬痹,彼此互不干擾也無(wú)法互相直接通信。AIDL就是在需要通信的雙方定義一些公共的方法接口和需要傳遞的數(shù)據(jù)類型讨越。通信兩端具體實(shí)現(xiàn)接口的一方為服務(wù)端两残,調(diào)用接口的一方成為客戶端,客戶端調(diào)用服務(wù)端的相應(yīng)方法達(dá)到進(jìn)程間通信的目的把跨。具體的實(shí)現(xiàn)方式是服務(wù)的調(diào)用人弓。
語(yǔ)法特性:
文件類型:用AIDL書寫的文件的后綴是 .aidl,而不是 .java着逐。
數(shù)據(jù)類型:AIDL默認(rèn)支持一些數(shù)據(jù)類型崔赌,在使用這些數(shù)據(jù)類型的時(shí)候是不需要導(dǎo)包的,但是除了這些類型之外的數(shù)據(jù)類型耸别,在使用之前必須導(dǎo)包健芭,就算目標(biāo)文件與當(dāng)前正在編寫的 .aidl 文件在同一個(gè)包下——在 Java 中,這種情況是不需要導(dǎo)包的秀姐。比如慈迈,現(xiàn)在我們編寫了兩個(gè)文件,一個(gè)叫做 Book.java 省有,另一個(gè)叫做 BookManager.aidl痒留,它們都在 com.leo.aidldemo 包下 谴麦,現(xiàn)在我們需要在 .aidl 文件里使用 Book 對(duì)象,那么我們就必須在 .aidl 文件里面寫上 import com.leo.aidldemo.Book; 哪怕 .java 文件和 .aidl 文件就在一個(gè)包下伸头。
默認(rèn)支持的數(shù)據(jù)類型包括:
Java中的八種基本數(shù)據(jù)類型匾效,包括 byte,short恤磷,int面哼,long,float碗殷,double精绎,boolean,char锌妻。
String 類型代乃。
CharSequence類型。
List類型:只支持ArrayList仿粹,List中的所有元素必須是AIDL支持的類型之一搁吓,或者是一個(gè)其他AIDL生成的接口,或者是定義的parcelable(下文關(guān)于這個(gè)會(huì)有詳解)吭历,List可以使用泛型堕仔。
Map類型:只支持HashMap,Map中的所有元素必須是AIDL支持的類型之一晌区,或者是一個(gè)其他AIDL生成的接口摩骨,或者是定義的parcelable。Map是不支持泛型的朗若。
定向tag:這是一個(gè)極易被忽略的點(diǎn)——這里的“被忽略”指的不是大家都不知道恼五,而是很少人會(huì)正確的使用它。在我的理解里哭懈,定向 tag 是這樣的: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ì)接收到客戶端傳來(lái)對(duì)象的完整信息圆米,并且客戶端將會(huì)同步服務(wù)端對(duì)該對(duì)象的任何變動(dòng)卒暂。請(qǐng)不要濫用定向 tag ,而是要根據(jù)需要選取合適的——要是不管三七二十一娄帖,全都一上來(lái)就用 inout 也祠,等工程大了系統(tǒng)的開銷就會(huì)大很多——因?yàn)榕帕姓韰?shù)的開銷是很昂貴的。
實(shí)現(xiàn)原理:
由于不同的進(jìn)程有著不同的內(nèi)存區(qū)域近速,并且它們只能訪問自己的那一塊內(nèi)存區(qū)域诈嘿,所以我們不能像平時(shí)那樣,傳一個(gè)句柄過(guò)去就完事了——句柄指向的是一個(gè)內(nèi)存區(qū)域削葱,現(xiàn)在目標(biāo)進(jìn)程根本不能訪問源進(jìn)程的內(nèi)存奖亚,那把它傳過(guò)去又有什么用呢?所以我們必須將要傳輸?shù)臄?shù)據(jù)轉(zhuǎn)化為能夠在內(nèi)存之間流通的形式析砸。這個(gè)轉(zhuǎn)化的過(guò)程就叫做序列化與反序列化昔字。簡(jiǎn)單來(lái)說(shuō)是這樣的:比如現(xiàn)在我們要將一個(gè)對(duì)象的數(shù)據(jù)從客戶端傳到服務(wù)端去,我們就可以在客戶端對(duì)這個(gè)對(duì)象進(jìn)行序列化的操作首繁,將其中包含的數(shù)據(jù)轉(zhuǎn)化為序列化流作郭,然后將這個(gè)序列化流傳輸?shù)椒?wù)端的內(nèi)存中去,再在服務(wù)端對(duì)這個(gè)數(shù)據(jù)流進(jìn)行反序列化的操作弦疮,從而還原其中包含的數(shù)據(jù)——通過(guò)這種方式夹攒,我們就達(dá)到了在一個(gè)進(jìn)程中訪問另一個(gè)進(jìn)程的數(shù)據(jù)的目的。
而通常胁塞,在我們通過(guò)AIDL進(jìn)行跨進(jìn)程通信的時(shí)候咏尝,選擇的序列化方式是實(shí)現(xiàn) Parcelable 接口。
實(shí)現(xiàn)步驟:
1啸罢、通常我們需要n個(gè)定義parcelable對(duì)象和1個(gè)定義具體方法的aidl文件
實(shí)例如下:
// BookInterface.aidl
//這個(gè)文件的作用是引入了一個(gè)序列化對(duì)象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應(yīng)當(dāng)是一樣的
package leo.com.bookstoredemo;
// Declare any non-default types here with import statements
parcelable Book;
// BookStoreInterface.aidl
package leo.com.bookstoredemo;
// Declare any non-default types here with import statements
//導(dǎo)入所需要使用的非默認(rèn)支持?jǐn)?shù)據(jù)類型的包
import leo.com.bookstoredemo.Book;
interface BookStoreInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
//所有的返回值前都不需要加任何東西编检,不管是什么數(shù)據(jù)類型
//傳參時(shí)除了Java基本類型以及String,CharSequence之外的類型
//都需要在前面加上定向tag伺糠,具體加什么量需而定
List<Book> getBooks();
void addBook(in Book book);
}
2蒙谓、注意點(diǎn):
2.1 默認(rèn)生成的模板類的對(duì)象只支持為 in 的定向 tag 。為什么呢训桶?因?yàn)槟J(rèn)生成的類里面只有 writeToParcel() 方法累驮,而如果要支持為 out 或者 inout 的定向 tag 的話,還需要實(shí)現(xiàn) readFromParcel() 方法舵揭。
2.2 如果AS無(wú)法識(shí)別Java或者aidl文件谤专,則應(yīng)該檢查aidl文件的包結(jié)構(gòu)是否和Java相同,如果還有問題午绳,應(yīng)該設(shè)置sourceset區(qū)分aidl和Java文件位置
sourceSets {
main {
java.srcDirs = ['src/main/java']
aidl.srcDirs = ['src/main/aidl']
}
}
此外置侍,服務(wù)端和客戶端的aidl文件目錄結(jié)構(gòu)必須完全相同,比如服務(wù)端的book.aidl在com.leo.bookstoredemo目錄下,客戶端也必須如此蜡坊「苁洌客戶端需要反序列化服務(wù)端中和AIDL相關(guān)的類,如果兩端文件路徑不一致秕衙,就不能反序列化成功蠢甲。
還有,如果需要在接口方法中使用非默認(rèn)支持的數(shù)據(jù)類型据忘,比如Book.java鹦牛,則定義parcelable對(duì)象的aidl文件也必須叫做Book.aidl否則編譯器是無(wú)法識(shí)別的。
2.3 AIDL方法是在服務(wù)端的Binder線程池中執(zhí)行的勇吊,如果有多個(gè)客戶端同時(shí)鏈接曼追,那么服務(wù)端要做好線程同步問題——對(duì)于List,可以使用CopyOnWriteArrayList汉规,問題來(lái)了礼殊,不是ArrayList為什么可行?因?yàn)槭紫绕淙匀皇荓ist的實(shí)現(xiàn)類鲫忍,關(guān)鍵在Binder中會(huì)根據(jù)List規(guī)則訪問數(shù)據(jù)并返回一個(gè)ArrayList給客戶端膏燕。
2.4服務(wù)端的方法可能通過(guò)很久才有相應(yīng),需要做好應(yīng)對(duì)ANR的措施
當(dāng)定義完aidl文件且這一步?jīng)]問題后悟民,通過(guò)clean或者build項(xiàng)目后坝辫,可以讓AS生成和aidl文件同名的Java文件,這就是具體的接口實(shí)現(xiàn)類
3射亏、現(xiàn)在貼上服務(wù)端的代碼
package leo.com.bookstoredemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class BookStoreService extends Service {
private static final String TAG = "BookStoreService";
private List<Book> mBooks = new ArrayList<>();
private static final Object sObject = new Object();
private BookStoreInterface.Stub mBookStoreManager = new BookStoreInterface.Stub(){
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public List<Book> getBooks() throws RemoteException {
synchronized (sObject) {
if(mBooks != null){
return mBooks;
} else {
mBooks = new ArrayList<>();
return mBooks;
}
}
}
@Override
public void addBook(Book book) throws RemoteException {
synchronized (sObject) {
if(mBooks == null) {
mBooks = new ArrayList<>();
}
if(book == null) {
book = new Book("A Book",10);
Log.e(TAG,"the Book added in is null !");
}
if(!mBooks.contains(book)){
book.setmBookPrice(book.getmBookPrice() * 2);
mBooks.add(book);
Log.i(TAG,"now adding the book and change its price to "+book.getmBookPrice());
}
}
}
};
@Override
public void onCreate() {
super.onCreate();
Book originalBook = new Book("tap dancing to work",30);
mBooks.add(originalBook);
Log.i(TAG,"original book list is: "+originalBook.toString());
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG,"Bookstore's manager has been connected: "+mBookStoreManager.toString());
return mBookStoreManager;
}
}
4近忙、客戶端的代碼
package leo.com.bookstorecustomer;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import java.util.List;
import leo.com.bookstoredemo.Book;
import leo.com.bookstoredemo.BookStoreInterface;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private BookStoreInterface mBookStoreInterface = null;
private boolean mHasBound = false;
private List<Book> mBooks = null;
private Button mAddBookButton = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAddBookButton = findViewById(R.id.add_book);
mAddBookButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mHasBound && mBookStoreInterface != null) {
Book book = new Book("fucking android",50);
try {
mBookStoreInterface.addBook(book);
Log.i(TAG,"the book has been added to list "+book.toString());
}catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
@Override
protected void onStart() {
super.onStart();
attemptToBindService();
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnectionService);
mBookStoreInterface = null;
mHasBound = false;
}
private void attemptToBindService() {
Intent intent = new Intent("com.leo.bookstore.manager");
intent.setPackage("leo.com.bookstoredemo");
bindService(intent,mConnectionService, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnectionService = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.i(TAG,"Service connected !");
mBookStoreInterface = BookStoreInterface.Stub.asInterface(iBinder);
mHasBound = true;
if(mBookStoreInterface != null) {
try {
mBooks = mBookStoreInterface.getBooks();
Log.i(TAG,"customer has connected bookstore and get a book list: "+mBooks.toString());
}catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mBookStoreInterface = null;
mHasBound = false;
Log.i(TAG,"Service disconnected");
}
};
}
通過(guò)log可以看到整個(gè)通信的流程和定向tag的作用
07-29 15:21:21.245 3079 3079 I BookStoreService: original book list is: Book{mBookName='tap dancing to work', mBookPrice=30}
07-29 15:21:21.245 3079 3079 I BookStoreService: Bookstore's manager has been connected: leo.com.bookstoredemo.BookStoreService$1@986cd9a
07-29 15:21:21.256 2838 2838 I MainActivity: Service connected !
07-29 15:21:21.257 2838 2838 I MainActivity: customer has connected bookstore and get a book list: [Book{mBookName='tap dancing to work', mBookPrice=30}]
07-29 15:22:14.991 3079 3092 I BookStoreService: now adding the book and change its price to 100
07-29 15:22:14.991 2838 2838 I MainActivity: the book has been added to list Book{mBookName='fucking android', mBookPrice=50}
完整demo地址 https://github.com/LeeFranz/AndroidIPC