個(gè)人博客:
http://www.milovetingting.cn
1凡资、Android IPC簡介
IPC是Inter-Process Communication的縮寫秘狞,含義為進(jìn)程間通信或者跨進(jìn)程通信,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過程谈秫。
ANR:Application Not Responding,應(yīng)用無響應(yīng)惕橙。
2、Android中的多進(jìn)程模式
在Android中使用多進(jìn)程,可以通過給四大組件在AndroidMenifest中指定android:process屬性灶伊。默認(rèn)進(jìn)程的進(jìn)程名是包名疆前。
<activity android:process=":remote"/>
<activity android:process="com.example.ipc.remote"/>
上述兩種聲明的區(qū)別:
首先,":"的含義是指要在當(dāng)前的進(jìn)程名前附加上當(dāng)前的包名聘萨,即完整進(jìn)程名為:com.example.ipc:remote,而第二種則為完整的命名方式竹椒。其次,進(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并且簽名相同才可以锨能。在這種情況下,它們可以互相訪問對(duì)方的私有數(shù)據(jù)芍耘,比如data目錄址遇,組件信息等,不管他們是否跑在同一個(gè)進(jìn)程中斋竞。如果在同一個(gè)進(jìn)程中傲隶,還可以共享內(nèi)存數(shù)據(jù)。
3窃页、IPC基礎(chǔ)概念
Serializable接口
靜態(tài)成員變量屬于類不屬于對(duì)象跺株,所以不會(huì)參與序列化過程;用transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過程脖卖。
可以通過重寫writeObject和readObject方法去修改serialize的過程乒省。
Parcelable接口
一個(gè)類只要實(shí)現(xiàn)這個(gè)接口,就可以實(shí)現(xiàn)序列化并可以通過Intent和Binder傳遞畦木。
Serializable是Java中的序列化接口袖扛,其使用起來簡單但是開銷很大,序列化和反序列化過程需要大量I/O操作十籍。Parcelable是Android中的序列化方式蛆封,因此更適合在Android平臺(tái)上,它的缺點(diǎn)是使用起來稍微麻煩勾栗,但是效率很高惨篱,這是Android推薦的序列化方式,因此首選Parcelable围俘。Parcelable主要用在內(nèi)存序列化上砸讳。如果要將對(duì)象序列化到存儲(chǔ)設(shè)備或?qū)?duì)象序列化后通過網(wǎng)絡(luò)傳輸琢融,建議使用Serializable。
Binder
首先簿寂,當(dāng)客戶端發(fā)起遠(yuǎn)程請(qǐng)求時(shí)漾抬,由于當(dāng)前線程會(huì)被掛起直至服務(wù)端進(jìn)程返回?cái)?shù)據(jù),所以如果一個(gè)遠(yuǎn)程方法是很耗時(shí)的常遂,那么不能在UI線程中發(fā)起此遠(yuǎn)程請(qǐng)求纳令;其它,由于服務(wù)端的Binder方法運(yùn)行在Binder的線程池中克胳,所以Binder方法不管是否耗時(shí)都應(yīng)該采用同步的方式去實(shí)現(xiàn)泊碑,因?yàn)樗呀?jīng)運(yùn)行在一個(gè)線程中了。
linkToDeath和unlinkToDeath
Binder運(yùn)行在服務(wù)端進(jìn)程毯欣,如果服務(wù)端進(jìn)程由于某些原因異常終止,這個(gè)時(shí)候我們到服務(wù)端的Binder連接斷裂(稱之為Binder死亡),會(huì)導(dǎo)致我們的遠(yuǎn)程調(diào)用失敗臭脓。如果我們不知道Binder連接已經(jīng)斷裂酗钞,那么客戶端的功能就會(huì)受到影響。為了解決這個(gè)問題来累,Binder中提供了兩個(gè)配對(duì)的方法linkToDeath和unlinkToDeath,通過linkToDeath砚作,可以給Binder設(shè)置一個(gè)死亡代理,當(dāng)Binder死亡時(shí)嘹锁,我們就會(huì)收到通知葫录,這個(gè)時(shí)候就可以重新發(fā)起連接請(qǐng)求從而恢復(fù)連接。具體設(shè)置代理的步驟如下:
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@override
public void binderDied(){
if(mBookManager==null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager=null;
//TODO:重新綁定遠(yuǎn)程Service
}
}
在客戶端綁定遠(yuǎn)程服務(wù)成功后领猾,給binder設(shè)置死亡代理:
mService = IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
通過Binder的isBinderAlive方法可以判斷Binder是否死亡米同。
4、Android中的IPC方式
- 使用Bundle
Bundle實(shí)現(xiàn)了Parcelable接口摔竿,可以方便地在不同的進(jìn)程間傳輸面粮。傳輸?shù)臄?shù)據(jù)必須能被序列化,比如基本類型继低、實(shí)現(xiàn)了Parcelable接口的對(duì)象熬苍、實(shí)現(xiàn)了Serializable接口的對(duì)象以及一些Android支持的特殊對(duì)象。
使用文件共享
使用Messenger
一次處理一個(gè)請(qǐng)求袁翁,因此在服務(wù)端不用考慮線程同步的問題柴底,因?yàn)榉?wù)端中不存在并發(fā)執(zhí)行的情形。
- 使用AIDL
AIDL支持的數(shù)據(jù)類型:
基本數(shù)據(jù)類型(int粱胜、long柄驻、char、boolean焙压、double等)凿歼;
String和CharSequence;
List:只支持ArrayList,里面每個(gè)元素都必須能夠被AIDL支持;
Map:只支持HashMap,里面的每個(gè)元素都必須能夠被AIDL支持褪迟,包括key和value;
Parcelable:所有實(shí)現(xiàn)了Parcelable接口的對(duì)象;
AIDL:所有的AIDL接口本身也可以在AIDL文件中使用。
以上6種數(shù)據(jù)類型就是AIDL所支持的所有類型答憔,其中自定義的Parcelable對(duì)象和AIDL對(duì)象必須要顯式import進(jìn)來味赃,不管它們是否和當(dāng)前的AIDL文件位于同一個(gè)包內(nèi)。
如果AIDL文件中用到了自定義的Parcelable對(duì)象虐拓,那么必須新建一個(gè)和它同名的AIDL文件心俗,并在其中聲明它為Parcelable類型。
AIDL中除了基本數(shù)據(jù)類型蓉驹,其它類型的參數(shù)必須標(biāo)上方向:in城榛、out或者inout,in表示輸入型參數(shù)态兴,out表示輸出型參數(shù)狠持,inout表示輸入輸出型參數(shù)。
AIDL接口只支持方法瞻润,不支持聲明靜態(tài)常量喘垂。
為了方便AIDL開發(fā),建議把所有和AIDL相關(guān)的類和文件全部放入同一個(gè)包中绍撞。AIDL的包結(jié)構(gòu)在服務(wù)端和客戶端要保持一致正勒,否則運(yùn)行會(huì)出錯(cuò)。這是因?yàn)榭蛻舳诵枰葱蛄谢?wù)端中和AIDL接口相關(guān)的所有類傻铣,如果類的完整路徑不一樣的話章贞,就無法成功反序列化,程序也無法正常運(yùn)行非洲。
CopyOnWriteArrayList支持并發(fā)讀/寫鸭限。
RemoteCallbackList是系統(tǒng)專門提供的用于刪除跨進(jìn)程listener的接口。RemoteCallbackList是一個(gè)泛型两踏,支持管理任意的AIDL接口里覆。
public class RemoteCallbackList<E extends IInterface>
在它的內(nèi)部有一個(gè)Map結(jié)構(gòu)專門用來保存所有的AIDL回調(diào)缆瓣,這個(gè)Map的key是IBinder類型,value是Callback類型弓坞。
ArrayMap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();
其中Callback封裝了真正的遠(yuǎn)程listener。當(dāng)客戶端注冊(cè)listener的時(shí)候渡冻,它會(huì)把這個(gè)listener的信息存入mCallbacks中戚扳,其中Key和value分別通過下面的方式獲得:
IBinder key = listener.asBinder();
Callback value = new Callback(listener,cookie);
當(dāng)客戶端解注冊(cè)的時(shí)候,只要遍歷服務(wù)端所有的listener,找出那個(gè)和解注冊(cè)listener具有相同Binder對(duì)象的服務(wù)端listener并把它刪除就可以了帽借。當(dāng)客戶端進(jìn)程終止后,RemoteCallbackList能夠自動(dòng)移除客戶端所注冊(cè)的listener砍艾。RemoteCallbackList內(nèi)部自動(dòng)實(shí)現(xiàn)了線程同步的功能蒂教,所以使用它來注冊(cè)和解注冊(cè)時(shí),不需要做額外的線程同步工作脆荷。
使用RemoteCallbackList,有一點(diǎn)需要注意凝垛。我們無法像操作List一樣去操作它蜓谋,盡管它的名字中也帶個(gè)List,但是它并不是一個(gè)List剑肯。遍歷RemoteCallbackList观堂,必須要按照下面的方式進(jìn)行,其中beginBroadcast和finishBroadcast必須配對(duì)使用型将,哪怕我們僅僅是想要獲取RemoteCallbackList的元素個(gè)數(shù)荐虐。
final int N = mListenerList.beginBroadcast();
for(int i=0;i<N;i++){
IOnNewBookArrivedListener l = mListenerList.getBroadcastItem(i);
if(l!=null){
//TODO
}
}
mListenerList.finishBroadcast();
AIDL中使用權(quán)限驗(yàn)證功能
第一種方法:在onBind中進(jìn)行驗(yàn)證福扬,驗(yàn)證不通過就直接返回null☆醣可以使用permission驗(yàn)證這種驗(yàn)證方式。先在AndroidMenifest中聲明所需的權(quán)限涛菠。
定義權(quán)限后撇吞,就可以在Service的onBind方法做權(quán)限驗(yàn)證。
public IBinder onBind(Intent intent){
int check = checkCallingOrSelfPermission("xx.xx.xx");
if(check==PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}
第二種方法:可以在服務(wù)端的onTransact中進(jìn)行權(quán)限驗(yàn)證迄薄,如果驗(yàn)證失敗煮岁,就直接返回false,這樣服務(wù)端就不會(huì)終止執(zhí)行AIDL中的方法從而達(dá)到保護(hù)服務(wù)端的效果讥蔽。可以驗(yàn)證permission,也可以驗(yàn)證Uid和Pid冶伞。
public boolean onTransact(int code,Parcel data,Parcel reply,int flags) throws RemoteException{
int check = checkCallingOrSelfPermission("xx.xx.xx");
if(check==PackageManager.PERMISSION_DENIED){
return false;
}
String packageName = null;
String[] packages = getPackageManager().getPackgesForUid(getCallingUid());
if(packages!=null&&packages.length>0){
packageName = packages[0];
}
if(!packageName.startWith("xx.xx")){
return false;
}
return super.onTransact(code,data,reply,flags);
}
- 使用ContentProvider
ContentProvider的onCreate方法運(yùn)行在主線程碰缔,其它query、getType瀑焦、insert梗肝、delete、update方法運(yùn)行在Binder線程池中巫击。需要注意,query粹懒、update顷级、insert、delete四大方法存在多線程并發(fā)訪問帽芽,因此方法內(nèi)部要做好線程同步翔冀。
- 使用Socket