【後臺任務】綁定的服務概述(13)

概要


綁定服務是客戶端 - 服務器接口中的服務器。它允許組件(如活動)綁定到服務,發送請求,接收響應以及執行進程間通信(IPC)。綁定服務通常只在服務於另一個應用程序組件時才存在,並且不會無限期地在後臺運行。

本文檔介紹瞭如何創建綁定服務,包括如何綁定來自其他應用程序組件的服務。有關一般服務的其他信息,例如如何從服務傳遞通知並將服務設置爲在前臺運行,請參閱 服務文檔。

基礎


綁定服務是Service該類的一個實現,允許其他應用程序綁定並與之交互。要爲服務提供綁定,您必須實現onBind()回調方法。此方法返回一個IBinder對象,該對象定義客戶端可用於與服務交互的編程接口。

綁定到已啓動的服務

正如服務 文檔中所討論的,您可以創建一個既啓動又受約束的服務。也就是說,您可以通過調用來啓動服務startService(),這可以使服務無限期運行,並且還可以允許客戶端通過調用來綁定到服務bindService()。

如果你讓你的服務要啓動和約束,那麼當該服務已啓動,系統並不會在所有客戶端解除綁定摧毀服務。相反,您必須通過調用stopSelf()或顯式地停止服務stopService()。

雖然你通常實現onBind() 或者 onStartCommand(),但有時需要實現兩者。例如,音樂播放器可能會發現允許其服務無限期運行並提供綁定是有用的。這樣,一項活動即可開始播放某些音樂的服務,即使用戶離開應用程序,音樂也會繼續播放。然後,當用戶返回到應用程序時,活動可以綁定到服務以重新獲得播放控制權。

有關向已啓動服務添加綁定時的服務生命週期的更多信息,請參閱管理綁定服務的生命週期。

客戶端通過調用綁定到服務 bindService()。當它這樣做時,它必須提供一個實現ServiceConnection,它監視與服務的連接。返回值 bindService()指示請求的服務是否存在以及客戶端是否被允許訪問它。當Android系統會在客戶端和服務之間的連接,它調用onServiceConnected()的ServiceConnection。該 onServiceConnected()方法包含一個IBinder 參數,然後客戶端使用該參數與綁定服務進行通信。

您可以同時將多個客戶端連接到服務。但是,系統緩存IBinder服務通信信道。換句話說,系統調用服務的onBind()方法,以便IBinder在第一個客戶端綁定時才生成。然後,系統將該內容傳遞IBinder給綁定到相同服務的所有其他客戶端,而無需onBind()再次調用 。

當最後一個客戶端從服務中解除綁定時,系統將銷燬該服務,除非該服務也是由該服務啓動的startService()。

綁定服務實現中最重要的部分是定義onBind()回調方法返回的接口。以下部分討論了可以定義服務IBinder界面的幾種不同方式 。

創建一個綁定的服務


創建提供綁定的服務時,必須提供一個IBinder 提供客戶端可用於與服務交互的編程接口。有三種方法可以定義接口:

擴展Binder類
如果您的服務對您自己的應用程序是私有的,並且與客戶端運行在相同的進程中(這很常見),您應該通過擴展Binder 該類並從中返回實例來 創建接口onBind()。客戶端收到Binder並可以使用它來直接訪問Binder 實現中或公共方法中可用的公共方法Service。
當您的服務僅僅是您自己的應用程序的後臺工作人員時,這是首選技術。您不會以這種方式創建界面的唯一原因是因爲您的服務被其他應用程序或單獨的進程使用。

使用Messenger
如果你需要你的界面在不同的進程中工作,你可以使用a創建一個服務界面Messenger。以這種方式,服務定義了一個Handler響應不同類型的Message對象的響應。這Handler 是一個Messenger可以IBinder 與客戶端共享的基礎,允許客戶端使用Message對象向服務發送命令。另外,客戶端可以Messenger自己定義一個,所以服務可以發回消息。
這是執行進程間通信(IPC)的最簡單方法,因爲Messenger隊列將所有請求放入單個線程中,因此您不必將您的服務設計爲線程安全。

使用AIDL
Android接口定義語言(AIDL)將對象分解成操作系統可以理解的原語,並在進程間編組它們以執行IPC。以前的技術,使用a Messenger,實際上是基於AIDL作爲其基礎結構。如上所述,Messenger在單個線程中創建所有客戶端請求的隊列,以便服務一次接收一個請求。但是,如果您希望您的服務同時處理多個請求,則可以直接使用AIDL。在這種情況下,您的服務必須是線程安全的並且能夠進行多線程。
要直接使用AIDL,您必須創建一個.aidl定義編程接口的文件。Android SDK工具使用此文件生成一個抽象類,該類實現接口並處理IPC,然後可以在您的服務中進行擴展。

