Android官方文檔筆記:Bound Services

bound service是CS接口中的服務(wù)端憔维。一個bound service允許組件(例如activity)與它綁定,發(fā)送請求毁枯,接受回應(yīng)甚至執(zhí)行IPC。一般情況下叮称,一個bound service只有在服務(wù)其余應(yīng)用組件的時候才存活而不會單獨(dú)的在后臺運(yùn)行种玛。

基礎(chǔ)

bound service實(shí)現(xiàn)了service的接口,允許其他組件與它綁定并與它交互颅拦。你必須實(shí)現(xiàn)onBind回調(diào)方法來給service提供綁定蒂誉。此方法返回一個IBinder對象,該對象定義了客戶端可用于與service交互的編程接口距帅。

一個客戶端可以調(diào)用bindService來與service綁定右锨。這時客戶端必須提供一個ServiceConnection的實(shí)現(xiàn)來監(jiān)控與service的連接。bindService方法會直接返回碌秸,不帶任何值绍移。但是如果Android系統(tǒng)創(chuàng)建了客戶端與service之間的連接,它會調(diào)用ServiceConnectiononServiceConnected方法來傳遞一個IBinder對象給客戶端讥电,這個IBinder可以用來與service通訊蹂窖。

多個客戶端可以立刻連接到一個service。然而恩敌,系統(tǒng)只會在第一個客戶端與service綁定的時候調(diào)用onBind方法來檢索IBinder瞬测。然后系統(tǒng)會返回相同的IBinder給其余的綁定客戶端,而不是再次調(diào)用onBind纠炮。

當(dāng)最后一個客戶端與service解綁的時候月趟,系統(tǒng)會銷毀該service。(除非該service已經(jīng)被startServie方法啟動過)

如果你允許你的service可以被start也可以被綁定恢口,那么當(dāng)它被start后孝宗,所有客戶端與它解綁的時候,系統(tǒng)是不會銷毀它的耕肩。你還需要調(diào)用stopSelf或者stopService來停止它因妇。

當(dāng)你實(shí)現(xiàn)你的bound service的時候,最重要的部分就是定義onBind回調(diào)方法返回的接口猿诸。有幾種不同的方式來定義你的IBinder接口婚被,下面將討論每一種技術(shù)。

創(chuàng)建一個bound service

當(dāng)創(chuàng)建一個可以綁定的service的時候梳虽,你必須提供一個IBinder摔寨,該IBinder提供了客戶端可以與service交互的編程接口。這里有三種方式可以定義這個接口:

繼承Binder類

如果你的service是你應(yīng)用私有的怖辆,和客戶端運(yùn)行在一個進(jìn)程的是复,你應(yīng)該繼承實(shí)現(xiàn)Binder類來創(chuàng)建你的接口删顶,并且在onBind方法中返回該接口的實(shí)例∈缋龋客戶端接收到Binder后可以用它來直接訪問Binder實(shí)現(xiàn)中的甚至service中的公開方法逗余。

當(dāng)你的service僅僅是你應(yīng)用程序的后臺工作人員的時候,這是首選的技術(shù)季惩。你不使用這種方式創(chuàng)建你的service的唯一的原因是你的service可以被外部應(yīng)用使用或者可以跨進(jìn)程录粱。

使用Messenger

如果你需要你的接口在不同的線程中工作,你可以用Messenger來給service創(chuàng)建一個接口画拾。以這種方式啥繁,service定義了一個handler來回應(yīng)不同類型的Message對象,這個Handler是Messenger的基礎(chǔ)青抛,可以和客戶端分享一個IBinder,允許客戶端用Message向service發(fā)送指令旗闽。客戶端可以定義自己的Messenger,這樣service就可以發(fā)送Message回來蜜另。

這是最簡單的方式來進(jìn)程間通訊适室,因?yàn)镸essenger將所有的請求在一個線程中排出隊(duì)列所以你不需要考慮你的service要是線程安全的。

使用AIDL

