2. IPC機(jī)制《Android開發(fā)藝術(shù)探索》

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)程模式

  1. 開啟多進(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并且簽名相同才可以
  2. 多進(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

  1. DESCRIPTOR
    Binder的唯一標(biāo)識(shí)账嚎,一般用當(dāng)前Binder的類名表示
  2. asInterface(android.os.IBinder obj)
    用于將服務(wù)端的Binder對象轉(zhuǎn)換成客戶端所需的AIDL接口類型的對象,這種轉(zhuǎn)換過程是區(qū)分進(jìn)程的儡蔓,如果客戶端和服務(wù)端位于同一進(jìn)程郭蕉,那么此方法返回的就是服務(wù)端的Stub對象本身,否則返回的是系統(tǒng)封裝后的Stub.proxy對象
  3. asBinder
    此方法用于返回當(dāng)前Binder對象
  4. 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í)行過程就是這樣的
  5. 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í)行的情形

  1. 服務(wù)端進(jìn)程
    我們需要在服務(wù)端創(chuàng)建一個(gè)Service來處理客戶端的連接請求跃赚,同時(shí)創(chuàng)建一個(gè)Handler并通過它來創(chuàng)建一個(gè)Messenger對象笆搓,然后在Service的onBind中返回這個(gè)Messenger對象底層的Binder即可。
  2. 客戶端進(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();
    }
Messenger的工作原理

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)用。

  1. 服務(wù)端
    服務(wù)端首先要?jiǎng)?chuàng)建一個(gè)Service用來監(jiān)聽客戶端的連接請求拨黔,然后創(chuàng)建一個(gè)AIDL文件蛔溃,將暴露給客戶端的接口在這個(gè)AIDL文件中聲明,最后在Service中實(shí)現(xiàn)這個(gè)AIDL接口即可篱蝇。

  2. 客戶端
    客戶端所要做事情就稍微簡單一些贺待,首先需要綁定服務(wù)端的Service,綁定成功后零截,將服務(wù)端返回的Binder對象轉(zhuǎn)成AIDL接口所屬的類型麸塞,接著就可以調(diào)用AIDL中的方法了。

  3. 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)行。
  1. 遠(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;
         }
    }

  1. 客戶端的實(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)證包名。

2.4.5 使用ContentProvider

2.4.6 使用Socket

2.5 Binder連接池

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末媳否,一起剝皮案震驚了整個(gè)濱河市栅螟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌篱竭,老刑警劉巖力图,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異掺逼,居然都是意外死亡吃媒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赘那,“玉大人刑桑,你說我怎么就攤上這事∧贾郏” “怎么了祠斧?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長拱礁。 經(jīng)常有香客問我琢锋,道長,這世上最難降的妖魔是什么呢灶? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任吴超,我火速辦了婚禮,結(jié)果婚禮上鸯乃,老公的妹妹穿的比我還像新娘鲸阻。我一直安慰自己,他們只是感情好飒责,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布赘娄。 她就那樣靜靜地躺著,像睡著了一般宏蛉。 火紅的嫁衣襯著肌膚如雪遣臼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天拾并,我揣著相機(jī)與錄音揍堰,去河邊找鬼。 笑死嗅义,一個(gè)胖子當(dāng)著我的面吹牛屏歹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播之碗,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蝙眶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了褪那?” 一聲冷哼從身側(cè)響起幽纷,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎博敬,沒想到半個(gè)月后友浸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡偏窝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年收恢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了武学。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伦意,死狀恐怖火窒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驮肉,我是刑警寧澤沛鸵,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站缆八,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疾捍。R本人自食惡果不足惜奈辰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乱豆。 院中可真熱鬧奖恰,春花似錦、人聲如沸宛裕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揩尸。三九已至蛹屿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岩榆,已是汗流浹背错负。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勇边,地道東北人犹撒。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像粒褒,于是被迫代替她去往敵國和親识颊。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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