一腿短、AIDL基礎(chǔ)
文件類型:
后綴是 .aidl屏箍,而不是 .java绘梦。
數(shù)據(jù)類型:
1.默認(rèn)支持的數(shù)據(jù)類型
在使用這些數(shù)據(jù)類型的時(shí)候是不需要導(dǎo)包的。
(1)Java中的八種基本數(shù)據(jù)類型赴魁,包括 byte卸奉,short,int颖御,long榄棵,float,double潘拱,boolean疹鳄,char。
(2)String 類型芦岂。
(3)CharSequence類型瘪弓。
(4)List類型:只支持ArrayList,里面的所有元素都必須能夠被AIDL支持禽最。
注意:CopyOnWriteArrayList腺怯,支持并發(fā)讀/寫,可在服務(wù)端中使用川无,在Binder中會(huì)按照List的規(guī)范去訪問(wèn)數(shù)據(jù)并最終形成一個(gè)新的ArrayList傳遞給客戶端呛占。
(5)Map類型:只支持HashMap,里面的所有元素都必須能夠被AIDL支持懦趋,包括key和value栓票。
注意:ConcurrentHashMap,支持并發(fā)讀/寫愕够,可在服務(wù)端中使用走贪,在Binder中會(huì)按照Map的規(guī)范去訪問(wèn)數(shù)據(jù)并最終形成一個(gè)新的HashMap傳遞給客戶端。
2.非默認(rèn)支持的數(shù)據(jù)類型
在使用這些數(shù)據(jù)類型的時(shí)候必須導(dǎo)包惑芭,就算目標(biāo)文件與當(dāng)前正在編寫的 .aidl 文件在同一個(gè)包下也一樣要導(dǎo)包坠狡。
(1)Parcelable:所有實(shí)現(xiàn)了Parcelable接口的對(duì)象。
(2)AIDL:所有的AIDL接口本身也可以在AIDL文件中使用遂跟。
定向tag:
AIDL中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向逃沿,其中 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶端幻锁,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通凯亮。其中,數(shù)據(jù)流向是針對(duì)客戶端中傳入方法的對(duì)象參數(shù)而言的哄尔。
in:表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)對(duì)象參數(shù)的完整數(shù)據(jù)假消,但是客戶端的那個(gè)對(duì)象參數(shù)不會(huì)因?yàn)榉?wù)端對(duì)接收到的對(duì)象參數(shù)的修改而發(fā)生變動(dòng);
out:表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)對(duì)象參數(shù)的空對(duì)象岭接,但是在服務(wù)端對(duì)接收到的空對(duì)象有任何修改之后客戶端將會(huì)同步變動(dòng)富拗;
inout:表現(xiàn)為服務(wù)端將會(huì)接收到一個(gè)對(duì)象參數(shù)的完整數(shù)據(jù)臼予,并且在服務(wù)端對(duì)接收到的對(duì)象參數(shù)有任何修改之后客戶端將會(huì)同步變動(dòng)。
另外啃沪,Java 中的基本類型和 String 粘拾,CharSequence 的定向 tag 默認(rèn)且只能是 in。
AIDL文件分類:
所有的AIDL文件大致可以分為兩類:
第一類:用來(lái)定義parcelable對(duì)象创千,以供其他AIDL文件使用AIDL中非默認(rèn)支持的數(shù)據(jù)類型的缰雇;
第二類:用來(lái)定義方法接口,以供系統(tǒng)使用來(lái)完成跨進(jìn)程通信的追驴。
可以看到寓涨,兩類文件都是在"定義"些什么,而不涉及具體的實(shí)現(xiàn)氯檐,這就是為什么它叫做"Android接口定義語(yǔ)言"。
注意:所有的非默認(rèn)支持的數(shù)據(jù)類型必須通過(guò)第一類AIDL文件定義才能被使用体捏。
其它:
1.AIDL接口中只支持方法冠摄,不支持聲明靜態(tài)常量。
二几缭、AIDL使用
1.使數(shù)據(jù)類實(shí)現(xiàn)Parcelable接口
由于不同的進(jìn)程有著不同的內(nèi)存區(qū)域河泳,并且它們只能訪問(wèn)自己的那一塊內(nèi)存區(qū)域,因此我們無(wú)法從源進(jìn)程向目標(biāo)進(jìn)程傳遞一個(gè)句柄(句柄指向的是一個(gè)內(nèi)存區(qū)域)年栓,而必須將要傳輸?shù)臄?shù)據(jù)轉(zhuǎn)化為能夠在內(nèi)存之間流通的形式拆挥。這個(gè)轉(zhuǎn)化的過(guò)程就叫做序列化與反序列化。
序列化與反序列化的大概流程為:我們要將一個(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)程中訪問(wèn)另一個(gè)進(jìn)程的數(shù)據(jù)的目的洲拇。
//數(shù)據(jù)類實(shí)現(xiàn)Parcelable接口
public class Book implements Parcelable{
private String name;
private int price;
public Book(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(price);
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
private Book(Parcel in) {
name = in.readString();
price = in.readInt();
}
//AIDL文件中,方法參數(shù)的定向tag為 out 跟 inout 時(shí)曲尸,需要用到此方法
//作用:服務(wù)端修改客戶端傳遞的對(duì)象參數(shù)赋续,傳回到客戶端,客戶端通過(guò)此方法讀取傳回的數(shù)據(jù)
public void readFromParcel(Parcel dest) {
//注意另患,此處的讀值順序應(yīng)當(dāng)是和writeToParcel()方法中一致的
name = dest.readString();
price = dest.readInt();
}
@Override
public String toString() {
return "name : " + name + " , price : " + price;
}
}
2.創(chuàng)建AIDL文件
鼠標(biāo)移到app上面去纽乱,點(diǎn)擊右鍵,然后 new==>AIDL==>AIDL File昆箕,按下鼠標(biāo)左鍵就會(huì)彈出一個(gè)框提示生成AIDL文件了迫淹。項(xiàng)目的目錄比起以前多了一個(gè)叫做 aidl 的包秘通,而且它的層級(jí)是和 java 包相同的,并且 aidl 包里默認(rèn)有著和 java 包里默認(rèn)的包結(jié)構(gòu)敛熬。
//Book.aidl
//第一類AIDL文件
//作用是引入了一個(gè)序列化對(duì)象 Book 供其他的AIDL文件使用
//注意:Book.aidl與Book.java的包名應(yīng)當(dāng)是一樣的
package com.tomorrow.androidtest7.aidl;
//注意parcelable是小寫
parcelable Book;
//BookManager.aidl
//第二類AIDL文件
//作用是定義方法接口
package com.tomorrow.androidtest7.aidl;
//導(dǎo)入所需要使用的非默認(rèn)支持?jǐn)?shù)據(jù)類型的包
import com.tomorrow.androidtest7.aidl.Book;
interface BookManager {
//所有的返回值前都不需要加任何東西肺稀,不管是什么數(shù)據(jù)類型
List<Book> getBooks();
//傳參時(shí)除了Java基本類型以及String,CharSequence之外的類型
//都需要在前面加上定向tag应民,具體加什么量需而定
void addBookWithTagIn(in Book book);
void addBookWithTagOut(out Book book);
void addBookWithTagInOut(inout Book book);
}
3.移植相關(guān)文件
我們需要保證话原,在客戶端和服務(wù)端中都有我們需要用到的 .aidl 文件(如Book.aidl、BookManager.aidl)和其中涉及到的 .java 文件(如Book.java)诲锹,因此不管在哪一端寫這些文件繁仁,寫完之后我們都要把這些文件復(fù)制到另一端去,并且保證在客戶端跟服務(wù)端有相同的目錄归园。
4.編寫服務(wù)端代碼
在我們寫完AIDL文件并 clean 或者 rebuild 項(xiàng)目之后黄虱,編譯器會(huì)根據(jù)AIDL文件為我們生成一個(gè)與AIDL文件同名的 .java 文件,這個(gè) .java 文件與跨進(jìn)程通信密切相關(guān)庸诱。
基本的操作流程就是:在服務(wù)端實(shí)現(xiàn)AIDL中定義的方法接口的具體邏輯捻浦,然后在客戶端調(diào)用這些方法接口,從而達(dá)到跨進(jìn)程通信的目的桥爽。
//服務(wù)端代碼
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, "zwm, invoking getBooks() method , now the list is : " + mBooks.toString());
if (mBooks != null) {
return mBooks;
}
return new ArrayList<>();
}
}
@Override
public void addBookWithTagIn(Book book) throws RemoteException {
synchronized (this) {
if (mBooks == null) {
mBooks = new ArrayList<>();
}
if (book == null) {
Log.e(TAG, "zwm, Book is null in In");
book = new Book();
}
//嘗試修改book的參數(shù)朱灿,主要是為了觀察其到客戶端的反饋
book.setPrice(5968);
if (!mBooks.contains(book)) {
mBooks.add(book);
}
//打印mBooks列表,觀察客戶端傳過(guò)來(lái)的值
Log.e(TAG, "zwm, invoking addBooks() method , now the list is : " + mBooks.toString());
}
}
@Override
public void addBookWithTagOut(Book book) throws RemoteException {
Log.e(TAG, "zwm, addBookWithTagOut");
addBookWithTagIn(book);
}
@Override
public void addBookWithTagInOut(Book book) throws RemoteException {
Log.e(TAG, "zwm, addBookWithTagInOut");
addBookWithTagIn(book);
}
};
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "zwm, onCreate");
Book book = new Book();
book.setName("Android開發(fā)藝術(shù)探索");
book.setPrice(88);
mBooks.add(book);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e(getClass().getSimpleName(), String.format("zwm, on bind,intent = %s", intent.toString()));
return mBookManager;
}
}
//在AndroidManifest.xml注冊(cè)Service
<service
android:name=".service.AIDLService"
android:exported="true">
<intent-filter>
<action android:name="com.tomorrow.aidl"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
5.編寫客戶端代碼
客戶端的工作主要是綁定服務(wù)端钠四,并調(diào)用服務(wù)端的方法盗扒。
//客戶端代碼
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ù)端的addBook方法
*/
public void addBook() {
Log.e(getLocalClassName(), "zwm, addBook");
//如果與服務(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ā)錄");
book.setPrice(66);
try {
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), "zwm, before add book, 參數(shù):" + book.toString());
Log.e(getLocalClassName(), "zwm, before add book, 返回值:" + mBooks.toString());
mBookManager.addBookWithTagInOut(book);
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), "zwm, after add book, 參數(shù):" + book.toString());
Log.e(getLocalClassName(), "zwm, after add book, 返回值:" + mBooks.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 嘗試與服務(wù)端建立連接
*/
private void attemptToBindService() {
Log.e(getLocalClassName(), "zwm, attemptToBindService");
Intent intent = new Intent();
intent.setClassName("com.tomorrow.androidtest7", "com.tomorrow.androidtest7.service.AIDLService");
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStart() {
super.onStart();
Log.e(getLocalClassName(), "zwm, onStart");
if (!mBound) {
attemptToBindService();
}
}
@Override
protected void onStop() {
super.onStop();
Log.e(getLocalClassName(), "zwm, onStop");
if (mBound) {
unbindService(mServiceConnection);
mBound = false;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(getLocalClassName(), "zwm, service connected");
mBookManager = BookManager.Stub.asInterface(service);
mBound = true;
if (mBookManager != null) {
try {
mBooks = mBookManager.getBooks();
Log.e(getLocalClassName(), "zwm, " + mBooks.toString());
addBook();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "zwm, service disconnected");
mBound = false;
}
};
}
//在AndroidManifest.xml注冊(cè)Activity
<activity android:name=".activity.AIDLActivity"/>
6.測(cè)試
將兩個(gè)app同時(shí)運(yùn)行在同一臺(tái)手機(jī)上,就可以開始進(jìn)程間通信了阎曹。
三伪阶、高級(jí)用法
1.給Binder設(shè)置死亡代理
Binder運(yùn)行在服務(wù)端進(jìn)程,如果服務(wù)端進(jìn)程由于某種原因異常終止处嫌,這個(gè)時(shí)候我們到服務(wù)端的Binder連接斷裂(稱為Binder死亡)栅贴,會(huì)導(dǎo)致我們的遠(yuǎn)程調(diào)用失敗。更關(guān)鍵的是熏迹,如果我們不知道Binder連接已經(jīng)斷裂檐薯,那么客戶端的功能就會(huì)受到影響。這時(shí)我們可以給Binder設(shè)置一個(gè)死亡代理,當(dāng)Binder死亡時(shí)坛缕,我們就會(huì)收到通知墓猎,然后可以重新發(fā)起連接請(qǐng)求從而恢復(fù)連接。Binder提供兩個(gè)配對(duì)方法linkToDeath和unlinkToDeath赚楚,使用代碼如下:
//AIDLActivity
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.e(getLocalClassName(), "zwm, binderDied");
if(mBookManager == null)
return;
mBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
mBookManager = null;
//重新綁定遠(yuǎn)程service
attemptToBindService();
}
};
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mBookManager = BookManager.Stub.asInterface(service);
try {
service.linkToDeath(mDeathRecipient, 0); //第二個(gè)參數(shù)直接設(shè)為0即可
} catch (RemoteException e) {
e.printStackTrace();
}
//省略
}
//省略
}
注意:binderDied方法運(yùn)行在Binder線程池毙沾。
另外,通過(guò)Binder的方法isBinderAlive也可以判斷Binder是否死亡宠页。
2.觀察者模式
定義AIDL監(jiān)聽接口左胞,實(shí)現(xiàn)客戶端對(duì)服務(wù)端的數(shù)據(jù)進(jìn)行監(jiān)聽,當(dāng)服務(wù)端的數(shù)據(jù)改變時(shí)會(huì)通知客戶端举户。
//AIDL監(jiān)聽接口
package com.tomorrow.androidtest7.aidl;
import com.tomorrow.androidtest7.aidl.Book;
interface OnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
//在原有的接口中添加兩個(gè)新方法
package com.tomorrow.androidtest7.aidl;
import com.tomorrow.androidtest7.aidl.Book;
import com.tomorrow.androidtest7.aidl.OnNewBookArrivedListener;
interface BookManager {
List<Book> getBooks();
void addBookWithTagIn(in Book book);
void addBookWithTagOut(out Book book);
void addBookWithTagInOut(inout Book book);
void registerListener(OnNewBookArrivedListener listener); //注冊(cè)監(jiān)聽
void unregisterListener(OnNewBookArrivedListener listener); //解注冊(cè)監(jiān)聽
}
問(wèn)題:
客戶端創(chuàng)建OnNewBookArrivedListener.Stub Binder對(duì)象烤宙,調(diào)用服務(wù)端BookManager.Stub.Proxy#registerListener(listener)方法進(jìn)行注冊(cè)監(jiān)聽,注冊(cè)監(jiān)聽之后客戶端能成功接收到服務(wù)端的通知俭嘁。
客戶端再調(diào)用服務(wù)端BookManager.Stub.Proxy#unregisterListener(listener)方法進(jìn)行解注冊(cè)監(jiān)聽躺枕,解注冊(cè)監(jiān)聽之后客戶端仍然能成功接收到服務(wù)端的通知。
原因:
對(duì)客戶端來(lái)說(shuō)供填,注冊(cè)監(jiān)聽跟解注冊(cè)監(jiān)聽的listener對(duì)象是同一個(gè)拐云。但是對(duì)服務(wù)端來(lái)說(shuō),服務(wù)端接收到的注冊(cè)監(jiān)聽跟解注冊(cè)監(jiān)聽的listener對(duì)象卻是不同的捕虽,因?yàn)閷?duì)象不能跨進(jìn)程直接傳輸,對(duì)象的跨進(jìn)程傳輸本質(zhì)上都是反序列化的過(guò)程坡脐,因此在服務(wù)端重新生成了兩個(gè)對(duì)象泄私,導(dǎo)致解注冊(cè)失敗。
方案:
在服務(wù)端使用系統(tǒng)專門提供的用于刪除跨進(jìn)程listener的接口RemoteCallbackList备闲。
RemoteCallbackList是一個(gè)泛型晌端,支持管理任意的AIDL接口。雖然多次跨進(jìn)程傳輸客戶端的同一個(gè)對(duì)象會(huì)在服務(wù)端生成不同的對(duì)象恬砂,但是這些新生成的對(duì)象有一個(gè)共同點(diǎn)咧纠,那就是它們底層的Binder對(duì)象是同一個(gè),RemoteCallbackList就是利用這個(gè)特性來(lái)完成注冊(cè)監(jiān)聽跟解注冊(cè)監(jiān)聽功能泻骤。
//服務(wù)端BookManager$Stub的實(shí)現(xiàn)代碼
private RemoteCallbackList<OnNewBookArrivedListener> mListenerList = new RemoteCallbackList<OnNewBookArrivedListener>();
@Override
public void registerListener(OnNewBookArrivedListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unregisterListener(OnNewBookArrivedListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast(); //要跟finishBroadcast匹配
for(int i = 0; i < N; i++) {
OnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if(listener != null) {
try {
listener.onNewBookArrived(book);
} catch(RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast(); //要跟beginBroadcast匹配
}
3.在AIDL中進(jìn)行權(quán)限驗(yàn)證
方法一:在服務(wù)端的AIDL方法中進(jìn)行權(quán)限驗(yàn)證
//在服務(wù)端的AndroidManifest.xml中聲明所需的權(quán)限
<permission
android:name="com.tomorrow.androidtest7.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
//服務(wù)端的AIDL方法
int check = checkCallingOrSelfPermission("com.tomorrow.androidtest7.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED) {
return;
}
...
//如果客戶端想綁定服務(wù)漆羔,需要在AndroidManifest.xml中使用所需的權(quán)限
<uses-permission android:name="com.tomorrow.androidtest7.permission.ACCESS_BOOK_SERVICE" />
方法二:在服務(wù)端Stub的onTransact方法中進(jìn)行權(quán)限驗(yàn)證
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
// TODO Auto-generated method stub
int checkPermission = checkCallingOrSelfPermission("com.tomorrow.androidtest7.permission.ACCESS_BOOK_SERVICE");
if (checkPermission == PackageManager.PERMISSION_DENIED) {
return false;
}
String packageName = "";
//通過(guò)getCallingUid()方法得到客戶端的uid,
//接著通過(guò)PackageManager的getPackagesForUid(int uid)方法得到客戶端Package相關(guān)信息
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
//字符串?dāng)?shù)組packages中第一個(gè)字符串是包名信息
packageName = packages[0];
}
//如果包名不是以"com.tomorrow"開頭狱掂,直接返回false演痒,權(quán)限驗(yàn)證失敗
if (!packageName.startsWith("com.tomorrow")) {
return false;
}
return super.onTransact(code, data, reply, flags);
}
方法三:為服務(wù)端Service指定android:permission屬性
方法四:其它方法