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

2.1 Android IPC簡(jiǎn)介

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對(duì)單個(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)前的包名,這是一種簡(jiǎn)寫的方法,對(duì)于SecondActivity來說,它完整的進(jìn)程名為com.ryg.chapter_2:remote,這一點(diǎn)通過圖2-1和2-2中的進(jìn)程信息也能看出來,而對(duì)于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è)類的對(duì)象會(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接口可以完成對(duì)象的序列化過程,當(dāng)我們需要通過Intent和Binder傳輸數(shù)據(jù)時(shí)就需要使用Parcelable或者Serializable。還有的時(shí)候我們需要把對(duì)象持久化到存儲(chǔ)設(shè)備上或者通過網(wǎng)絡(luò)傳輸給其他客戶端,這個(gè)時(shí)候也需要使用Serializable來完成對(duì)象的持久化

2.3.1. Serializable接口

  • serialVersionUID是用來輔助序列化和反序列化過程的,原則上序列化后的數(shù)據(jù)中的serialVersionUID只有和當(dāng)前類的serialVersionUID相同才能夠正常地被反序列化
  • 靜態(tài)成員變量屬于類不屬于對(duì)象,所以不會(huì)參與序列化過程;其次用transient關(guān)鍵字標(biāo)記的成員變量不參與序列化過程

2.3.2. Parcelable接口

Serializable是Java中的序列化接口,其使用起來簡(jiǎn)單但是開銷很大,序列化和反序列化過程需要大量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對(duì)象,通過這個(gè)Binder對(duì)象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù),這里的服務(wù)包括普通服務(wù)和基于AIDL的服務(wù)

首先,它聲明了兩個(gè)方法getBookList和addBook,顯然這就是我們?cè)贗BookManager.aidl中所聲明的方法,同時(shí)它還聲明了兩個(gè)整型的id分別用于標(biāo)識(shí)這兩個(gè)方法,這兩個(gè)id用于標(biāo)識(shí)在transact過程中客戶端所請(qǐng)求的到底是哪個(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è)接口的確很簡(jiǎn)單,但是我們也應(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對(duì)象轉(zhuǎn)換成客戶端所需的AIDL接口類型的對(duì)象,這種轉(zhuǎn)換過程是區(qū)分進(jìn)程的,如果客戶端和服務(wù)端位于同一進(jìn)程,那么此方法返回的就是服務(wù)端的Stub對(duì)象本身,否則返回的是系統(tǒng)封裝后的Stub.proxy對(duì)象
  3. asBinder
    此方法用于返回當(dāng)前Binder對(duì)象
  4. onTransact
    這個(gè)方法運(yùn)行在服務(wù)端中的Binder線程池中,當(dāng)客戶端發(fā)起跨進(jìn)程請(qǐng)求時(shí),遠(yuǎn)程請(qǐng)求會(huì)通過系統(tǒng)底層封裝后交由此方法來處理。該方法的原型為public Boolean onTransact(int code,android.os.Parcel data,android.os.Parcel reply,int flags)。服務(wù)端通過code可以確定客戶端所請(qǐng)求的目標(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對(duì)象_data、輸出型Parcel對(duì)象_reply和返回值對(duì)象List;然后把該方法的參數(shù)信息寫入_data中(如果有參數(shù)的話);接著調(diào)用transact方法來發(fā)起RPC(遠(yuǎn)程過程調(diào)用)請(qǐng)求,同時(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)程請(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è)線程中了
  • 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è)配對(duì)的方法linkToDeath和unlinkToDeath,通過linkToDeath我們可以給Binder設(shè)置一個(gè)死亡代理,當(dāng)Binder死亡時(shí),我們就會(huì)收到通知,這個(gè)時(shí)候我們就可以重新發(fā)起連接請(qǐng)求從而恢復(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)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)了另一個(gè)進(jìn)程的Activity、Service和Receiver,我們就可以在Bundle中附加我們需要傳輸給遠(yuǎn)程進(jìn)程的信息并通過Intent發(fā)送出去。
除了直接傳遞數(shù)據(jù)這種典型的使用場(chǎng)景,它還有一種特殊的使用場(chǎng)景。比如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 使用文件共享

文件共享方式適合在對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信,并且要妥善處理并發(fā)讀/寫的問題。

