Android中bindService的使用及Service生命週期

孫群發表於2015-09-02

Android中有兩種主要方式使用Service,通過呼叫Context的startService方法或呼叫Context的bindService方法,本文只探討純bindService的使用,不涉及任何startService方法呼叫的情況。如果想了解startService相關的使用,請參見《Android中startService的使用及Service生命週期》


bindService啟動服務的特點

相比於用startService啟動的Service,bindService啟動的服務具有如下特點:
1. bindService啟動的服務在呼叫者和服務之間是典型的client-server的介面,即呼叫者是客戶端,service是服務端,service就一個,但是連線繫結到service上面的客戶端client可以是一個或多個。這裡特別要說明的是,這裡所提到的client指的是元件,比如某個Activity。
2. 客戶端client(即呼叫bindService的一方,比如某個Activity)可以通過IBinder介面獲取Service的例項,從而可以實現在client端直接呼叫Service中的方法以實現靈活的互動,並且可藉助IBinder實現跨程式的client-server的互動,這在純startService啟動的Service中是無法實現的。
3. 不同於startService啟動的服務預設無限期執行(可以通過Context的stopService或Service的stopSelf方法停止執行),bindService啟動的服務的生命週期與其繫結的client息息相關。當client銷燬的時候,client會自動與Service解除繫結,當然client也可以通過明確呼叫Context的unbindService方法與Service解除繫結。當沒有任何client與Service繫結的時候,Service會自行銷燬(通過startService啟動的除外)。
4. startService和bindService二者執行的回撥方法不同:startService啟動的服務會涉及Service的的onStartCommand回撥方法,而通過bindService啟動的服務會涉及Service的onBind、onUnbind等回撥方法。


bindService程式碼示例

使用bindService主要分兩種情形:
1. Service的呼叫者client與Service在同一個App中;
2. Service的呼叫者client是App1中的一個Activity,而Service是App2中的Service,client與service分屬兩個App,這種情形下主要用於實現跨程式的通訊。

為了簡單起見,本文只討論第一種情形,即Service的呼叫者client與Service在同一個App中,該情形也是我們在實際開發中用到最多的情形。如果想了解通過bindService在兩個不同的程式中讓客戶端與Service通訊,可參見另一篇博文《Android中通過Messenger與Service實現程式間雙向通訊》

下面我們通過一個例子演示一下第一種情形下bindService的基本使用流程。

首先我們有一個TestService,該類繼承自Service,其是client-server介面中的server端。我們在其主要的生命週期回撥方法中都加入了輸出語句。TestService程式碼如下:

package com.ispring.startservicedemo;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import java.util.Random;

public class TestService extends Service {

    public class MyBinder extends Binder{

        public TestService getService(){
            return TestService.this;
        }

    }

    //通過binder實現呼叫者client與Service之間的通訊
    private MyBinder binder = new MyBinder();

    private final Random generator = new Random();

    @Override
    public void onCreate() {
        Log.i("DemoLog","TestService -> onCreate, Thread: " + Thread.currentThread().getName());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("DemoLog", "TestService -> onStartCommand, startId: " + startId + ", Thread: " + Thread.currentThread().getName());
        return START_NOT_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i("DemoLog", "TestService -> onBind, Thread: " + Thread.currentThread().getName());
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i("DemoLog", "TestService -> onUnbind, from:" + intent.getStringExtra("from"));
        return false;
    }

    @Override
    public void onDestroy() {
        Log.i("DemoLog", "TestService -> onDestroy, Thread: " + Thread.currentThread().getName());
        super.onDestroy();
    }

    //getRandomNumber是Service暴露出去供client呼叫的公共方法
    public int getRandomNumber(){
        return generator.nextInt();
    }
}

在該App中,除了TestService,還有兩個Activity: ActivityA和ActivityB,它們都是Service的呼叫者,即client-server介面中的client。

ActivityA是App的啟動介面,介面如下:
這裡寫圖片描述

ActivityA的程式碼如下:

