進程間通信:bindService的替換方案

隨著項目的業(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)用宪巨。

這里要注意:

  1. 在進程間傳遞的對象都是現(xiàn)實Parcelable接口,即CallArgs方法中的參數(shù)(methodArgs)和返回值(result)都應(yīng)該實現(xiàn)Parcelable接口溜畅。
  1. 進程間通信是通過Binder機制進行的捏卓,每個調(diào)用線程都會被block,不管它是主線程還是子線程慈格,直到Binder的服務(wù)端(本例中是ContentProvider)的binder線程執(zhí)行完成返回給調(diào)用端時怠晴,線程才會繼續(xù)運行。所以如果ContentProvider中的操作較耗時浴捆,最好是用子線程去調(diào)用蒜田。
  2. 每個Binder線程能傳輸?shù)臄?shù)據(jù)是有大小受Binder緩沖區(qū)的大小限制的,一般一個線程最大能占用128KB选泻。超過這個限制會報TransactionTooLargeException異常冲粤。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末美莫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子梯捕,更是在濱河造成了極大的恐慌厢呵,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件傀顾,死亡現(xiàn)場離奇詭異襟铭,居然都是意外死亡,警方通過查閱死者的電腦和手機锣笨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來道批,“玉大人错英,你說我怎么就攤上這事÷”” “怎么了椭岩?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長璃赡。 經(jīng)常有香客問我判哥,道長,這世上最難降的妖魔是什么碉考? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任塌计,我火速辦了婚禮,結(jié)果婚禮上侯谁,老公的妹妹穿的比我還像新娘锌仅。我一直安慰自己,他們只是感情好墙贱,可當(dāng)我...
    茶點故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布热芹。 她就那樣靜靜地躺著,像睡著了一般惨撇。 火紅的嫁衣襯著肌膚如雪伊脓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天魁衙,我揣著相機與錄音报腔,去河邊找鬼。 笑死剖淀,一個胖子當(dāng)著我的面吹牛榄笙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播祷蝌,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼茅撞,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起米丘,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤剑令,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拄查,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吁津,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年堕扶,在試婚紗的時候發(fā)現(xiàn)自己被綠了碍脏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,861評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡稍算,死狀恐怖典尾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情糊探,我是刑警寧澤钾埂,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站科平,受9級特大地震影響褥紫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜瞪慧,卻給世界環(huán)境...
    茶點故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一髓考、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弃酌,春花似錦绳军、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至多柑,卻和暖如春奶是,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背竣灌。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工聂沙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人初嘹。 一個月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓及汉,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屯烦。 傳聞我的和親對象是個殘疾皇子坷随,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,860評論 2 361

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