2.4.3 使用Messenger

Messenger可以翻譯為信使,顧名思義,通過它可以在不同進(jìn)程中傳遞Message對(duì)象,在Message中放入我們需要傳遞的數(shù)據(jù),就可以輕松地實(shí)現(xiàn)數(shù)據(jù)的進(jìn)程間傳遞了。Messenger是一種輕量級(jí)的IPC方案,它的底層實(shí)現(xiàn)是AIDL
Messenger的使用方法很簡(jiǎn)單,它對(duì)AIDL做了封裝,使得我們可以更簡(jiǎn)便地進(jìn)行進(jìn)程間通信。同時(shí),由于它一次處理一個(gè)請(qǐng)求,因此在服務(wù)端我們不用考慮線程同步的問題,這是因?yàn)榉?wù)端中不存在并發(fā)執(zhí)行的情形

  1. 服務(wù)端進(jìn)程
    我們需要在服務(wù)端創(chuàng)建一個(gè)Service來處理客戶端的連接請(qǐng)求,同時(shí)創(chuàng)建一個(gè)Handler并通過它來創(chuàng)建一個(gè)Messenger對(duì)象,然后在Service的onBind中返回這個(gè)Messenger對(duì)象底層的Binder即可。
  2. 客戶端進(jìn)程
    客戶端進(jìn)程中,首先要綁定服務(wù)端的Service,綁定成功后用服務(wù)端返回的IBinder對(duì)象創(chuàng)建一個(gè)Messenger,通過這個(gè)Messenger就可以向服務(wù)端發(fā)送消息了,發(fā)消息類型為Message對(duì)象。如果需要服務(wù)端能夠回應(yīng)客戶端,就和服務(wù)端一樣,我們還需要?jiǎng)?chuàng)建一個(gè)Handler并創(chuàng)建一個(gè)新的Messenger,并把這個(gè)Messenger對(duì)象通過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ā)請(qǐng)求,那么用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)聽客戶端的連接請(qǐng)求,然后創(chuàng)建一個(gè)AIDL文件,將暴露給客戶端的接口在這個(gè)AIDL文件中聲明,最后在Service中實(shí)現(xiàn)這個(gè)AIDL接口即可。

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

  3. AIDL接口的創(chuàng)建

  • 如果AIDL文件中用到了自定義的Parcelable對(duì)象,那么必須新建一個(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();
         }
    }

這種解注冊(cè)的處理方式在日常開發(fā)過程中時(shí)常使用到,但是放到多進(jìn)程中卻無法奏效,因?yàn)锽inder會(huì)把客戶端傳遞過來的對(duì)象重新轉(zhuǎn)化并生成一個(gè)新的對(duì)象。雖然我們?cè)谧?cè)和解注冊(cè)過程中使用的是同一個(gè)客戶端對(duì)象,但是通過Binder傳遞到服務(wù)端后,卻會(huì)產(chǎn)生兩個(gè)全新的對(duì)象。別忘了對(duì)象是不能跨進(jìn)程直接傳輸?shù)模瑢?duì)象的跨進(jìn)程傳輸本質(zhì)上都是反序列化的過程,這就是為什么AIDL中的自定義對(duì)象都必須要實(shí)現(xiàn)Parcelable接口的原因。那么到底我們?cè)撛趺醋霾拍軐?shí)現(xiàn)解注冊(cè)功能呢?答案是使用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è)對(duì)象會(huì)在服務(wù)端生成不同的對(duì)象,但是這些新生成的對(duì)象有一個(gè)共同點(diǎn),那就是它們底層的Binder對(duì)象是同一個(gè),利用這個(gè)特性,就可以實(shí)現(xiàn)上面我們無法實(shí)現(xiàn)的功能。當(dāng)客戶端解注冊(cè)的時(shí)候,我們只要遍歷服務(wù)端所有的listener,找出那個(gè)和解注冊(cè)listener具有相同Binder對(duì)象的服務(wù)端listener并把它刪掉即可,這就是RemoteCallbackList為我們做的事情。同時(shí)RemoteCallbackList還有一個(gè)很有用的功能,那就是當(dāng)客戶端進(jìn)程終止后,它能夠自動(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和beginBroadcast必須要配對(duì)使用,哪怕我們僅僅是想要獲取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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容