package com.ispring.startservicedemo;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class ActivityA extends Activity implements Button.OnClickListener {

    private TestService service = null;

    private boolean isBound = false;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            isBound = true;
            TestService.MyBinder myBinder = (TestService.MyBinder)binder;
            service = myBinder.getService();
            Log.i("DemoLog", "ActivityA onServiceConnected");
            int num = service.getRandomNumber();
            Log.i("DemoLog", "ActivityA 中呼叫 TestService的getRandomNumber方法, 結果: " + num);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
            Log.i("DemoLog", "ActivityA onServiceDisconnected");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_a);
        Log.i("DemoLog", "ActivityA -> onCreate, Thread: " + Thread.currentThread().getName());
    }

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.btnBindService){
            //單擊了“bindService”按鈕
            Intent intent = new Intent(this, TestService.class);
            intent.putExtra("from", "ActivityA");
            Log.i("DemoLog", "----------------------------------------------------------------------");
            Log.i("DemoLog", "ActivityA 執行 bindService");
            bindService(intent, conn, BIND_AUTO_CREATE);
        }else if(v.getId() == R.id.btnUnbindService){
            //單擊了“unbindService”按鈕
            if(isBound){
                Log.i("DemoLog", "----------------------------------------------------------------------");
                Log.i("DemoLog", "ActivityA 執行 unbindService");
                unbindService(conn);
            }
        }else if(v.getId() == R.id.btnStartActivityB){
            //單擊了“start ActivityB”按鈕
            Intent intent = new Intent(this, ActivityB.class);
            Log.i("DemoLog", "----------------------------------------------------------------------");
            Log.i("DemoLog", "ActivityA 啟動 ActivityB");
            startActivity(intent);
        }else if(v.getId() == R.id.btnFinish){
            //單擊了“Finish”按鈕
            Log.i("DemoLog", "----------------------------------------------------------------------");
            Log.i("DemoLog", "ActivityA 執行 finish");
            this.finish();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.i("DemoLog", "ActivityA -> onDestroy");
    }
}

通過單擊ActivityA上的“start ActivityB”可以啟動ActivityB,ActivityB的介面如下:
這裡寫圖片描述

ActivityB的程式碼如下:

package com.ispring.startservicedemo;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.Button;


public class ActivityB extends Activity implements Button.OnClickListener {

    private TestService service = null;

    private boolean isBound = false;

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            isBound = true;
            TestService.MyBinder myBinder = (TestService.MyBinder)binder;
            service = myBinder.getService();
            Log.i("DemoLog", "ActivityB onServiceConnected");
            int num = service.getRandomNumber();
            Log.i("DemoLog", "ActivityB 中呼叫 TestService的getRandomNumber方法, 結果: " + num);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isBound = false;
            Log.i("DemoLog", "ActivityB onServiceDisconnected");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
    }

    @Override
    public void onClick(View v) {
        if(v.getId() == R.id.btnBindService){
            Intent intent = new Intent(this, TestService.class);
            intent.putExtra("from", "ActivityB");
            Log.i("DemoLog", "----------------------------------------------------------------------");
            Log.i("DemoLog", "ActivityB 執行 bindService");
            bindService(intent, conn, BIND_AUTO_CREATE);
        }else if(v.getId() == R.id.btnUnbindService){
            if(isBound){
                Log.i("DemoLog", "----------------------------------------------------------------------");
                Log.i("DemoLog", "ActivityB 執行 unbindService");
                unbindService(conn);
            }
        }else if(v.getId() == R.id.btnFinish){
            //單擊了“Finish”按鈕
            Log.i("DemoLog", "----------------------------------------------------------------------");
            Log.i("DemoLog", "ActivityB 執行 finish");
            this.finish();
        }
    }

    @Override
    public void onDestroy(){
        super.onDestroy();
        Log.i("DemoLog", "ActivityB -> onDestroy");
    }
}

我們暫時不點選上面的按鈕,先看一下TestService和ActivityA的程式碼。

呼叫者(客戶端client)要想和Service進行互動,那麼Service和呼叫者必須都要做好準備。

我們先看Service要做的工作。
使用bindService將client與server聯絡在一起的關鍵是binder,在TestService中,我們在其中寫了一個內部類MyBinder,該類有個公共方法getService,通過該方法我們可以獲取包含MyBinder的TestService。如果想要自己的Service支援bindService啟動方式,就必須在Service的onBind中返回一個IBinder型別的例項。在示例中,我們例項化了一個MyBinder的例項binder作為TestService的欄位,並且將其作為onBind的返回值。
我們總結一下如果想讓Service支援bindService呼叫方式,Service需要做以下事情:
1. 在Service的onBind方法中返回IBinder型別的例項。
2. onBind方法返回的IBinder的例項需要能夠返回Service例項本身或者通過binder暴露出Service公共方法。通常情況下,最簡單明瞭的做法就是將binder弄成Service的內部類,然後在binder中加入類似於getService之類的方法返回包含binder的Service,這樣client可以通過該方法得到Service例項。