AIDL(Android接口定義語言)執(zhí)行所有的工作举瑰,將對象分解成操作系統(tǒng)可以理解的原語捣辆,并在進(jìn)程間編組它們以執(zhí)行IPC。前面的技術(shù)使用了Messenger,這也是基于AIDL作為它的底層結(jié)構(gòu)此迅。像上面提到的汽畴,Messenger在單個線程中給所有的客戶端請求創(chuàng)建了一個隊(duì)列,所以service同一時間只能接收到一個請求耸序。如果你想要你的service可以同時處理多個請求整袁,你可以直接使用AIDL。這時候佑吝,你的service必須能支持多線程且是線程安全的。

要直接使用AIDL绳匀,您必須創(chuàng)建一個定義編程接口的.aidl文件芋忿。Android SDK工具使用此文件生成一個抽象類,該類實(shí)現(xiàn)接口并處理IPC疾棵,然后可以在你的service中繼承它戈钢。

注意:大多數(shù)的應(yīng)用不需要通過AIDL來創(chuàng)建一個bound service。因?yàn)檫@需要支持多線程且導(dǎo)致更加復(fù)雜的實(shí)現(xiàn)是尔。因此殉了,AIDL不適合大多數(shù)的應(yīng)用,這篇文章也不會介紹如何給service使用它拟枚,如果你確定要直接使用AIDL薪铜,請查看專門的AIDL文章众弓。

繼承Binder類

如果你的service只在本應(yīng)用中使用,不需要跨進(jìn)程工作隔箍,那么你可以實(shí)現(xiàn)你自己的IBinder來讓你的客戶端直接訪問service中的公開方法谓娃。

注意:這只有在service和client在同一個進(jìn)程中才有效。

以下是設(shè)置方法:

  1. 在你的service中蜒滩,用以下方法中的一個來創(chuàng)建IBinder實(shí)例:

    • 包含client可以調(diào)用的公開方法
    • 返回當(dāng)前的service實(shí)例滨达,該service包含client可以調(diào)用的公開方法
    • 返回一個service持有的對象,client可以訪問該對象的公開方法
  2. onBind回調(diào)方法中返回該IBinder的實(shí)例俯艰。

  3. 在client中捡遍,接受從onServiceConnected回調(diào)方法中傳來的IBinder,然后可以通過提供的方法調(diào)用service竹握。

說明:service和client必須在同一個線程中的原因是client可以將返回的對象轉(zhuǎn)型并正確的調(diào)用它的API画株。服務(wù)和客戶端也必須處于同一個進(jìn)程中,因?yàn)榇思夹g(shù)不會跨進(jìn)程執(zhí)行任何編組涩搓。

下面有個例子污秆,這個service通過實(shí)現(xiàn)Binder來讓client可以訪問service的方法:

public class LocalService extends Service {

    // Binder given to clients

    private final IBinder mBinder = new LocalBinder();

    // Random number generator

    private final Random mGenerator = new Random();

    /**

     * Class used for the client Binder.  Because we know this service always

     * runs in the same process as its clients, we don't need to deal with IPC.

     */

    public class LocalBinder extends Binder {

        LocalService getService() {

            // Return this instance of LocalService so clients can call public methods

            return LocalService.this;

        }

    }

    @Override

    public IBinder onBind(Intent intent) {

        return mBinder;

    }

    /** method for clients */

    public int getRandomNumber() {

      return mGenerator.nextInt(100);

    }

}

LocalBinder提供了getService方法給client來獲取當(dāng)前LocalService的實(shí)例。這將允許client調(diào)用service的公開方法昧甘。例如client可以調(diào)用service的getRandomNumber方法良拼。

下面是一個activity與LocalService綁定并在按鈕被點(diǎn)擊的時候調(diào)用getRandomNumber方法:

public class BindingActivity extends Activity {

    LocalService mService;

