Android四大組件之綁定Service詳解

前言

如果沒有看過上一篇博客的同學,建議去看上一篇博客《Android四大組件之Service詳解

簡介

綁定服務是客戶端-服務器接口中的服務器。綁定服務可讓組件(例如 Activity)綁定到服務、發送請求、接收響應,甚至執行進程間通信 (IPC)。 綁定服務通常只在爲其他應用組件服務時處於活動狀態,不會無限期在後臺運行。

本文向您介紹如何創建綁定服務,包括如何綁定到來自其他應用組件的服務。如果沒有看過上一篇博客的朋友建議先去看上一篇的《Android四大組件之Service詳解》 瞭解有關一般服務的更多信息,例如:如何利用服務傳送通知、如何將服務設置爲在前臺運行等等。

綁定服務是 Service 類的實現,可讓其他應用與其綁定和交互。要提供服務綁定,您必須實現 onBind() 回調方法。該方法返回的 IBinder 對象定義了客戶端用來與服務進行交互的編程接口。

綁定到已啓動服務

正如上篇中所述,您可以創建同時具有已啓動和綁定兩種狀態的服務。 也就是說,可通過調用 startService() 啓動該服務,讓服務無限期運行;此外,還可通過調用 bindService() 使客戶端綁定到服務。

如果您確實允許服務同時具有已啓動和綁定狀態,則服務啓動後,系統“不會”在所有客戶端都取消綁定時銷燬服務。 爲此,您必須通過調用 stopSelf() 或 stopService() 顯式停止服務。

儘管您通常應該實現 onBind() 或 onStartCommand(),但有時需要同時實現這兩者。例如,音樂播放器可能發現讓其服務無限期運行並同時提供綁定很有用處。 這樣一來,Activity 便可啓動服務進行音樂播放,即使用戶離開應用,音樂播放也不會停止。 然後,當用戶返回應用時,Activity 可綁定到服務,重新獲得回放控制權。

請務必閱上篇中讀管理綁定服務的生命週期部分,詳細瞭解有關爲已啓動服務添加綁定時該服務的生命週期信息。

客戶端可通過調用 bindService() 綁定到服務。調用時,它必須提供 ServiceConnection 的實現,後者會監控與服務的連接。bindService() 方法會立即無值返回,但當 Android 系統創建客戶端與服務之間的連接時,會對 ServiceConnection 調用 onServiceConnected(),向客戶端傳遞用來與服務通信的 IBinder。

多個客戶端可同時連接到一個服務。不過,只有在第一個客戶端綁定時,系統纔會調用服務的 onBind() 方法來檢索 IBinder。系統隨後無需再次調用 onBind(),便可將同一 IBinder 傳遞至任何其他綁定的客戶端。

當最後一個客戶端取消與服務的綁定時,系統會將服務銷燬(除非 startService() 也啓動了該服務)。

當您實現綁定服務時,最重要的環節是定義您的 onBind() 回調方法返回的接口。您可以通過幾種不同的方法定義服務的 IBinder 接口,下文對這些方法逐一做了闡述。

創建綁定服務