注意:大多數應用程序不應該使用AIDL來創建綁定服務,因爲它可能需要多線程功能並可能導致更復雜的實現。因此,AIDL不適用於大多數應用程序,本文檔不討論如何將其用於您的服務。如果您確定需要直接使用AIDL,請參閱AIDL 文檔。

擴展Binder類

如果您的服務僅由本地應用程序使用,並且不需要跨進程工作,那麼您可以實現自己的Binder類,以便客戶端直接訪問服務中的公共方法。

注意:僅當客戶端和服務處於相同的應用程序和進程中時纔有效,這是最常見的。例如,對於需要將活動綁定到在後臺播放音樂的自己的服務的音樂應用程序,這可以很好地工作。

以下是設置方法:

  1. 在您的服務中,創建一個實例Binder,執行以下操作之一:
    • 包含客戶端可以調用的公共方法。
    • 返回當前Service實例,該實例具有客戶端可以調用的公共方法。
    • 通過客戶端可以調用的公共方法返回服務託管的另一個類的實例。
  2. Binder從onBind()回調方法返回這個實例。
  3. 在客戶端,接收Binder來自onServiceConnected()回調方法並使用提供的方法調用綁定服務。

注意:服務和客戶端必須位於同一個應用程序中,以便客戶端可以投射返回的對象並正確調用其API。服務和客戶端也必須處於相同的過程中,因爲此技術不會跨進程執行任何編組。

例如,以下是一個服務,通過Binder實現爲客戶提供對服務中方法的訪問:

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()爲客戶獲取的當前實例方法LocalService。這允許客戶在服務中調用公共方法。例如,客戶可以getRandomNumber()從服務中調用。

這是一個綁定到 單擊按鈕時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();
        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;
        }
    };
}

上面的示例顯示了客戶端如何使用實現ServiceConnection和onServiceConnected()回調綁定到服務 。下一節提供了關於綁定到服務的這個過程的更多信息。

注意:在上面的示例中,該 onStop()方法將客戶端從服務中解除綁定。客戶應該在適當的時候解除服務綁定,如 附加說明中所述。

有關更多示例代碼,請參閱ApiDemos中的類和類。 LocalService.java LocalServiceActivities.java

使用Messenger

與AIDL相比
當你需要執行IPC時,Messenger爲你的接口使用a 比使用AIDL更簡單,因爲Messenger隊列調用了所有的服務。純AIDL接口向服務發送同時請求,然後服務必須處理多線程。

對於大多數應用程序,服務不需要執行多線程,因此使用a Messenger允許服務一次處理一個呼叫。如果您的服務多線程化很重要,請使用AIDL來定義您的界面。

如果您需要您的服務與遠程進程進行通信,則可以使用a Messenger爲您的服務提供接口。這種技術允許您執行進程間通信(IPC)而無需使用AIDL。

以下是如何使用a的總結Messenger:

  1. 該服務實現了一個Handler接收來自客戶端的每個呼叫的回調。
  2. 該服務使用Handler創建一個Messenger 對象(這是對該對象的引用Handler)。
  3. 在Messenger創建IBinder該服務從返回給客戶端onBind()。
  4. 客戶端使用IBinder實例化Messenger (引用服務的Handler)(客戶端用來將Message對象發送 到服務)。
  5. 服務接收的每個Message在其Handler-特別,在handleMessage()方法。

通過這種方式,有沒有方法爲客戶端上的服務電話。相反,客戶端提供服務在其中接收的消息(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();
    }
}

請注意,該handleMessage()方法 Handler是服務收到傳入的地址,Message 並根據該what成員決定要執行的操作。

所有客戶端需要做的是Messenger根據IBinder服務返回的信息創建一個消息並使用消息發送消息send()。例如,以下是綁定到服務並將MSG_SAY_HELLO消息傳遞給服務的簡單活動:

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;
        }
    }
}

注意這個例子並沒有顯示服務如何響應客戶端。如果你想要服務響應,你還需要Messenger在客戶端創建一個。當客戶端收到onServiceConnected()回調時,它會在方法的參數中發送一個Message包含客戶端的服務。MessengerreplyTosend()

您可以看到如何在(服務)和(客戶端)示例中提供雙向消息處理的示例。 MessengerService.java MessengerServiceActivities.java

綁定到服務


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

該綁定是異步的,並bindService()立即返回而不返回IBinder給客戶端。爲了接收IBinder,客戶端必須創建一個實例ServiceConnection並傳遞給它bindService()。在ServiceConnection包括系統調用提供的回調方法IBinder。

