【Android開發(fā)藝術(shù)探索】IPC機(jī)制

個(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è)線程中了。

02.Binder的工作機(jī)制.png

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í)行的情形。

02.Messenger的工作原理.png
  • 使用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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搬瑰,隨后出現(xiàn)的幾起案子控硼,更是在濱河造成了極大的恐慌,老刑警劉巖佩厚,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件说订,死亡現(xiàn)場(chǎng)離奇詭異潮瓶,居然都是意外死亡钙姊,警方通過查閱死者的電腦和手機(jī)煞额,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胀莹,“玉大人婚温,你說我怎么就攤上這事≌っ” “怎么了?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵步绸,是天一觀的道長吃媒。 經(jīng)常有香客問我晓折,道長兽泄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任胃珍,我火速辦了婚禮蜓陌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钮热。我一直安慰自己晚顷,他們只是感情好把鉴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布宏蛉。 她就那樣靜靜地躺著,像睡著了一般揍堰。 火紅的嫁衣襯著肌膚如雪嗅义。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天西采,我揣著相機(jī)與錄音继控,去河邊找鬼。 笑死霹崎,一個(gè)胖子當(dāng)著我的面吹牛冶忱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播囚枪,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼链沼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了括勺?” 一聲冷哼從身側(cè)響起疾捍,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奖恰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瑟啃,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翰守,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了了袁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湿颅。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡油航,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谊囚,到底是詐尸還是另有隱情,我是刑警寧澤函筋,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布奠伪,位于F島的核電站,受9級(jí)特大地震影響谨敛,放射性物質(zhì)發(fā)生泄漏滤否。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一肥惭、第九天 我趴在偏房一處隱蔽的房頂上張望紊搪。 院中可真熱鬧全景,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乓梨,卻和暖如春清酥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背焰轻。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國打工辱志, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人揩懒。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓旭从,卻偏偏與公主長得像,于是被迫代替她去往敵國和親和悦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容