創建提供綁定的服務時,您必須提供 IBinder,用以提供客戶端用來與服務進行交互的編程接口。 您可以通過三種方法定義接口:

  • 擴展 Binder 類
    如果服務是供您的自有應用專用,並且在與客戶端相同的進程中運行(常見情況),則應通過擴展 Binder 類並從 onBind() 返回它的一個實例來創建接口。客戶端收到 Binder 後,可利用它直接訪問 Binder 實現中乃至 Service 中可用的公共方法。

    如果服務只是您的自有應用的後臺工作線程,則優先採用這種方法。 不以這種方式創建接口的唯一原因是,您的服務被其他應用或不同的進程佔用。

  • 使用 Messenger
    如需讓接口跨不同的進程工作,則可使用 Messenger 爲服務創建接口。服務可以這種方式定義對應於不同類型 Message 對象的 Handler。此 Handler 是 Messenger 的基礎,後者隨後可與客戶端分享一個 IBinder,從而讓客戶端能利用 Message 對象向服務發送命令。此外,客戶端還可定義自有 Messenger,以便服務回傳消息。

    這是執行進程間通信 (IPC) 的最簡單方法,因爲 Messenger 會在單一線程中創建包含所有請求的隊列,這樣您就不必對服務進行線程安全設計。

  • 使用 AIDL
    AIDL(Android 接口定義語言)執行所有將對象分解成原語的工作,操作系統可以識別這些原語並將它們編組到各進程中,以執行 IPC。 之前採用 Messenger 的方法實際上是以 AIDL 作爲其底層結構。 如上所述,Messenger 會在單一線程中創建包含所有客戶端請求的隊列,以便服務一次接收一個請求。 不過,如果您想讓服務同時處理多個請求,則可直接使用 AIDL。 在此情況下,您的服務必須具備多線程處理能力,並採用線程安全式設計。

    如需直接使用 AIDL,您必須創建一個定義編程接口的 .aidl 文件。Android SDK 工具利用該文件生成一個實現接口並處理 IPC 的抽象類,您隨後可在服務內對其進行擴展。

注:大多數應用“都不會”使用 AIDL 來創建綁定服務,因爲它可能要求具備多線程處理能力,並可能導致實現的複雜性增加。因此,AIDL 並不適合大多數應用,本文也不會闡述如何將其用於您的服務。如果您確定自己需要直接使用 AIDL,請參閱 AIDL 文檔。

擴展 Binder 類

如果您的服務僅供本地應用使用,不需要跨進程工作,則可以實現自有 Binder 類,讓您的客戶端通過該類直接訪問服務中的公共方法。

注:此方法只有在客戶端和服務位於同一應用和進程內這一最常見的情況下方纔有效。 例如,對於需要將 Activity 綁定到在後臺播放音樂的自有服務的音樂應用,此方法非常有效。

以下是具體的設置方法:

  1. 在您的服務中,創建一個可滿足下列任一要求的 Binder 實例:

    • 包含客戶端可調用的公共方法
    • 返回當前 Service 實例,其中包含客戶端可調用的公共方法
    • 或返回由服務承載的其他類的實例,其中包含客戶端可調用的公共方法
  2. 從 onBind() 回調方法返回此 Binder 實例。

  3. 在客戶端中,從 onServiceConnected() 回調方法接收 Binder,並使用提供的方法調用綁定服務。

注:之所以要求服務和客戶端必須在同一應用內,是爲了便於客戶端轉換返回的對象和正確調用其 API。服務和客戶端還必須在同一進程內,因爲此方法不執行任何跨進程編組。

例如,以下這個服務可讓客戶端通過 Binder 實現訪問服務中的方法:

public class LocalService extends Service {
    // Binder給客戶
    private final IBinder mBinder = new LocalBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    /**
     * 客戶的方法
     */
    public int getRandomNumber() {
        return new Random().nextInt(100);
    }

    /**
     * 用於客戶端Binder的類。
     * 因爲我們總是知道這項服務在與客戶相同的流程中運行,我們不需要處理IPC。
     */
    public class LocalBinder extends Binder {
        LocalService getService() {
            //返回此LocalService實例,以便客戶端可以調用公共方法
            return LocalService.this;
        }
    }
}

LocalBinder 爲客戶端提供 getService() 方法,以檢索 LocalService 的當前實例。這樣,客戶端便可調用服務中的公共方法。 例如,客戶端可調用服務中的 getRandomNumber()。

點擊按鈕時,以下這個 Activity 會綁定到 LocalService 並調用 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;
        }
    };
}

注:在上例中,onStop() 方法將客戶端與服務取消綁定。 客戶端應在適當時機與服務取消綁定,如附加說明中所述。

使用 Messenger

與 AIDL 比較

當您需要執行 IPC 時,爲您的接口使用 Messenger 要比使用 AIDL 實現它更加簡單,因爲 Messenger 會將所有服務調用排入隊列,而純粹的 AIDL 接口會同時向服務發送多個請求,服務隨後必須應對多線程處理。