注意:只有活動,服務和內容提供商可以綁定到服務 - 您不能綁定到廣播接收方的服務。

要綁定到客戶端的服務,請按照下列步驟操作:
執行ServiceConnection。
你的實現必須覆蓋兩個回調方法:

onServiceConnected()
系統調用它來傳遞IBinder服務onBind()方法返回的結果。
onServiceDisconnected()
Android系統在與服務的連接意外丟失時調用此服務,例如服務崩潰或已被終止。這是不是 在客戶端解除綁定調用。
調用bindService(),通過ServiceConnection實現。
注意:如果該方法返回false,則您的客戶端沒有與該服務的有效連接。但是,您的客戶仍然應該打電話unbindService(); 否則,您的客戶端會在空閒時關閉服務。

當系統調用onServiceConnected()回調方法時,可以使用接口定義的方法開始調用服務。
要斷開與服務的連接,請致電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) {
        // 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;
    }
};

通過這種方式ServiceConnection,客戶端可以通過傳遞給服務來綁定到服務bindService(),如以下示例所示:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • 第一個參數bindService()是 Intent明確命名要綁定的服務。

警告:如果您使用意圖綁定到a Service,請使用明確的 意圖確保您的應用程序是安全的。使用隱式意圖啓動服務會帶來安全隱患,因爲您無法確定哪些服務會響應該意圖,並且用戶無法看到啓動哪項服務。從Android 5.0(API級別21)開始,如果您bindService() 使用隱式意圖調用,則系統會引發異常。

  • 第二個參數是ServiceConnection對象。
  • 第三個參數是一個指示綁定選項的標誌。它通常應該BIND_AUTO_CREATE是爲了創建服務,如果它還沒有活着。其他可能的值是BIND_DEBUG_UNBIND 和BIND_NOT_FOREGROUND,或0爲無。

補充筆記

以下是關於綁定到服務的一些重要提示

  • 您應該始終捕獲DeadObjectException連接斷開時引發的異常。這是遠程方法拋出的唯一異常。
  • 對象是跨進程的引用計數。
  • 您通常在匹配客戶生命週期的啓動和拆卸時間期間對綁定和解除綁定進行配對,如以下示例中所述:

  • 如果您只有在您的活動可見時才需要與服務進行交互,則您應該在期間onStart()綁定和解除綁定onStop()。
  • 如果您希望活動即使在後臺停止時也能收到回覆,那麼您可以在期間onCreate()綁定和解除綁定onDestroy()。當心,這意味着你的活動需要使用該服務,它的運行(甚至在背景中)的整段時間,因此,如果該服務是在另一個進程中,那麼你就增加了過程的重量,它變得更容易,該系統將殺了它。

注意:您不一般綁定,並在您的活動的解除綁定onResume()和onPause(),因爲這些回調發生在每一個生命週期的過渡和你應該保持發生在這些過渡到最低限度的處理。另外,如果應用程序中的多個活動綁定到相同的服務,並且這兩個活動之間存在轉換,則可能會因爲當前活動在下一個綁定(在恢復期間)之前解除綁定(在暫停期間)而被破壞並重新創建。活動 文檔中描述了活動如何協調其生命週期的活動轉換。

有關更多示例代碼,展示如何綁定到服務,請參閱ApiDemos中的類。 RemoteService.java

管理綁定服務的生命週期


當一個服務從所有客戶端解除綁定時,Android系統將銷燬它(除非它也是以它開始的onStartCommand())。因此,如果服務是純粹的綁定服務,則不必管理服務的生命週期 - Android系統會根據它是否綁定到任何客戶端來管理它。

但是,如果選擇實現onStartCommand()回調方法,則必須明確停止服務,因爲現在認爲該服務已啓動。在這種情況下,服務會一直運行,直到服務停止stopSelf()或使用其他組件調用stopService(),而不管它是否綁定到任何客戶端。

此外,如果您的服務已啓動並接受綁定,那麼當系統調用您的onUnbind()方法時,true如果您希望onRebind()下次客戶端綁定到服務時收到呼叫,則可以選擇返回 。onRebind()返回void,但客戶仍然收到IBinder它的 onServiceConnected()回調。下圖說明了這種生命週期的邏輯。

【後臺任務】綁定的服務概述(13)
圖1.啓動並允許綁定的服務的生命週期。

有關已啓動服務的生命週期的更多信息,請參閱服務文檔。

Lastest Update:2018.05.08

聯繫我

QQ:94297366
微信打賞:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公衆號推薦:

【後臺任務】綁定的服務概述(13)

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