    boolean mBound = false;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

    }

    @Override

    protected void onStart() {

        super.onStart();

        // Bind to LocalService

        Intent intent = new Intent(this, LocalService.class);

        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

    }

    @Override

    protected void onStop() {

        super.onStop();

        // Unbind from the service

        if (mBound) {

            unbindService(mConnection);

            mBound = false;

        }

    }

    /** Called when a button is clicked (the button in the layout file attaches to

      * this method with the android:onClick attribute) */

    public void onButtonClick(View v) {

        if (mBound) {

            // Call a method from the LocalService.

            // However, if this call were something that might hang, then this request should

            // occur in a separate thread to avoid slowing down the activity performance.

            int num = mService.getRandomNumber();

            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();

        }

    }

    /** Defines callbacks for service binding, passed to bindService() */

    private ServiceConnection mConnection = new ServiceConnection() {

        @Override

        public void onServiceConnected(ComponentName className,

                IBinder service) {

            // We've bound to LocalService, cast the IBinder and get LocalService instance

            LocalBinder binder = (LocalBinder) service;

            mService = binder.getService();

            mBound = true;

        }

        @Override

        public void onServiceDisconnected(ComponentName arg0) {

            mBound = false;

        }

    };

}

使用Messenger

如果你的service需要和遠(yuǎn)程進(jìn)程通信,你可以使用一個Messenger來給你的service提供接口充边。這項(xiàng)技術(shù)可以讓你不使用AIDL就能進(jìn)行IPC庸推。

以下是如何使用Messenger的摘要:

  • 該service實(shí)現(xiàn)了一個handler來接受來自client的每個調(diào)用的回調(diào)。
  • Messenger創(chuàng)建了一個IBinder浇冰,service將該IBinder通過onBinder方法返回給client贬媒。
  • 客戶端使用IBinder實(shí)例化Messenger(引用服務(wù)的Handler),客戶端用它將Message對象發(fā)送到service肘习。
  • service在它的Handler中接收到每個Message际乘,明確的說,在handleMessage方法中漂佩。

以這種方式脖含,client在service上沒有方法可以調(diào)用,取而代之的是投蝉,client可以發(fā)送信息养葵,service可以在Handler中接受信息。

以下是一個service使用Messenger的簡單的例子:

public class MessengerService extends Service {

    /** Command to the service to display a message */

    static final int MSG_SAY_HELLO = 1;

    /**

     * Handler of incoming messages from clients.

     */

    class IncomingHandler extends Handler {

        @Override

        public void handleMessage(Message msg) {

            switch (msg.what) {

                case MSG_SAY_HELLO:

                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();

                    break;

                default:

                    super.handleMessage(msg);

            }

        }

    }

    /**

     * Target we publish for clients to send messages to IncomingHandler.

     */

    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**

     * When binding to the service, we return an interface to our messenger

     * for sending messages to the service.

     */

    @Override

    public IBinder onBind(Intent intent) {

        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();

        return mMessenger.getBinder();

    }

}

client需要做的就是使用service返回的IBinder創(chuàng)建一個Messenger并且使用它的send方法發(fā)送信息瘩缆。下面是一個activity綁定到上面service并發(fā)送信息給該service的例子:

public class ActivityMessenger extends Activity {

    /** Messenger for communicating with the service. */

    Messenger mService = null;

    /** Flag indicating whether we have called bind on the service. */

    boolean mBound;

    /**

     * Class for interacting with the main interface of the service.

     */

    private ServiceConnection mConnection = new ServiceConnection() {

        public void onServiceConnected(ComponentName className, IBinder service) {

            // This is called when the connection with the service has been

            // established, giving us the object we can use to

            // interact with the service.  We are communicating with the

            // service using a Messenger, so here we get a client-side

            // representation of that from the raw IBinder object.

            mService = new Messenger(service);

            mBound = true;

        }

        public void onServiceDisconnected(ComponentName className) {

            // This is called when the connection with the service has been

            // unexpectedly disconnected -- that is, its process crashed.

            mService = null;

            mBound = false;

        }

    };

    public void sayHello(View v) {

        if (!mBound) return;

        // Create and send a message to the service, using a supported 'what' value

        Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);

        try {

            mService.send(msg);

        } catch (RemoteException e) {

            e.printStackTrace();

        }

    }

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

    }

    @Override

    protected void onStart() {

        super.onStart();

        // Bind to the service

        bindService(new Intent(this, MessengerService.class), mConnection,

            Context.BIND_AUTO_CREATE);

    }

    @Override

    protected void onStop() {

        super.onStop();

        // Unbind from the service

        if (mBound) {

            unbindService(mConnection);

            mBound = false;

        }

    }

}