我們已經知道了Service需要做的事情,我們接下來看一下呼叫者需要做的工作。
在我們的示例中,呼叫者(也就是客戶端client)是ActivityA,我們在其中初始化了一個ServiceConnection型別的例項,需要重寫其onServiceConnected方法以及onServiceDisconnected方法。我們需要將這個ServiceConnection型別的例項作為引數傳給bindService方法,當Service還沒有建立的時候,Android會先建立Service的例項,然後執行Service的onBind方法,得到IBinder型別的例項,將該方法作為引數傳入client端的ServiceConnection的onServiceConnected方法中,onServiceConnected方法的執行表明client端可以獲取到Service的IBinder型別的例項,然後將IBinder轉換為自己實際的Binder型別,然後可以通過其直接獲取Service的例項或者通過Binder直接執行公共方法,這取決於Service中Binder的具體實現。在本例中,在onServiceConnected方法中,呼叫者ActivityA通過binder的getService方法獲取到了與其對應的Service,然後我們就可以直接呼叫Service的公共方法以達到使用Service的目的,這樣client與Service之間就通過IBinder建立了連線,從而進行互動。當client與Service失去連線時會觸發onServiceDisconnected方法。
我們總結一下client端要做的事情:
1. 建立ServiceConnection型別的例項,並重寫其onServiceConnected方法和onServiceDisconnected方法。
2. 當Android執行onServiceConnected回撥方法時,我們可以通過IBinder例項得到Service的例項物件或直接呼叫binder的公共方法,這樣就實現了client與Service的連線。
3. 當Android執行onServiceDisconnected回撥方法時,表示client與Service之間斷開了連線,我們在此處要寫一些斷開連線後需要做的處理。

在知道了如何讓client與Service進行互動之後,我們執行我們的App,觀察各個回撥方法的執行過程,我們有三個測試流程。


測試流程A

該測試涉及到ActivityA,但不涉及ActivityB.
首先我們點選ActivityA中的“bindService”按鈕,然後點選”unbindService”按鈕,輸出結果如下所示:
這裡寫圖片描述

首先,通過上面的程式碼我們可以看到Service中執行的回撥方法都是執行在主執行緒中的。
當我們呼叫bindService方法時,我們需要將Intent、ServiceConnection等例項傳入,Intent包含了我們要繫結的Service,ServiceConnection我們在上面提到過,實現了其onServiceConnected方法和onServiceDisconnected方法。 在呼叫了bindService之後,由於Service此時還不存在,那麼Android就會首先建立一個TestService的例項,並執行其onCreate回撥方法,onCreate方法在其生命週期中只會被呼叫一次。然後會呼叫Service的onBind方法,該方法只有在第一次bindService呼叫後才會執行,onBind執行後會返回一個IBinder型別的例項,此時Android會將該IBinder例項存起來,這個IBinder例項是對所有client共享的。當下次其他的client執行bindService的時候,不會再執行onBind方法,因為我們之前已經得到了一個IBinder例項,Android會直接使用這個IBinder例項。 在得到了IBinder例項之後,Android會執行client端ServiceConnection中的onServiceConnected方法,在該方法中我們會得到IBinder例項,並通過該IBinder例項得到了TestService例項,這樣我們的客戶端ActivityA就通過IBinder與TestService建立了連線,我們就可以呼叫TestService的公共方法,比如呼叫其getRandomNumber方法獲得隨機數。

總結一下呼叫bindService之後發生的事情:
client 執行 bindService ->
如果Service不存在,Service 執行 onCreate ->
如果沒有執行過onBind,Service 執行 onBind ->
client的例項ServiceConnection 執行 onServiceConnected

在執行了bindService之後,一共有一個client連線到了TestService,即ActivityA,每次client在呼叫了unbindService方法之後,該client會與Service解除繫結,在與某個client解除繫結之後,Service會檢測是否還有其他的client與其連線繫結,如果沒有其他任何client與其處於連線狀態,那麼Service會執行onUnbind方法,然後執行onDestroy方法,最終銷燬自己。當ActivityA執行unbindService的時候,唯一的一個client與TestService解除了繫結的關係,TestService就執行了onUnbind方法,進而執行onDestroy方法。

總結一下呼叫unbindService之後發生的事情:
client 執行 unbindService ->
client 與 Service 解除繫結連線狀態 ->
Service 檢測是否還有其他client與其連線,如果沒有 ->
Service 執行onUnbind ->
Service 執行onDestroy


測試流程B

