2.1 Android IPC簡介
IPC是Inter-Process Communication的縮寫,含義為進(jìn)程間通信或者跨進(jìn)程通信步脓,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程
進(jìn)程一般指一個(gè)執(zhí)行單元,在PC和移動(dòng)設(shè)備上指一個(gè)程序或者一個(gè)應(yīng)用。一個(gè)進(jìn)程可以包含多個(gè)線程骑疆。
- 什么情況使用多進(jìn)程
- 第一種情況是一個(gè)應(yīng)用因?yàn)槟承┰蜃陨硇枰捎枚噙M(jìn)程模式來實(shí)現(xiàn)敛滋,至于原因许布,可能有很多,比如有些模塊由于特殊原因需要運(yùn)行在單獨(dú)的進(jìn)程中绎晃,又或者為了加大一個(gè)應(yīng)用可使用的內(nèi)存所以需要通過多進(jìn)程來獲取多份內(nèi)存空間蜜唾。Android對單個(gè)應(yīng)用所使用的最大內(nèi)存做了限制,早期的一些版本可能是16MB庶艾,不同設(shè)備有不同的大小
- 另一種情況是當(dāng)前應(yīng)用需要向其他應(yīng)用獲取數(shù)據(jù)袁余,由于是兩個(gè)應(yīng)用,所以必須采用跨進(jìn)程的方式來獲取所需的數(shù)據(jù)咱揍,甚至我們通過系統(tǒng)提供的ContentProvider去查詢數(shù)據(jù)的時(shí)候颖榜,其實(shí)也是一種進(jìn)程間通信,只不過通信細(xì)節(jié)被系統(tǒng)內(nèi)部屏蔽了煤裙,我們無法感知而已
2.2 Android中的多進(jìn)程模式
通過給四大組件指定android:process屬性掩完,我們可以輕易地開啟多進(jìn)程模式
-
開啟多進(jìn)程模式
- SecondActivity和ThirdActivity的android:process屬性分別為“:remote”和“com.ryg.chapter_2.remote”,那么這兩種方式有區(qū)別嗎硼砰?
- 首先且蓬,“:”的含義是指要在當(dāng)前的進(jìn)程名前面附加上當(dāng)前的包名,這是一種簡寫的方法题翰,對于SecondActivity來說恶阴,它完整的進(jìn)程名為com.ryg.chapter_2:remote,這一點(diǎn)通過圖2-1和2-2中的進(jìn)程信息也能看出來豹障,而對于ThirdActivity中的聲明方式冯事,它是一種完整的命名方式,不會(huì)附加包名信息
- 其次沼填,進(jìn)程名以“:”開頭的進(jìn)程屬于當(dāng)前應(yīng)用的私有進(jìn)程桅咆,其他應(yīng)用的組件不可以和它跑在同一個(gè)進(jìn)程中,而進(jìn)程名不以“:”開頭的進(jìn)程屬于全局進(jìn)程坞笙,其他應(yīng)用通過ShareUID方式可以和它跑在同一個(gè)進(jìn)程中岩饼。
- Android系統(tǒng)會(huì)為每個(gè)應(yīng)用分配一個(gè)唯一的UID,具有相同UID的應(yīng)用才能共享數(shù)據(jù)薛夜。這里要說明的是籍茧,兩個(gè)應(yīng)用通過ShareUID跑在同一個(gè)進(jìn)程中是有要求的,需要這兩個(gè)應(yīng)用有相同的ShareUID并且簽名相同才可以
- SecondActivity和ThirdActivity的android:process屬性分別為“:remote”和“com.ryg.chapter_2.remote”,那么這兩種方式有區(qū)別嗎硼砰?
-
多進(jìn)程模式的運(yùn)行機(jī)制
- Android為每一個(gè)應(yīng)用分配了一個(gè)獨(dú)立的虛擬機(jī)梯澜,或者說為每個(gè)進(jìn)程都分配一個(gè)獨(dú)立的虛擬機(jī)寞冯,不同的虛擬機(jī)在內(nèi)存分配上有不同的地址空間,這就導(dǎo)致在不同的虛擬機(jī)中訪問同一個(gè)類的對象會(huì)產(chǎn)生多份副本
- 所有運(yùn)行在不同進(jìn)程中的四大組件,只要它們之間需要通過內(nèi)存來共享數(shù)據(jù)吮龄,都會(huì)共享失敗
- 使用多進(jìn)程會(huì)造成如下幾方面的問題:
- 靜態(tài)成員和單例模式完全失效
- 線程同步機(jī)制完全失效
- SharedPreferences的可靠性下降
- Application會(huì)多次創(chuàng)建
2.3. IPC基礎(chǔ)概念介紹
主要包含三方面內(nèi)容:Serializable接口俭茧、Parcelable接口以及Binder,只有熟悉這三方面的內(nèi)容后漓帚,我們才能更好地理解跨進(jìn)程通信的各種方式母债。Serializable和Parcelable接口可以完成對象的序列化過程,當(dāng)我們需要通過Intent和Binder傳輸數(shù)據(jù)時(shí)就需要使用Parcelable或者Serializable尝抖。還有的時(shí)候我們需要把對象持久化到存儲(chǔ)設(shè)備上或者通過網(wǎng)絡(luò)傳輸給其他客戶端毡们,這個(gè)時(shí)候也需要使用Serializable來完成對象的持久化
2.3.1. Serializable接口
- serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化后的數(shù)據(jù)中的serialVersionUID只有和當(dāng)前類的serialVersionUID相同才能夠正常地被反序列化
- 靜態(tài)成員變量屬于類不屬于對象昧辽,所以不會(huì)參與序列化過程衙熔;其次用transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過程
2.3.2. Parcelable接口
Serializable是Java中的序列化接口,其使用起來簡單但是開銷很大搅荞,序列化和反序列化過程需要大量I/O操作红氯。而Parcelable是Android中的序列化方式,因此更適合用在Android平臺(tái)上咕痛,它的缺點(diǎn)就是使用起來稍微麻煩點(diǎn)脖隶,但是它的效率很高,這是Android推薦的序列化方式暇检,因此我們要首選Parcelable
2.3.3. Binder
Binder是Android中的一個(gè)類,它繼承了IBinder接口婉称。從IPC角度來說块仆,Binder是Android中的一種跨進(jìn)程通信方式,Binder還可以理解為一種虛擬的物理設(shè)備王暗,它的設(shè)備驅(qū)動(dòng)是/dev/binder悔据,該通信方式在Linux中沒有;從Android Framework角度來說俗壹,Binder是ServiceManager連接各種Manager(ActivityManager科汗、WindowManager,等等)和相應(yīng)ManagerService的橋梁绷雏;從Android應(yīng)用層來說头滔,Binder是客戶端和服務(wù)端進(jìn)行通信的媒介,當(dāng)bindService的時(shí)候涎显,服務(wù)端會(huì)返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對象坤检,通過這個(gè)Binder對象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù)期吓,這里的服務(wù)包括普通服務(wù)和基于AIDL的服務(wù)
首先早歇,它聲明了兩個(gè)方法getBookList和addBook,顯然這就是我們在IBookManager.aidl中所聲明的方法,同時(shí)它還聲明了兩個(gè)整型的id分別用于標(biāo)識(shí)這兩個(gè)方法箭跳,這兩個(gè)id用于標(biāo)識(shí)在transact過程中客戶端所請求的到底是哪個(gè)方法晨另。接著,它聲明了一個(gè)內(nèi)部類Stub谱姓,這個(gè)Stub就是一個(gè)Binder類借尿,當(dāng)客戶端和服務(wù)端都位于同一個(gè)進(jìn)程時(shí),方法調(diào)用不會(huì)走跨進(jìn)程的transact過程逝段,而當(dāng)兩者位于不同進(jìn)程時(shí)垛玻,方法調(diào)用需要走transact過程,這個(gè)邏輯由Stub的內(nèi)部代理類Proxy來完成奶躯。這么來看帚桩,IBookManager這個(gè)接口的確很簡單,但是我們也應(yīng)該認(rèn)識(shí)到嘹黔,這個(gè)接口的核心實(shí)現(xiàn)就是它的內(nèi)部類Stub和Stub的內(nèi)部代理類Proxy
- DESCRIPTOR
Binder的唯一標(biāo)識(shí)账嚎,一般用當(dāng)前Binder的類名表示 - asInterface(android.os.IBinder obj)
用于將服務(wù)端的Binder對象轉(zhuǎn)換成客戶端所需的AIDL接口類型的對象,這種轉(zhuǎn)換過程是區(qū)分進(jìn)程的儡蔓,如果客戶端和服務(wù)端位于同一進(jìn)程郭蕉,那么此方法返回的就是服務(wù)端的Stub對象本身,否則返回的是系統(tǒng)封裝后的Stub.proxy對象 - asBinder
此方法用于返回當(dāng)前Binder對象 - onTransact
這個(gè)方法運(yùn)行在服務(wù)端中的Binder線程池中喂江,當(dāng)客戶端發(fā)起跨進(jìn)程請求時(shí)召锈,遠(yuǎn)程請求會(huì)通過系統(tǒng)底層封裝后交由此方法來處理。該方法的原型為public Boolean onTransact(int code,android.os.Parcel data,android.os.Parcel reply,int flags)获询。服務(wù)端通過code可以確定客戶端所請求的目標(biāo)方法是什么涨岁,接著從data中取出目標(biāo)方法所需的參數(shù)(如果目標(biāo)方法有參數(shù)的話),然后執(zhí)行目標(biāo)方法吉嚣。當(dāng)目標(biāo)方法執(zhí)行完畢后梢薪,就向reply中寫入返回值(如果目標(biāo)方法有返回值的話),onTransact方法的執(zhí)行過程就是這樣的 - Proxy#getBookList
這個(gè)方法運(yùn)行在客戶端尝哆,當(dāng)客戶端遠(yuǎn)程調(diào)用此方法時(shí)秉撇,它的內(nèi)部實(shí)現(xiàn)是這樣的:首先創(chuàng)建該方法所需要的輸入型Parcel對象_data、輸出型Parcel對象_reply和返回值對象List秋泄;然后把該方法的參數(shù)信息寫入_data中(如果有參數(shù)的話)琐馆;接著調(diào)用transact方法來發(fā)起RPC(遠(yuǎn)程過程調(diào)用)請求,同時(shí)當(dāng)前線程掛起印衔;然后服務(wù)端的onTransact方法會(huì)被調(diào)用啡捶,直到RPC過程返回后,當(dāng)前線程繼續(xù)執(zhí)行奸焙,并從_reply中取出RPC過程的返回結(jié)果瞎暑;最后返回_reply中的數(shù)據(jù)
-
需要注意事項(xiàng)
- 首先彤敛,當(dāng)客戶端發(fā)起遠(yuǎn)程請求時(shí),由于當(dāng)前線程會(huì)被掛起直至服務(wù)端進(jìn)程返回?cái)?shù)據(jù)了赌,所以如果一個(gè)遠(yuǎn)程方法是很耗時(shí)的墨榄,那么不能在UI線程中發(fā)起此遠(yuǎn)程請求;
- 其次勿她,由于服務(wù)端的Binder方法運(yùn)行在Binder的線程池中袄秩,所以Binder方法不管是否耗時(shí)都應(yīng)該采用同步的方式去實(shí)現(xiàn),因?yàn)樗呀?jīng)運(yùn)行在一個(gè)線程中了
Binder的兩個(gè)很重要的方法linkToDeath和unlinkToDeath
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ì)受到影響玻蝌。為了解決這個(gè)問題蟹肘,Binder中提供了兩個(gè)配對的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設(shè)置一個(gè)死亡代理俯树,當(dāng)Binder死亡時(shí)帘腹,我們就會(huì)收到通知,這個(gè)時(shí)候我們就可以重新發(fā)起連接請求從而恢復(fù)連接
2.4 Android中的IPC方式
具體方式有很多许饿,比如可以通過在Intent中附加extras來傳遞信息阳欲,或者通過共享文件的方式來共享數(shù)據(jù),還可以采用Binder方式來跨進(jìn)程通信陋率,另外胸完,ContentProvider天生就是支持跨進(jìn)程訪問的,因此我們也可以采用它來進(jìn)行IPC翘贮。此外,通過網(wǎng)絡(luò)通信也是可以實(shí)現(xiàn)數(shù)據(jù)傳遞的爆惧,所以Socket也可以實(shí)現(xiàn)IPC狸页。上述所說的各種方法都能實(shí)現(xiàn)IPC
2.4.1 使用Bundle
由于Bundle實(shí)現(xiàn)了Parcelable接口,所以它可以方便地在不同的進(jìn)程間傳輸扯再∩衷牛基于這一點(diǎn),當(dāng)我們在一個(gè)進(jìn)程中啟動(dòng)了另一個(gè)進(jìn)程的Activity熄阻、Service和Receiver斋竞,我們就可以在Bundle中附加我們需要傳輸給遠(yuǎn)程進(jìn)程的信息并通過Intent發(fā)送出去。
除了直接傳遞數(shù)據(jù)這種典型的使用場景秃殉,它還有一種特殊的使用場景坝初。比如A進(jìn)程正在進(jìn)行一個(gè)計(jì)算浸剩,計(jì)算完成后它要啟動(dòng)B進(jìn)程的一個(gè)組件并把計(jì)算結(jié)果傳遞給B進(jìn)程,可是遺憾的是這個(gè)計(jì)算結(jié)果不支持放入Bundle中鳄袍,因此無法通過Intent來傳輸绢要,這個(gè)時(shí)候如果我們用其他IPC方式就會(huì)略顯復(fù)雜∞中。可以考慮如下方式:我們通過Intent啟動(dòng)進(jìn)程B的一個(gè)Service組件(比如IntentService)重罪,讓Service在后臺(tái)進(jìn)行計(jì)算,計(jì)算完畢后再啟動(dòng)B進(jìn)程中真正要啟動(dòng)的目標(biāo)組件哀九,由于Service也運(yùn)行在B進(jìn)程中剿配,所以目標(biāo)組件就可以直接獲取計(jì)算結(jié)果,這樣一來就輕松解決了跨進(jìn)程的問題阅束。這種方式的核心思想在于將原本需要在A進(jìn)程的計(jì)算任務(wù)轉(zhuǎn)移到B進(jìn)程的后臺(tái)Service中去執(zhí)行呼胚,這樣就成功地避免了進(jìn)程間通信問題,而且只用了很小的代價(jià)围俘。
2.4.2 使用文件共享
文件共享方式適合在對數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信砸讳,并且要妥善處理并發(fā)讀/寫的問題。
2.4.3 使用Messenger
Messenger可以翻譯為信使界牡,顧名思義簿寂,通過它可以在不同進(jìn)程中傳遞Message對象,在Message中放入我們需要傳遞的數(shù)據(jù)宿亡,就可以輕松地實(shí)現(xiàn)數(shù)據(jù)的進(jìn)程間傳遞了常遂。Messenger是一種輕量級(jí)的IPC方案,它的底層實(shí)現(xiàn)是AIDL
Messenger的使用方法很簡單挽荠,它對AIDL做了封裝克胳,使得我們可以更簡便地進(jìn)行進(jìn)程間通信。同時(shí)圈匆,由于它一次處理一個(gè)請求漠另,因此在服務(wù)端我們不用考慮線程同步的問題,這是因?yàn)榉?wù)端中不存在并發(fā)執(zhí)行的情形
- 服務(wù)端進(jìn)程
我們需要在服務(wù)端創(chuàng)建一個(gè)Service來處理客戶端的連接請求跃赚,同時(shí)創(chuàng)建一個(gè)Handler并通過它來創(chuàng)建一個(gè)Messenger對象笆搓,然后在Service的onBind中返回這個(gè)Messenger對象底層的Binder即可。 - 客戶端進(jìn)程
客戶端進(jìn)程中纬傲,首先要綁定服務(wù)端的Service满败,綁定成功后用服務(wù)端返回的IBinder對象創(chuàng)建一個(gè)Messenger,通過這個(gè)Messenger就可以向服務(wù)端發(fā)送消息了叹括,發(fā)消息類型為Message對象算墨。如果需要服務(wù)端能夠回應(yīng)客戶端,就和服務(wù)端一樣汁雷,我們還需要?jiǎng)?chuàng)建一個(gè)Handler并創(chuàng)建一個(gè)新的Messenger净嘀,并把這個(gè)Messenger對象通過Message的replyTo參數(shù)傳遞給服務(wù)端报咳,服務(wù)端通過這個(gè)replyTo參數(shù)就可以回應(yīng)客戶端。
服務(wù)端代碼
public class MessengerService extends Service {
private static final String TAG = "MessengerService";
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.i(TAG,"receive msg from Client:" + msg.getData().
getString("msg"));
break;
default:
super.handleMessage(msg);
}
}
}
private final Messenger mMessenger = new Messenger(new Messenger-
Handler());
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
}
客戶端代碼
public class MessengerActivity extends Activity {
private static final String TAG = " MessengerActivity";
private Messenger mService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,IBinder
service) {
mService = new Messenger(service);
Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello,this is client.");
msg.setData(data);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_messenger);
Intent intent = new Intent(this,MessengerService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
上面的例子演示了如何在服務(wù)端接收客戶端中發(fā)送的消息面粮,但是有時(shí)候我們還需要能回應(yīng)客戶端少孝,下面就介紹如何實(shí)現(xiàn)這種效果。
服務(wù)端修改
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_CLIENT:
Log.i(TAG,"receive msg from Client:" + msg.getData().getString
("msg"));
Messenger client = msg.replyTo;
Message relpyMessage = Message.obtain(null,MyConstants.MSG_
FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("reply","嗯熬苍,你的消息我已經(jīng)收到稍走,稍后會(huì)回復(fù)你。");
relpyMessage.setData(bundle);
try {
client.send(relpyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
客戶端修改
private Messenger mGetReplyMessenger = new Messenger(new Messenger-
Handler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MyConstants.MSG_FROM_SERVICE:
Log.i(TAG,"receive msg from Service:" + msg.getData().
getString("reply"));
break;
default:
super.handleMessage(msg);
}
}
}
"還有很關(guān)鍵的一點(diǎn)柴底,當(dāng)客戶端發(fā)送消息的時(shí)候婿脸,需要把接收服務(wù)端回復(fù)的Messenger通過Message的replyTo參數(shù)傳遞給服務(wù)端"
mService = new Messenger(service);
Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
Bundle data = new Bundle();
data.putString("msg","hello,this is client.");
msg.setData(data);
//注意下面這句
msg.replyTo = mGetReplyMessenger;
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
2.4.4 使用AIDL
Messenger是以串行的方式處理客戶端發(fā)來的消息,如果大量的消息同時(shí)發(fā)送到服務(wù)端柄驻,服務(wù)端仍然只能一個(gè)個(gè)處理狐树,如果有大量的并發(fā)請求,那么用Messenger就不太合適了鸿脓。同時(shí)抑钟,Messenger的作用主要是為了傳遞消息,很多時(shí)候我們可能需要跨進(jìn)程調(diào)用服務(wù)端的方法野哭,這種情形用Messenger就無法做到了在塔,但是我們可以使用AIDL來實(shí)現(xiàn)跨進(jìn)程的方法調(diào)用。
服務(wù)端
服務(wù)端首先要?jiǎng)?chuàng)建一個(gè)Service用來監(jiān)聽客戶端的連接請求拨黔,然后創(chuàng)建一個(gè)AIDL文件蛔溃,將暴露給客戶端的接口在這個(gè)AIDL文件中聲明,最后在Service中實(shí)現(xiàn)這個(gè)AIDL接口即可篱蝇。客戶端
客戶端所要做事情就稍微簡單一些贺待,首先需要綁定服務(wù)端的Service,綁定成功后零截,將服務(wù)端返回的Binder對象轉(zhuǎn)成AIDL接口所屬的類型麸塞,接著就可以調(diào)用AIDL中的方法了。AIDL接口的創(chuàng)建
- 如果AIDL文件中用到了自定義的Parcelable對象涧衙,那么必須新建一個(gè)和它同名的AIDL文件喘垂,并在其中聲明它為Parcelable類型。
- AIDL中除了基本數(shù)據(jù)類型绍撞,其他類型的參數(shù)必須標(biāo)上方向:in、out或者inout得院,in表示輸入型參數(shù)傻铣,out表示輸出型參數(shù),inout表示輸入輸出型參數(shù)祥绞。
- AIDL接口中只支持方法非洲,不支持聲明靜態(tài)常量鸭限,這一點(diǎn)區(qū)別于傳統(tǒng)的接口。
- AIDL的包結(jié)構(gòu)在服務(wù)端和客戶端要保持一致两踏,否則運(yùn)行會(huì)出錯(cuò)败京,這是因?yàn)榭蛻舳诵枰葱蛄谢?wù)端中和AIDL接口相關(guān)的所有類,如果類的完整路徑不一樣的話梦染,就無法成功反序列化赡麦,程序也就無法正常運(yùn)行。
- 遠(yuǎn)程服務(wù)端Service的實(shí)現(xiàn)
public class BookManagerService extends Service {
private static final String TAG = "BMS";
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArray-
List<Book>();
private Binder mBinder = new IBookManager.Stub() {
@Override
public List<Book> getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(2,"Ios"));
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
- 客戶端的實(shí)現(xiàn)
public class BookManagerActivity extends Activity {
private static final String TAG = "BookManagerActivity";
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,IBinder
service) {
IBookManager bookManager = IBookManager.Stub.asInterface
(service);
try {
List<Book> list = bookManager.getBookList();
Log.i(TAG,"query book list,list type:" + list.getClass().
getCanonicalName());
Log.i(TAG,"query book list:" + list.toString());
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void onServiceDisconnected(ComponentName className) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_book_manager);
Intent intent = new Intent(this,BookManagerService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(mConnection);
super.onDestroy();
}
}
這種解注冊的處理方式在日常開發(fā)過程中時(shí)常使用到帕识,但是放到多進(jìn)程中卻無法奏效泛粹,因?yàn)锽inder會(huì)把客戶端傳遞過來的對象重新轉(zhuǎn)化并生成一個(gè)新的對象。雖然我們在注冊和解注冊過程中使用的是同一個(gè)客戶端對象肮疗,但是通過Binder傳遞到服務(wù)端后晶姊,卻會(huì)產(chǎn)生兩個(gè)全新的對象。別忘了對象是不能跨進(jìn)程直接傳輸?shù)奈被酰瑢ο蟮目邕M(jìn)程傳輸本質(zhì)上都是反序列化的過程们衙,這就是為什么AIDL中的自定義對象都必須要實(shí)現(xiàn)Parcelable接口的原因。那么到底我們該怎么做才能實(shí)現(xiàn)解注冊功能呢?答案是使用RemoteCallbackList喇伯。
RemoteCallbackList是系統(tǒng)專門提供的用于刪除跨進(jìn)程listener的接口码耐。Remote-CallbackList是一個(gè)泛型,支持管理任意的AIDL接口脆荷,這點(diǎn)從它的聲明就可以看出,因?yàn)樗械腁IDL接口都繼承自IInterface接口懊悯,讀者還有印象嗎蜓谋?
public class RemoteCallbackList<E extends IInterface>
雖然說多次跨進(jìn)程傳輸客戶端的同一個(gè)對象會(huì)在服務(wù)端生成不同的對象,但是這些新生成的對象有一個(gè)共同點(diǎn)炭分,那就是它們底層的Binder對象是同一個(gè)桃焕,利用這個(gè)特性,就可以實(shí)現(xiàn)上面我們無法實(shí)現(xiàn)的功能捧毛。當(dāng)客戶端解注冊的時(shí)候观堂,我們只要遍歷服務(wù)端所有的listener,找出那個(gè)和解注冊listener具有相同Binder對象的服務(wù)端listener并把它刪掉即可呀忧,這就是RemoteCallbackList為我們做的事情师痕。同時(shí)RemoteCallbackList還有一個(gè)很有用的功能,那就是當(dāng)客戶端進(jìn)程終止后而账,它能夠自動(dòng)移除客戶端所注冊的listener胰坟。另外,RemoteCallbackList內(nèi)部自動(dòng)實(shí)現(xiàn)了線程同步的功能泞辐,所以我們使用它來注冊和解注冊時(shí)笔横,不需要做額外的線程同步工作竞滓。
使用RemoteCallbackList,有一點(diǎn)需要注意吹缔,我們無法像操作List一樣去操作它商佑,盡管它的名字中也帶個(gè)List,但是它并不是一個(gè)List厢塘。遍歷RemoteCallbackList茶没,必須要按照下面的方式進(jìn)行,其中beginBroadcast和beginBroadcast必須要配對使用俗冻,哪怕我們僅僅是想要獲取RemoteCallbackList中的元素個(gè)數(shù)礁叔,這是必須要注意的地方。
我們知道迄薄,客戶端調(diào)用遠(yuǎn)程服務(wù)的方法琅关,被調(diào)用的方法運(yùn)行在服務(wù)端的Binder線程池中,同時(shí)客戶端線程會(huì)被掛起讥蔽,這個(gè)時(shí)候如果服務(wù)端方法執(zhí)行比較耗時(shí)涣易,就會(huì)導(dǎo)致客戶端線程長時(shí)間地阻塞在這里,而如果這個(gè)客戶端線程是UI線程的話冶伞,就會(huì)導(dǎo)致客戶端ANR新症,這當(dāng)然不是我們想要看到的。因此响禽,如果我們明確知道某個(gè)遠(yuǎn)程方法是耗時(shí)的徒爹,那么就要避免在客戶端的UI線程中去訪問遠(yuǎn)程方法。由于客戶端的onServiceConnected和onService Disconnected方法都運(yùn)行在UI線程中芋类,所以也不可以在它們里面直接調(diào)用服務(wù)端的耗時(shí)方法隆嗅,這點(diǎn)要尤其注意。另外侯繁,由于服務(wù)端的方法本身就運(yùn)行在服務(wù)端的Binder線程池中胖喳,所以服務(wù)端方法本身就可以執(zhí)行大量耗時(shí)操作,這個(gè)時(shí)候切記不要在服務(wù)端方法中開線程去進(jìn)行異步任務(wù)贮竟,除非你明確知道自己在干什么丽焊,否則不建議這么做。
Binder是可能意外死亡的咕别,這往往是由于服務(wù)端進(jìn)程意外停止了技健,這時(shí)我們需要重新連接服務(wù)。有兩種方法惰拱,第一種方法是給Binder設(shè)置DeathRecipient監(jiān)聽雌贱,當(dāng)Binder死亡時(shí),我們會(huì)收到binderDied方法的回調(diào),在binderDied方法中我們可以重連遠(yuǎn)程服務(wù)帽芽,具體方法在Binder那一節(jié)已經(jīng)介紹過了,這里就不再詳細(xì)描述了翔冀。另一種方法是在onServiceDisconnected中重連遠(yuǎn)程服務(wù)导街。這兩種方法我們可以隨便選擇一種來使用,它們的區(qū)別在于:onServiceDisconnected在客戶端的UI線程中被回調(diào)纤子,而binderDied在客戶端的Binder線程池中被回調(diào)搬瑰。也就是說,在binderDied方法中我們不能訪問UI控硼,這就是它們的區(qū)別泽论。
如何在AIDL中使用權(quán)限驗(yàn)證功能
- 第一種方法,我們可以在onBind中進(jìn)行驗(yàn)證卡乾,驗(yàn)證不通過就直接返回null翼悴,這樣驗(yàn)證失敗的客戶端直接無法綁定服務(wù),至于驗(yàn)證方式可以有多種幔妨,比如使用permission驗(yàn)證鹦赎。使用這種驗(yàn)證方式,我們要先在AndroidMenifest中聲明所需的權(quán)限误堡,比如:
<permission
android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
- 第二種方法古话,我們可以在服務(wù)端的onTransact方法中進(jìn)行權(quán)限驗(yàn)證,如果驗(yàn)證失敗就直接返回false锁施,這樣服務(wù)端就不會(huì)終止執(zhí)行AIDL中的方法從而達(dá)到保護(hù)服務(wù)端的效果陪踩。至于具體的驗(yàn)證方式有很多,可以采用permission驗(yàn)證悉抵,具體實(shí)現(xiàn)方式和第一種方法一樣肩狂。還可以采用Uid和Pid來做驗(yàn)證,通過getCallingUid和getCallingPid可以拿到客戶端所屬應(yīng)用的Uid和Pid基跑,通過這兩個(gè)參數(shù)我們可以做一些驗(yàn)證工作婚温,比如驗(yàn)證包名。