使用Bundle
四大組件中的三大組件(Activity、Service、Receiver)都是支持在Intent中傳遞Bundle數(shù)據(jù)的。
所以我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)了另外一個(gè)進(jìn)程的Activity、Service奈辰、Receiver,我們就可以在Bundle中附加我們需要傳輸給遠(yuǎn)程進(jìn)程的信息并通過Intent發(fā)送出去乱豆。
Intent intent = new Intent(BundleFirstActivity.this, BundleSecondActivity.class);
intent.putExtra("User", new User("haha", 222));
startActivity(intent);
特殊場(chǎng)景:
當(dāng)A進(jìn)程計(jì)算出來了一個(gè)結(jié)果奖恰,需要傳輸給B進(jìn)程,但是傳輸?shù)臄?shù)據(jù)不支持放入Bundle中,因此無法通過Intent來傳輸瑟啃。這個(gè)時(shí)候我們可以通過Intent啟動(dòng)進(jìn)程B的一個(gè)Service組件(比如IntentService)论泛,讓Service在后臺(tái)計(jì)算,計(jì)算完成后再啟動(dòng)B進(jìn)程中真要需要的目標(biāo)組件蛹屿。
使用文件共享
共享文件也是一種不錯(cuò)的進(jìn)程間通信方式屁奏,兩個(gè)進(jìn)程通過讀/寫同一個(gè)文件來交換數(shù)據(jù),比如A進(jìn)程把數(shù)據(jù)寫入文件错负,B進(jìn)程通過讀取這個(gè)文件來獲取數(shù)據(jù)坟瓢。
需要注意的是并發(fā)情況下的讀/寫,可能造成數(shù)據(jù)錯(cuò)亂犹撒。
可能有人會(huì)想到SharedPreferences载绿,但是SharedPreferences是個(gè)特例。從本質(zhì)上來說油航,SharedPreferences也屬于文件的一種,但是由于系統(tǒng)對(duì)它的讀/寫有一定的緩存策略怀浆,即在內(nèi)存中會(huì)有一個(gè)SharedPreferences文件的緩存谊囚,因此在多進(jìn)程模式下,系統(tǒng)對(duì)它的讀/寫就變得不可靠执赡,當(dāng)面對(duì)高并發(fā)的讀/寫訪問镰踏,SharedPreferences有很大幾率會(huì)丟失數(shù)據(jù),因此沙合,不建議在進(jìn)程間通信中使用SharedPreferences奠伪。
每個(gè)應(yīng)用的SharedPreferences文件都可以在當(dāng)前包所在的data目錄下查看到。一般來說首懈,它的目錄位于/data/data/package name/share_prefs目錄下绊率。
使用Messenger
Messenger是一種輕量級(jí)的IPC方案,它的底層實(shí)現(xiàn)是AIDL究履。
由于它一次處理一個(gè)請(qǐng)求滤否,因此在服務(wù)端我們不用考慮線程同步的問題,這是因?yàn)榉?wù)端中不存在并發(fā)執(zhí)行的情形最仑。正是因?yàn)镸essenger是以串行的方式處理客戶端發(fā)來的消息藐俺,如果大量的消息同時(shí)發(fā)送到服務(wù)端,服務(wù)端仍然只能一個(gè)個(gè)處理泥彤,如果有大量的并發(fā)請(qǐng)求欲芹,那么用Messenger就不太合適 了。同時(shí)吟吝,Messenger的作用主要是為了傳遞消息菱父,很多時(shí)候我們可能需要跨進(jìn)程調(diào)用服務(wù)端的方法,這種情形用Messenger就無法做到了。
使用AIDL(Android Interface Defined Language(安卓接口定義語言))
如上所說Messenger無法完成跨進(jìn)程調(diào)用服務(wù)端的方法滞伟,而且不能處理并發(fā)請(qǐng)求揭鳞。但是我們可以使用AIDL來實(shí)現(xiàn)跨進(jìn)程的方法調(diào)用。AIDL也是Messenger的底層實(shí)現(xiàn)梆奈,因此Messenger本質(zhì)上也是AIDL野崇,只不過系統(tǒng)為我們做了封裝從而方便上層的調(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.客戶端
首先需要綁定服務(wù)端的Service扶镀,然后將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)成AIDL接口所屬類型,最后調(diào)用AIDL中的方法焰轻。
3.AIDL接口的創(chuàng)建
默認(rèn)情況下臭觉,AIDL 支持下列數(shù)據(jù)類型:
- Java 編程語言中的所有原語類型(如 int、long辱志、char蝠筑、boolean 等等)
- String
- CharSequence
- List
List 中的所有元素都必須是以上列表中支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或您聲明的可打包類型揩懒。 可選擇將 List 用作“通用”類(例如什乙,List<String>)。另一端實(shí)際接收的具體類始終是 ArrayList已球,但生成的方法使用的是 List 接口臣镣。 - Map
Map 中的所有元素都必須是以上列表中支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或您聲明的可打包類型智亮。 不支持通用 Map(如 Map<String,Integer> 形式的 Map)忆某。 另一端實(shí)際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 接口阔蛉。 - Parcelable
所有實(shí)現(xiàn)了Parcelable接口的對(duì)象褒繁。
注意: 如果AIDL文件中用到了自定義的Parcelable對(duì)象,那么必須新建一個(gè)和它同名的AIDL文件馍忽,并在其中聲明它為Parcelable類型棒坏。
// Book.aidl
package com.whyalwaysmea.ipc;
parcelable Book;
4.遠(yuǎn)程服務(wù)端Service的實(shí)現(xiàn)
public class BookManagerService extends Service {
// CopyOnWriteArrayList支持并發(fā)讀/寫
// 因?yàn)锳IDL方法是在服務(wù)端的Binder線程池中執(zhí)行的,因此當(dāng)多個(gè)客戶端同時(shí)連接的時(shí)候遭笋,會(huì)存在多個(gè)線程同時(shí)訪問的情形
// 所以我們要在AIDL方法中處理線程同步
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
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();
}
}
5.客戶端的實(shí)現(xiàn)
首先要綁定遠(yuǎn)程服務(wù)坝冕,綁定成功后將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)換成AIDL接口,然后就可以通過這個(gè)接口去調(diào)用服務(wù)端的遠(yuǎn)程方法了瓦呼。
public class BookManagerActivity extends AppCompatActivity {
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
try {
List<Book> bookList = bookManager.getBookList();
System.out.println("book list type : " + bookList.getClass().getCanonicalName());
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@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();
}
}
可能有人會(huì)發(fā)現(xiàn)喂窟,在Service中测暗,我們返回的數(shù)據(jù)類型是CopyOnWriteArrayList,但是到了Activity中磨澡,接收到了卻是List碗啄。這是為什么呢?
AIDL中能夠使用的List只有ArrayList稳摄,但是我們這里卻使用了CopyOnWriteArrayList(注意它并不是繼承自ArrayList)稚字。這是因?yàn)锳IDL中所支持的是抽象的List,而List只是一個(gè)接口厦酬,因此雖然服務(wù)端返回的是CopyOnWriteArrayList胆描,但是在Binder中會(huì)按照List的規(guī)范去訪問數(shù)據(jù)并最終形成一個(gè)ArrayList傳遞給客戶端。
RemoteCallbackList
RemoteCallbackList是系統(tǒng)專門提供的用于刪除跨進(jìn)程Listener的接口仗阅。RemoteCallbackList是一個(gè)泛型昌讲,支持管理任意的AIDL接口,因?yàn)樗械腁IDL接口都繼承自IInterface接口减噪。
注意
客戶端調(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。所以馒吴,如果我們明確知道某個(gè)遠(yuǎn)程方法是耗時(shí)的扎运,那么就要避免在客戶端的UI線程中去訪問遠(yuǎn)程的耗時(shí)方法。
同理饮戳,當(dāng)遠(yuǎn)程服務(wù)端需要調(diào)用客戶端的方法時(shí)豪治,被調(diào)用的方法也運(yùn)行在Binder線程池中,只不過是客戶端的線程池扯罐。所以负拟,我們同樣不可以在服務(wù)端中調(diào)用客戶端的耗時(shí)方法。
Binder是可能意外死亡的歹河,由于服務(wù)端進(jìn)程意外停止了掩浙,這時(shí)我們需要重新連接服務(wù)。
- 給Binder設(shè)備DeathRecipient監(jiān)聽
- 在onServiceDisconnected中重連遠(yuǎn)程服務(wù)秸歧。
兩者的區(qū)別是:onServiceDisconnected在客戶端的UI線程中被回調(diào)厨姚,而binderDied在客戶端的Binder線程池中被回調(diào)。
權(quán)限驗(yàn)證
默認(rèn)情況下键菱,我們的遠(yuǎn)程服務(wù)任何人都可以連接谬墙,但這以你哥哥不是我們?cè)敢饪吹降模晕覀儽仨毤尤霗?quán)限驗(yàn)證功能。
1.在onBind中進(jìn)行驗(yàn)證拭抬,驗(yàn)證不通過就直接返回null部默。比如使用permission驗(yàn)證
<permission
android:name="com.whyalwaysmea.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal" />
public IBinder onbind(Intent intent) {
int check = checkCallingOrSelfPermission("com.whyalwaysmea.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED) {
return null;
}
return mBinder;
}
如果我們自己的應(yīng)用想綁定到服務(wù)上,只需要在它的AndroidMenifest文件中采用如下方式使用permission
<uses-permission android:name="com.whyalwaysmea.permission.ACCESS_BOOK_SERVICE" />
2.我們可以在服務(wù)端的onTransact方法中進(jìn)行權(quán)限驗(yàn)證造虎,如果驗(yàn)證失敗就返回false傅蹂,這樣服務(wù)端就不會(huì)終止執(zhí)行AIDL中的方法從而達(dá)到保護(hù)客戶端的效果。這里可以驗(yàn)證的方式很多累奈,比如permission贬派,Uid,Pid等驗(yàn)證澎媒。
使用ContentProvider
ContentProvider是Android中提供的專門用于不同應(yīng)用間進(jìn)行數(shù)據(jù)共享的方式搞乏,它的底層實(shí)現(xiàn)同樣也是Binder。
系統(tǒng)預(yù)置了許多ContentProvider戒努,比如通訊錄请敦、日程表等,要跨進(jìn)程訪問這些信息储玫,只需要通過ContentProvider的query侍筛、update、insert和delete方法即可撒穷。
關(guān)于自定義ContentProvider的過程:
1.創(chuàng)建一個(gè)自定義的ContentProvider匣椰,名字就叫BookProvider,只需要繼承ContentProvider類并實(shí)現(xiàn)六個(gè)抽象方法即可:onCreate端礼、query禽笑、update、insert蛤奥、delete和geType佳镜。除了onCreate由系統(tǒng)回調(diào)并運(yùn)行在主線程里鬼癣,其他五個(gè)方法均由外界回調(diào)并運(yùn)行在Binder線程池中玉锌。
2.注冊(cè)ContentProvider。
<provider
// ContentProvider的唯一標(biāo)識(shí)翔横,通過這個(gè)屬性外部應(yīng)用就可以訪問我們的BookProvider
android:authorities="com.whyalwaysmea.ipc.provider.BookProvider"
android:name=".provider.BookProvider"
android:permission="com.whyalwaysmea.provider" // 權(quán)限
android:process=":provider"/>
3.通過外部應(yīng)用訪問BookProvider缅刽。
注意
- ContentProvider主要以表格的形式來組織數(shù)據(jù)啊掏,并且可以包含多個(gè)表;
- ContentProvider還支持文件數(shù)據(jù)衰猛,比如圖片脖律、視頻等,系統(tǒng)提供的MediaStore就是文件類型的ContentProvider腕侄;
- ContentProvider對(duì)底層的數(shù)據(jù)存儲(chǔ)方式?jīng)]有任何要求小泉,可以是SQLite芦疏、文件,甚至是內(nèi)存中的一個(gè)對(duì)象都行微姊;
- 要觀察ContentProvider中的數(shù)據(jù)變化情況酸茴,可以通過ContentResolver的registerContentObserver方法來注冊(cè)觀察者;
- query,update,insert,delete四大方法是存在多線程并發(fā)訪問的兢交,因此方法內(nèi)部要做好線程同步薪捍。
使用Socket
Binder連接池
當(dāng)項(xiàng)目規(guī)模很大的時(shí)候,創(chuàng)建很多個(gè)Service是不對(duì)的做法配喳,因?yàn)閟ervice是系統(tǒng)資源酪穿,太多的service會(huì)使得應(yīng)用看起來很重,所以最好是將所有的AIDL放在同一個(gè)Service中去管理晴裹。
整個(gè)工作機(jī)制是:每個(gè)業(yè)務(wù)模塊創(chuàng)建自己的AIDL接口并實(shí)現(xiàn)此接口被济,這個(gè)時(shí)候不同業(yè)務(wù)模塊之間是不能有耦合的,所有實(shí)現(xiàn)細(xì)節(jié)我們要單獨(dú)開來涧团,然后向服務(wù)端提供自己的唯一標(biāo)識(shí)和其對(duì)應(yīng)的Binder對(duì)象只磷;對(duì)于服務(wù)端來說,只需要一個(gè)Service泌绣,服務(wù)端提供一個(gè)queryBinder接口钮追,這個(gè)接口能夠根據(jù)業(yè)務(wù)模塊的特征來返回相應(yīng)的Binder對(duì)象給它們,不同的業(yè)務(wù)模塊拿到所需的Binder對(duì)象后就可以進(jìn)行遠(yuǎn)程方法調(diào)用了阿迈。
選擇合適的IPC方式
