隨著項目的業(yè)務(wù)和復(fù)雜度的增大,對內(nèi)存的壓力越來越明顯,有時不得不使用多進程的方案芬膝,將一些功能放到另一個進程中去完成。其實很多時候形娇,簡單的業(yè)務(wù)也需要開一個單獨的進程锰霜,如音樂播放器。
我們就以音樂播放器為例桐早,播放音樂的實現(xiàn)功能部份我們往往放在Service中去做癣缅,并把這個Service運行在一個單獨的進程中(通過設(shè)置android:process屬性)。這樣做的好處是哄酝,音樂播放器的UI部份在一個進程友存,可以退出釋放內(nèi)存。而Service部份所在的進程沒有UI資源陶衅,占用的內(nèi)存也較少屡立。
麻煩一點的就是,每次UI進程打開Activity界面時需要使用bindService來綁定Service搀军,才能和它進行通訊膨俐,如讀取現(xiàn)在的播放進度,控制暫驼志洌快進等功能焚刺。
其實在音樂播放器這個場景中,Service這個方案是很合理的门烂。但有些時候乳愉,我們需要調(diào)用一個遠(yuǎn)程進程中的功能,但是想在一個同步方法中調(diào)用屯远,而且不能排除用戶是在主線程調(diào)用蔓姚。這種情況如果用Service的方案來實現(xiàn),那么慨丐,我們只能通過bindService去綁定Service獲取它的服務(wù)接口(Binder實例)赂乐,但問題出在這個bindService是異步方法,調(diào)用后要在它的回調(diào)方法ServiceConnection中才能獲取到它的服務(wù)接口咖气。
如果我們想同步調(diào)用一個遠(yuǎn)程進程中提供的方法挨措,應(yīng)該怎么辦呢?
我有想過像系統(tǒng)提供的服務(wù)一樣崩溪,如ActivityManagerService浅役,在系統(tǒng)服務(wù)注冊,然后提供給Client端同步調(diào)用伶唯。
后來一想觉既,這種方案大復(fù)雜了。很多時候我們單獨進程中跑的功能并不復(fù)雜,有點殺雞用牛刀的感覺瞪讼。然后就是查找一些資料钧椰,沒查到什么好的方案,我就去看一下ContentProvider的源碼符欠,想看看它是怎么實現(xiàn)嫡霞,其實他和我的需求很像,可以進程間訪問希柿,每個方法都是同步的诊沪,需要時才啟動,就是方法要封裝成insert/delete/query這樣的曾撤。后來一看端姚,ContentProvider有一個call方法,完全可以實現(xiàn)我的需求挤悉。
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (extras == null) {
return null;
}
extras.setClassLoader(GoeasywayContentProvider.class.getClassLoader());
CallArgs callBundleArgs = extras.getParcelable(METHOD_CALL_ARGS);
......
注意代碼中的extras.setClassLoader渐裸,如果我們需要用extras傳遞自定義的Pacelable對象,那么這里要指定一下它的ClassLoader装悲,不然這里會無法解封CallArgs(自定義的Pacelable對象)橄仆。
可以用method區(qū)分每個方法,參數(shù)可以放到Bundle中衅斩。返回值也是一個Bundle對象盆顾,可以在返回值也傳遞一個自己定義的Pacelable對象。我把參數(shù)和返回值放在一個Pacelable中:
public class CallArgs implements Parcelable {
public String method;
public Object[] methodArgs;
public Object result;
public CallArgs() {
}
protected CallArgs(Parcel in) {
readFromParcel(in);
}
public static final Creator<CallArgs> CREATOR = new Creator<CallArgs>() {
@Override
public CallArgs createFromParcel(Parcel in) {
return new CallArgs(in);
}
@Override
public CallArgs[] newArray(int size) {
return new CallArgs[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(method);
dest.writeArray(methodArgs);
dest.writeValue(result);
}
public void readFromParcel(Parcel in) {
method = in.readString();
methodArgs = in.readArray(CallArgs.class.getClassLoader());
result = in.readValue(CallArgs.class.getClassLoader());
}
}
訪問就是通過getContentResolver().call這個方法畏梆,我們簡單的封裝了一下:
private CallArgs callRemoteMethod(String method, Object... args) {
CallArgs CallArgs = new CallArgs();
CallArgs.method = method;
CallArgs.methodArgs = args;
android.os.Bundle bundleArgs = new android.os.Bundle();
bundleArgs.putParcelable(METHOD_CALL_ARGS, CallArgs);
android.os.Bundle bundleResult = hostContext.getContentResolver().call(
CALL_URI, // 一個Uri,和使用ContentProvider的query方法一樣的
method, null, bundleArgs);
if (bundleResult == null) {
return null;
}
bundleResult.setClassLoader(CallArgs.class.getClassLoader());
CallArgs result = bundleResult.getParcelable(METHOD_CALL_RESULT);
return result;
}
這個使用起來就像一個簡單的API調(diào)用您宪,我們用很少的代碼就實現(xiàn)了一個進程間的同步調(diào)用,不用但心遠(yuǎn)程進程已經(jīng)被系統(tǒng)KILL掉奠涌,Android系統(tǒng)會幫我們重新創(chuàng)一個進程并繼續(xù)之前的調(diào)用宪巨。
這里要注意:
- 在進程間傳遞的對象都是現(xiàn)實Parcelable接口,即CallArgs方法中的參數(shù)(methodArgs)和返回值(result)都應(yīng)該實現(xiàn)Parcelable接口溜畅。
- 進程間通信是通過Binder機制進行的捏卓,每個調(diào)用線程都會被block,不管它是主線程還是子線程慈格,直到Binder的服務(wù)端(本例中是ContentProvider)的binder線程執(zhí)行完成返回給調(diào)用端時怠晴,線程才會繼續(xù)運行。所以如果ContentProvider中的操作較耗時浴捆,最好是用子線程去調(diào)用蒜田。
- 每個Binder線程能傳輸?shù)臄?shù)據(jù)是有大小受Binder緩沖區(qū)的大小限制的,一般一個線程最大能占用128KB选泻。超過這個限制會報TransactionTooLargeException異常冲粤。