我們在測試完第一種流程後,關掉App,重啟App,進行第二種測試流程。
該測試也只涉及ActivityA,不涉及ActivityB。首先先點選ActivityA中的“bindService”按鈕,然後點選”Finish”按鈕,輸出結果如下圖所示:
這裡寫圖片描述

在該測試中,我們首先通過點選”bindService”按鈕,使得ActivityA繫結了TestService,但是我們沒有呼叫unbindService,而是直接通過呼叫“Finish”按鈕讓ActivityA直接銷燬,通過上面的輸出結果我們可以看到,在ActivityA銷燬的時候,執行了ActivityA的onDestroy回撥方法,之後TestService依次執行了onUnbind、onDestroy回撥方法,TestService銷燬。client與Service通過bindService連線起來之後,如果client銷燬,那麼client會自動與Service解除繫結,相當於在destroy之前會執行unbindService,在ActivityA銷燬之後,ActivityA與Service解除了繫結,此時再沒有client與Service處於連線繫結狀態,這樣Service就會執行onUnbind回撥方法,表示沒有client和我玩了,最後執行onDestroy回撥方法。


測試流程C

我們在之前的兩次測試流程中都只涉及ActivtityA,本測試流程會同時涉及ActivityA以及ActivityB。
首先關掉App,重啟App,按照以下步驟測試:
1. 點選ActivityA中的”bindService”按鈕
2. 點選ActivityA中的”start ActivityB”按鈕,介面切換到ActivityB
3. 點選ActivityB中的”bindService”按鈕
4. 點選ActivityB中的”unbindService”按鈕
5. 點選ActivityB中的”Finish”按鈕
6. 點選ActivityA中的”unbindService”按鈕

LogCat輸出結果如下:
這裡寫圖片描述

下面我們依次分析每一步產生的影響,以便於完整地理解通過bindService啟動的Service的生命週期:

  1. 點選ActivityA中的”bindService”按鈕
    由於初始情況下TestService例項不存在,也就是TestService沒有執行。第一次呼叫bindService會例項化TestService,然後會執行其onBind方法,得到IBinder型別的例項,然後將其作為引數傳入ActivityA的ServiceConnection的onServiceConnected方法中,標誌著ActivityA與TestService建立了繫結連線,此時只有ActivityA這一個客戶端client與TestService繫結。

  2. 點選ActivityA中的”start ActivityB”按鈕,介面切換到ActivityB

  3. 點選ActivityB中的”bindService”按鈕
    由於TestService已經處於執行狀態,所以ActivityB呼叫bindService時,不會重新建立TestService的例項,所以也不會執行TestService的onCreate回撥方法,由於在ActivityA執行bindService的時候就已經執行了TestService的onBind回撥方法而獲取IBinder例項,並且該IBinder例項在所有的client之間是共享的,所以當ActivityB執行bindService的時候,不會執行其onBind回撥方法,而是直接獲取上次已經獲取到的IBinder例項。並將其作為引數傳入ActivityB的ServiceConnection的onServiceConnected方法中,標誌著ActivityB與TestService建立了繫結連線,此時有兩個客戶單client(ActivityA和ActivityB)與TestService繫結。

  4. 點選ActivityB中的”unbindService”按鈕
    ActivityB執行了unbindService之後,ActivityB就與TestService解除了繫結。當沒有任何client與Service處於繫結連線狀態的時候,TestService才會執行onUnbind方法、onDestroy方法。但是由於此時還有ActivityA這個client與TestService處於繫結連線中,所以不會執行Service的onBind及onDestroy回撥方法。

  5. 點選ActivityB中的”Finish”按鈕
    執行了ActivityB的finish方法後,ActivityB銷燬了,介面返回到ActivityA

  6. 點選ActivityA中的”unbindService”按鈕
    ActivityA執行unbindService之後,ActivityA與TestService就解除繫結了,這樣就沒有客戶端client與TestService相連,這時候Android會銷燬TestService,在銷燬前會先執行TestService的onUnbind方法,然後才會執行其onDestroy方法,這樣TestService就銷燬了。


bindService生命週期流程圖

這裡特別要說明的是,本文所提到的client指的是元件Component,比如某個Activity。如果在某一個Activity中,多次呼叫bindService方法連線Service,那麼對於Service來說,這個Activity也只是一個client,而不是多個client。

最後我們將bindService啟動的Service的生命週期總結為如下的流程圖:

這裡寫圖片描述

希望本文對大家瞭解bindService的使用有所幫助。

相關文章