對於大多數應用,服務不需要執行多線程處理,因此使用 Messenger 可讓服務一次處理一個調用。如果您的服務必須執行多線程處理,則應使用 AIDL 來定義接口。

如需讓服務與遠程進程通信,則可使用 Messenger 爲您的服務提供接口。利用此方法,您無需使用 AIDL 便可執行進程間通信 (IPC)。

以下是 Messenger 的使用方法摘要:

  • 服務實現一個 Handler,由其接收來自客戶端的每個調用的回調
  • Handler 用於創建 Messenger 對象(對 Handler 的引用)
  • Messenger 創建一個 IBinder,服務通過 onBind() 使其返回客戶端
  • 客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化,然後使用後者將 Message 對象發送給服務
  • 服務在其 Handler 中(具體地講,是在 handleMessage() 方法中)接收每個 Message。

這樣,客戶端並沒有調用服務的“方法”。而客戶端傳遞的“消息”(Message 對象)是服務在其 Handler 中接收的。

以下是一個使用 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();
    }
}

注意,服務就是在 Handler 的 handleMessage() 方法中接收傳入的 Message,並根據 what 成員決定下一步操作。

客戶端只需根據服務返回的 IBinder 創建一個 Messenger,然後利用 send() 發送一條消息。例如,以下就是一個綁定到服務並向服務傳遞 MSG_SAY_HELLO 消息的簡單 Activity:

public class MessengerActivity extends AppCompatActivity {
    /**
     * Messenger用於與服務通信。
     */
    Messenger mService = null;
    /**
     * 指示我們是否已在服務上調用綁定的標誌。
     */
    boolean mBound;
    /**
     * 用於與服務主界面交互的類。
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            //在與服務建立連接時調用此方法,
            // 爲我們提供可用於與服務交互的對象。
            // 我們正在使用Messenger與服務進行通信,
            // 因此我們在此處從原始IBinder對象獲取客戶端表示。
            mService = new Messenger(service);
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName className) {
            //當與服務的連接意外斷開時調用此方法 - 即,其進程崩潰。
            mService = null;
            mBound = false;
        }
    };

    public void sayHello(View v) {
        if (!mBound) return;
        // 使用支持的“what”值創建並向服務發送消息
        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.activity_messenger);
    }

    @Override
    protected void onStart() {
        super.onStart();
        //綁定到服務
        bindService(new Intent(this, MessengerService.class), mConnection,
                Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 從服務中取消綁定
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
}

請注意,此示例並未說明服務如何對客戶端作出響應。如果您想讓服務作出響應,則還需要在客戶端中創建一個 Messenger。然後,當客戶端收到 onServiceConnected() 回調時,會向服務發送一條 Message,並在其 send() 方法的 replyTo 參數中包含客戶端的 Messenger。

綁定到服務

應用組件(客戶端)可通過調用 bindService() 綁定到服務。Android 系統隨後調用服務的 onBind() 方法,該方法返回用於與服務交互的 IBinder。

綁定是異步的。bindService() 會立即返回,“不會”使 IBinder 返回客戶端。要接收 IBinder,客戶端必須創建一個 ServiceConnection 實例,並將其傳遞給 bindService()。ServiceConnection 包括一個回調方法,系統通過調用它來傳遞 IBinder。

注:只有 Activity、服務和內容提供程序可以綁定到服務 — 您無法從廣播接收器綁定到服務。

因此,要想從您的客戶端綁定到服務,您必須:

  1. 實現 ServiceConnection。

    您的實現必須重寫兩個回調方法:

    onServiceConnected()

    系統會調用該方法以傳遞服務的 onBind() 方法返回的 IBinder。

    onServiceDisconnected()

    Android 系統會在與服務的連接意外中斷時(例如當服務崩潰或被終止時)調用該方法。當客戶端取消綁定時,系統“不會”調用該方法。

  2. 調用 bindService(),傳遞 ServiceConnection 實現。

  3. 當系統調用您的 onServiceConnected() 回調方法時,您可以使用接口定義的方法開始調用服務。

  4. 要斷開與服務的連接,請調用 unbindService()。

    如果應用在客戶端仍綁定到服務時銷燬客戶端,則銷燬會導致客戶端取消綁定。 更好的做法是在客戶端與服務交互完成後立即取消綁定客戶端。 這樣可以關閉空閒服務。如需瞭解有關綁定和取消綁定的適當時機的詳細信息,請參閱附加說明。

例如,以下代碼段通過擴展 Binder 類將客戶端與上面創建的服務相連,因此它只需將返回的 IBinder 轉換爲 LocalService 類並請求 LocalService 實例:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // 因爲我們已經綁定了在我們自己的進程中運行的顯式服務,所以我們可以將其IBinder轉換爲具體類並直接訪問它。
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    // 與服務的連接意外斷開連接時調用
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};

客戶端可通過將此 ServiceConnection 傳遞至 bindService() 綁定到服務。例如:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • bindService() 的第一個參數是一個 Intent,用於顯式命名要綁定的服務(但 Intent 可能是隱式的)
  • 第二個參數是 ServiceConnection 對象
  • 第三個參數是一個指示綁定選項的標誌。它通常應該是 BIND_AUTO_CREATE,以便創建尚未激活的服務。其他可能的值爲 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示無)。

附加說明

以下是一些有關綁定到服務的重要說明:

  • 您應該始終捕獲 DeadObjectException 異常,它們是在連接中斷時引發的。這是遠程方法引發的唯一異常。
  • 對象是跨進程計數的引用。
  • 您通常應該在客戶端生命週期的匹配引入 (bring-up) 和退出 (tear-down) 時刻期間配對綁定和取消綁定。 例如:
    • 如果您只需要在 Activity 可見時與服務交互,則應在 onStart() 期間綁定,在 onStop() 期間取消綁定。
    • 如果您希望 Activity 在後臺停止運行狀態下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy() 期間取消綁定。請注意,這意味着您的 Activity 在其整個運行過程中(甚至包括後臺運行期間)都需要使用服務,因此如果服務位於其他進程內,那麼當您提高該進程的權重時,系統終止該進程的可能性會增加。

注:通常情況下,切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定,因爲每一次生命週期轉換都會發生這些回調,您應該使發生在這些轉換期間的處理保持在最低水平。此外,如果您的應用內的多個 Activity 綁定到同一服務,並且其中兩個 Activity 之間發生了轉換,則如果當前 Activity 在下一個 Activity 綁定(恢復期間)之前取消綁定(暫停期間),系統可能會銷燬服務並重建服務。 (Activity文檔中介紹了這種有關 Activity 如何協調其生命週期的 Activity 轉換。)

管理綁定服務的生命週期

當服務與所有客戶端之間的綁定全部取消時,Android 系統便會銷燬服務(除非還使用 onStartCommand() 啓動了該服務)。因此,如果您的服務是純粹的綁定服務,則無需對其生命週期進行管理 — Android 系統會根據它是否綁定到任何客戶端代您管理。

不過,如果您選擇實現 onStartCommand() 回調方法,則您必須顯式停止服務,因爲系統現在已將服務視爲已啓動。在此情況下,服務將一直運行到其通過 stopSelf() 自行停止,或其他組件調用 stopService() 爲止,無論其是否綁定到任何客戶端。

此外,如果您的服務已啓動並接受綁定,則當系統調用您的 onUnbind() 方法時,如果您想在客戶端下一次綁定到服務時接收 onRebind() 調用,則可選擇返回 true。onRebind() 返回空值,但客戶端仍在其 onServiceConnected() 回調中接收 IBinder。下文圖 1 說明了這種生命週期的邏輯。

圖 1. 允許綁定的已啓動服務的生命週期。

友情鏈接

Android四大組件之Service詳解
Android四大組件之綁定Service詳解
Android四大組件Service之AIDL詳解

示例代碼:

本文檔示例代碼:(飛機票

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章