注意关拒,這個例子沒有展示service如何對client做出回應(yīng)。如果你想要你的service做出回應(yīng),那么你也需要在client中創(chuàng)建一個Messenger着绊。然后當(dāng)client接收到onServiceConnected回調(diào)的時候谐算,client發(fā)送了一條Message給service,這條Message在它的reply參數(shù)中包含了client的Messenger畔柔。

綁定一個service

應(yīng)用組件(client)可以 通過調(diào)用bindService來和一個service進(jìn)行綁定氯夷。Android系統(tǒng)會調(diào)用service的OnBind方法,該方法返回一個可以與service交互的IBinder靶擦。

綁定是異步的腮考,bindSercice會立刻返回但是不會返回IBinder給client,client必須創(chuàng)建一個ServiceConnection實(shí)例并將它傳遞給bindService玄捕。ServiceConnection包含了一個方法踩蔚,系統(tǒng)會調(diào)用這個方法傳遞IBinder

說明:只有activity枚粘,service和content provider可以和service綁定-你不能將broadcast receiver和service綁定馅闽。

所以,為了能后綁定service馍迄,你必須要做到:

  1. 實(shí)現(xiàn)ServiceConnection

你的實(shí)現(xiàn)必須重寫兩個方法:

  • onServiceConnected()
    系統(tǒng)調(diào)用這個方法來傳送從onBind方法返回的IBinder給client
  • onServiceDisconnected()
    Android系統(tǒng)會在client與service的連接異常斷開的時候(例如service崩潰或者被殺死)調(diào)用該方法福也。client解綁的時候不會調(diào)用該方法。
  1. 調(diào)用bindService,傳遞ServiceConnection的實(shí)現(xiàn)攀圈。

  2. 當(dāng)系統(tǒng)調(diào)用onServiceConnected回調(diào)方法時暴凑,你可以使用接口定義的方法開始調(diào)用service。

  3. 調(diào)用unBindService來解綁赘来。

當(dāng)client銷毀的時候现喳,會與service解綁。但是在你與service交互完成之后或者在activity pause的時候犬辰,你應(yīng)該解綁嗦篱。以便service可以在不使用的時候關(guān)閉。

下面的例子連接了client和一個通過繼承Binder對象來創(chuàng)建的service幌缝。所有要做的就是將返回的IBinder轉(zhuǎn)型為LocalBinder且獲取LocalService的實(shí)例灸促。

LocalService mService;

private ServiceConnection mConnection = new ServiceConnection() {

    // Called when the connection with the service is established

    public void onServiceConnected(ComponentName className, IBinder service) {

        // Because we have bound to an explicit

        // service that is running in our own process, we can

        // cast its IBinder to a concrete class and directly access it.

        LocalBinder binder = (LocalBinder) service;

        mService = binder.getService();

        mBound = true;

    }

    // Called when the connection with the service disconnects unexpectedly

    public void onServiceDisconnected(ComponentName className) {

        Log.e(TAG, "onServiceDisconnected");

        mBound = false;

    }

};

client將該ServiceConnection傳遞給bindService來使用它。例如:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

bindService()的第一個參數(shù)是一個Intent涵卵,它明確地命名要綁定的服務(wù)(該intent可能是隱式的)浴栽。
第二個參數(shù)是ServiceConnetction對象
第三個參數(shù)是一個指示綁定選項(xiàng)的標(biāo)志。它通常應(yīng)該是BIND_AUTO_CREATE以便于在service沒有存活的時候創(chuàng)建它缘厢。其余可能的值為BIND_DEBUG_UNBINDBIND_NOT_FOREGROUND或者0表示沒有甩挫。

附加說明:

以下有一些關(guān)于綁定service的重要說明:
你應(yīng)該總是捕獲DeadObjectException贴硫,這會在連接被打斷的時候拋出。這是遠(yuǎn)程方法唯一拋出的一個異常。
對象是跨進(jìn)程引用計(jì)數(shù)的

你通常應(yīng)該將綁定和解綁與對應(yīng)的client的生命周期中的創(chuàng)建和銷毀對應(yīng)起來英遭。例如:

  • 如果你想你的activity在可見的時候與service交互间护,你應(yīng)該在onSatrt期間綁定在onStop期間解綁。
  • 如果你想你的activity在stop的時候仍然可也接受到回應(yīng)挖诸,那么你可以在onCreate中綁定汁尺,在onDestroy中解綁。注意多律,這意味著你的activity需要在整個生命周期中使用service(即使是在后臺)痴突,如果該service是在另一個進(jìn)程。你增加了該進(jìn)程的負(fù)擔(dān)狼荞,系統(tǒng)更有可能殺死它辽装。

在activity的onResumeonPause期間,通常不應(yīng)該綁定和解除綁定相味,因?yàn)檫@些回調(diào)會在每個生命周期轉(zhuǎn)換時發(fā)生拾积,并且應(yīng)該將在這些轉(zhuǎn)換時發(fā)生的處理保持在最低限度。 另外丰涉,如果應(yīng)用程序中的多個activity綁定到相同的service拓巧,并且這兩個活動之間存在轉(zhuǎn)換,則可能會因?yàn)楫?dāng)前activity在下一個activity綁定(在resume期間)之前解除綁定(在pause期間)而被破壞并重新創(chuàng)建一死。

管理bound service的生命周期

如果一個service被所有的組件解綁肛度,那么Android系統(tǒng)就會銷毀它(除非它也被onStartCommand啟動了)。因此摘符,如果你的service是一個單純的bound service贤斜,那么你不用管理它的生命周期-Android系統(tǒng)會根據(jù)是否有client與之綁定來為你管理。

然而逛裤,如果你選擇了實(shí)現(xiàn)onSatrtCommand回調(diào)方法瘩绒,那么你必須明確的停止service,因?yàn)楝F(xiàn)在該service被認(rèn)為是started带族。這種情況下锁荔,該service會一直運(yùn)行到自己調(diào)用stopSelf或者其余組件調(diào)用stopService來停止它,而不管它是否與所有的client解綁蝙砌。

除此之外阳堕,如果你的service是started且接受綁定,那么當(dāng)系統(tǒng)調(diào)用onUnbind方法的時候择克,你可選擇返回true恬总,如果你想在下次一個client與這個service綁定的時候接受到一個onRebind的回調(diào)。onRebind返回void肚邢,但是client還是會在onServiceConnected回調(diào)中接收到IBinder壹堰。下面的圖說明了這種生命周期的邏輯:

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拭卿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贱纠,更是在濱河造成了極大的恐慌峻厚,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谆焊,死亡現(xiàn)場離奇詭異惠桃,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)辖试,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門辜王,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人剃执,你說我怎么就攤上這事誓禁。” “怎么了肾档?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵摹恰,是天一觀的道長。 經(jīng)常有香客問我怒见,道長俗慈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任遣耍,我火速辦了婚禮闺阱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘舵变。我一直安慰自己酣溃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布纪隙。 她就那樣靜靜地躺著赊豌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绵咱。 梳的紋絲不亂的頭發(fā)上碘饼,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天,我揣著相機(jī)與錄音悲伶,去河邊找鬼艾恼。 笑死,一個胖子當(dāng)著我的面吹牛麸锉,可吹牛的內(nèi)容都是我干的钠绍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼花沉,長吁一口氣:“原來是場噩夢啊……” “哼柳爽!你這毒婦竟也來了纳寂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤泻拦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后忽媒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體争拐,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年晦雨,在試婚紗的時候發(fā)現(xiàn)自己被綠了架曹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡闹瞧,死狀恐怖绑雄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情奥邮,我是刑警寧澤万牺,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站洽腺,受9級特大地震影響脚粟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蘸朋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一核无、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧藕坯,春花似錦团南、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霹购,卻和暖如春佑惠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背齐疙。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工膜楷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贞奋。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓赌厅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親轿塔。 傳聞我的和親對象是個殘疾皇子特愿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評論 2 355