1. 程式人生 > >Android 官方文件

Android 官方文件

Bound Services

bound服務是客戶端-伺服器模式的服務。bound服務允許元件(比如activity)對其進行繫結、傳送請求、接收響應、甚至進行程序間通訊(IPC)。 bound服務一般只在為其它應用程式元件服務期間才是存活的,而不會一直在後臺保持執行。

本文展示瞭如何建立一個bound服務,包括如何從其它應用程式元件繫結到該服務。不過,通常你還應該參考服務文件以獲取關於服務的更多資訊,比如如何從服務中傳送通知、如何將服務設定為前臺執行等等。

簡介

bound服務是 Service 類的一種實現,它允許其它應用程式與其繫結並互動。為了讓服務支援繫結,你必須實現 onBind() 回撥方法。這個方法返回一個 IBinder 物件,此物件定義了客戶端與服務進行互動時所需的程式設計介面。

繫結到一個started服務
正如服務一文中所述,你可以建立一個同時支援started和bound的服務。也就是說,服務可以通過呼叫 startService() 來啟動,這使它一直保持執行,同時它也允許客戶端通過呼叫 bindService() 來與之繫結。

如果你的服務確實可以是started和bound的,那麼服務啟動後,系統將不會在所有客戶端解除繫結時銷燬它。取而代之的是,你必須通過呼叫 stopSelf() 或 stopService() 顯式終止此服務。

雖然你通常應該要實現 onBind() 或 onStartCommand() 中的一個,但有時需要同時實現兩者。比如,音樂播放器的服務也許就需要同時實現後臺執行和支援繫結。這樣,activity就可以啟動服務來播放音樂,並且音樂會一直播放下去,即使使用者離開該應用程式也沒關係,這個activity可以繫結播放服務來重新獲得播放控制權。

請確保已經閱讀了#管理Bound服務的生命週期章節,以獲取更多向started服務新增繫結時的服務生命週期的有關資訊。

客戶端可以通過呼叫 bindService() 方法來繫結服務。在呼叫時,必須提供一個 ServiceConnection 的實現程式碼,用於監控與服務的聯接。 bindService() 將會立即返回,沒有返回值。但是Android系統在建立客戶端與服務之間的聯接時,會呼叫 ServiceConnection 中的 onServiceConnected() 方法,傳遞一個 IBinder ,客戶端將用它與服務進行通訊。

多個客戶端可以同時聯接到一個服務上。不過,只有在第一個客戶端繫結時,系統才會呼叫服務的 onBind() 方法來獲取 IBinder 。然後,系統會向後續請求繫結的客戶端傳送這同一個 IBinder ,而不再呼叫 onBind() 。

當最後一個客戶端解除繫結後,系統會銷燬服務(除非服務同時是通過 startService() 啟動的)。

當你實現自己的bound服務時,最重要的工作就是定義 onBind() 回撥方法所返回的介面。定義服務 IBinder 介面的方式有好幾種,後續章節將會對每種技術進行論述。

2 . 建立一個Bound服務

建立一個支援繫結的服務時,你必須提供一個 IBinder ,用作客戶端和服務間進行通訊的程式設計介面。定義這類介面的方式有三種:

  • 擴充套件Binder類
    如果服務是你的應用程式所私有的,並且與客戶端運行於同一個程序中(通常都是如此),你應該通過擴充套件Binder類來建立你的介面,並從onBind()返回一個它的例項。客戶端接收該Binder物件並用它來直接訪問Binder甚至Service中可用的公共(public)方法。
    如果你的服務只是為你自己的應用程式執行一些後臺工作,那這就是首選的技術方案。不用這種方式來建立介面的理由只有一個,就是服務要被其它應用程式使用或者要跨多個程序使用。
  • 使用Messenger
    如果你需要介面跨越多個程序進行工作,可以通過Messenger來為服務建立介面。在這種方式下,服務定義一個響應各類訊息物件Message的Handler。此Handler是Messenger與客戶端共享同一個IBinder的基礎,它使得客戶端可以用訊息物件Message向服務傳送指令。此外,客戶端還可以定義自己的Message,以便服務能夠往回傳送訊息。
    這是執行程序間通訊(IPC)最為簡便的方式,因為Messenger會把所有的請求放入一個獨立程序中的佇列,這樣你就不一定非要把服務設計為執行緒安全的模式了。
  • 使用AIDL
    Android介面定義語言AIDL(Android Interface Definition Language)完成以下的所有工作:將物件解析為作業系統可識別的原始形態,並將它們跨程序序列化(marshal)以完成IPC。前一個使用Messenger的方式,實際上也是基於AIDL的,它用AIDL作為底層結構。如上所述,Messenger將在一個單獨的程序中建立一個包含了所有客戶端請求的佇列,這樣服務每次就只會收到一個請求。可是,如果想讓你的服務能同時處理多個請求,那你就可以直接使用AIDL。這種情況下,你的服務必須擁有多執行緒處理能力,並且是以執行緒安全的方式編寫的。
    要直接使用AIDL,你必須建立一個.aidl檔案,其中定義了程式設計的介面。Android SDK 工具使用此檔案來生成一個抽象類(abstract class),其中實現了介面及對IPC的處理,然後你就可以在自己的服務中擴充套件該類。
    注意: 絕大多數應用程式都不應該用AIDL來建立bound服務,因為這可能需要多執行緒處理能力並且會讓程式碼變得更為複雜。 因此,AIDL對絕大多數應用程式都不適用,並且本文也不會討論如何在服務中使用它的內容。如果你確信需要直接使用AIDL,那請參閱 AIDL 文件。

2.1 擴充套件Binder類

如果你的服務只用於本地應用程式並且不需要跨程序工作,那你只要實現自己的 Binder 類即可,這樣你的客戶端就能直接訪問服務中的公共方法了。

注意:僅當客戶端和服務位於同一個應用程式和程序中,這也是最常見的情況,這種方式才會有用。比如,一個音樂應用需要把一個activity繫結到它自己的後臺音樂播放服務上,採用這種方式就會很不錯。

以下是設定步驟:

  • 1.在你的服務中,建立一個Binder的例項,其中實現以下三者之一:
    • 包含了可供客戶端呼叫的公共方法
    • 返回當前Service例項,其中包含了可供客戶端呼叫的公共方法。
    • 或者,返回內含服務類的其它類的一個例項,服務中包含了可供客戶端呼叫的公共方法。
  • 2.從回撥方法onBind()中返回Binder的該例項。
  • 3.在客戶端中,在回撥方法onServiceConnected()中接收Binder並用所提供的方法對繫結的服務進行呼叫。

    注意:
    服務和客戶端之所以必須位於同一個應用程式中,是為了讓客戶端能夠正確轉換(cast)返回的物件並呼叫物件的API。 服務和客戶端也必須位於同一個程序中,因為這種方式不能執行任何跨程序的序列化(marshalling)操作。

比如,以下是一個服務的示例,它通過實現一個Binder來為客戶端訪問它內部的方法提供支援:
public class LocalService extends Service {
    // 給客戶端的Binder
    private final IBinder mBinder = new LocalBinder();
    // 生成隨機數
    private final Random mGenerator = new Random();

    /**
     * 用於客戶端Binder的類。
     * 因為知道本服務總是運行於與客戶端相同的程序中,我們就不需要用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的activity,當點選按鈕時,它會呼叫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();
        // 繫結到LocalService
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 與服務解除繫結
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /** 當按下按鈕時呼叫(該按鈕在layout檔案中利用android:onClick屬性與本方法關聯 */
    public void onButtonClick(View v) {
        if (mBound) {
            // 呼叫LocalService中的方法。
            // 不過,如果該呼叫會導致某些操作的掛起,那麼呼叫應該放入單獨的執行緒中進行,
            // 以免降低activity的效能。
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /** 定義服務繫結時的回撥方法,用於傳給bindService() */
    private ServiceConnection mConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // 我們已經繫結到LocalService了,對IBinder進行型別轉換(cast)並獲得LocalService物件的例項
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }
    };
}
上述例子展示了客戶端如何利用 ServiceConnection 和 onServiceConnected() 回撥方法繫結到服務。下一節將給出更多有關服務繫結過程的資訊。

注意:
上述例子並沒有明確地解除繫結,但所有的客戶端都應該適時地解除繫結(比如activity暫停pause時)。

更多示例程式碼,請參閱ApiDemos 中的LocalService.java類和 LocalServiceActivities.java 類。

2.2 使用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中的每個訊息Message——確切的說,是在handleMessage()方法中接收。

通過這種方式,客戶端不需要呼叫服務中的“方法”。取而代之的是,客戶端傳送“訊息”( Message物件),服務則接收位於 Handler中的這個訊息。

以下是服務使用一個Messenger做為介面的簡單例子:

public class MessengerService extends Service {
    /** 傳送給服務的用於顯示資訊的指令*/
    static final int MSG_SAY_HELLO = 1;

    /**
     * 從客戶端接收訊息的Handler
     */
    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);
            }
        }
    }

    /**
     * 向客戶端公佈的用於向IncomingHandler傳送資訊的Messager
     */
    final Messenger mMessenger = new Messenger(new IncomingHandler());

    /**
     * 當繫結到服務時,我們向Messager返回介面,
     * 用於向服務傳送訊息
     */
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        return mMessenger.getBinder();
    }
}

請注意Handler中的 handleMessage() 方法,這裡是服務接收輸入訊息Message 的地方,也是根據what數字來決定要執行什麼操作的地方。

客戶端要做的全部工作就是根據服務返回的IBinder建立一個 Messenger ,並用send() 方法傳送一個訊息。例如,以下是一個activity示例,它繫結到上述服務,並向服務傳送 MSG_SAY_HELLO訊息:

public class ActivityMessenger extends Activity {
    /** 用於和服務通訊的Messenger*/
    Messenger mService = null;

    /** 標識我們是否已繫結服務的標誌 */
    boolean mBound;

    /**
     * 與服務的主介面進行互動的類
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // 與服務建立聯接後將會呼叫本方法,
            // 給出用於和服務互動的物件。
            // 我們將用一個Messenger來與服務進行通訊,
            // 因此這裡我們獲取到一個原始IBinder物件的客戶端例項。
            mService = new Messenger(service);
            mBound = true;
        }

        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.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 給服務,訊息的send() 方法中的replyTo 引數裡包含了客戶端的Messenger。

在MessengerService.java (服務)和 MessengerServiceActivities.java (客戶端)例程中,你可以看到如何雙向傳送訊息的例子。

2.3 繫結一個服務

應用程式元件(客戶端)可以通過呼叫 bindService() 來繫結服務。然後Android系統會呼叫服務的 onBind() 方法,返回一個用於和服務進行互動的 IBinder。

繫結是非同步進行的。 bindService() 將立即返回,並不會向客戶端返回 IBinder 。為了接收 IBinder ,客戶端必須建立一個 ServiceConnection 的例項,並把它傳給 bindService()。 ServiceConnection 包含了一個回撥方法,系統將會呼叫該方法來傳遞客戶端所需的那個 IBinder。

注意:
只有activity、服務和content provider才可以繫結到服務上——你不能從廣播接收器(broadcast receiver)中繫結服務。

因此,要把客戶端繫結到服務上,你必須:

  • 實現ServiceConnection。
    • 你的實現程式碼必須重寫兩個回撥方法:
      onServiceConnected()
      系統呼叫該方法來傳遞服務的onBind()方法所返回的IBinder。
      onServiceDisconnected()
      當與服務的聯接發生意外中斷時,比如服務崩潰或者被殺死時,Android系統將會呼叫該方法。客戶端解除繫結時,不會呼叫該方法。
  • 呼叫bindService(),傳入已實現的ServiceConnection。
  • 當系統呼叫你的onServiceConnected()回撥方法時,你可以利用介面中定義的方法開始對服務的呼叫。
  • 要斷開與服務的聯接,請呼叫unbindService()。

當客戶端被銷燬時,與服務的繫結也將解除。但與服務互動完畢後,或者你的activity進入pause狀態時,你都應該確保解除繫結,以便服務能夠在用完後及時關閉。(繫結和解除繫結的合適時機將在後續章節中繼續討論。)

· 例如,以下程式碼段將客戶端與前面#擴充套件Binder類建立的服務聯接,而要做的全部工作就是把返回的 IBinder 轉換(cast)為LocalService類,並獲取LocalService的例項:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // 與服務的聯接建立之後將會呼叫
    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異常,當聯接中斷時會丟擲該異常。這是遠端方法唯一會丟擲的異常。
  • 物件的引用計數是跨程序的。
  • 你通常應該成對地進行繫結和解除繫結,並與客戶端生命週期的啟動和結束過程相呼應。比如:
    • 如果僅當你的activity可見時才需要與服務進行互動,則你應該在onStart()中進行繫結,並在onStop()中解除繫結。
    • 如果你的activity需要在stopped後並進入後臺期間仍然能接收響應,則你可以在onCreate()中進行繫結,並在[1]中解除繫結。請注意這表明你的activity在整個執行期間都需要使用服務(即使在後臺),因此假如服務位於其它程序中,則你會增加程序的重量級,程序也會更容易被系統殺死。
注意:你通常不應該在activity的onResume()和onPause()中繫結和解除繫結,因為這兩個回撥方法在每次切換生命週期狀態時都會發生,這時你應該讓處理工作最少化。而且,如果應用程式中有多個activity都繫結到同一個服務上,則在兩個activity間切換時都會發生狀態轉換,因為當前activity解除繫結(在pause時)後,緊接著下一個activity又會進行繫結(resume時),所以服務也許在銷燬後馬上就要重建。(這種activity狀態轉換、多個activity間的生命週期協作在Activities文件中描述。)

更多展示繫結服務的示例程式碼,請參閱ApiDemos中的RemoteService.java類。

管理Bound服務的生命週期

Service binding tree lifecycle.png
圖 1. started且允許繫結的服務的生命週期

一旦服務被所有客戶端解除繫結,則Android系統將會銷燬它(除非它同時又是用onStartCommand()started)。因此,如果你的服務就是一個純粹的bound服務,那你就不需要管理它的生命週期——Android系統會替你管理,根據是否還有客戶端對其繫結即可。

不過,如果你選擇實現onStartCommand()回撥方法,那麼你就必須顯式地終止服務,因為此服務現在已經被視為started了。這種情況下,無論是否還存在客戶端與其繫結,此服務都會執行下去,直至自行用stopSelf()終止或由其它元件呼叫stopService()來終止。

此外,如果你的服務是started且允許被繫結,那麼系統呼叫你的onUnbind()方法時,你可以選擇返回true。這樣作的結果就是,下次客戶端繫結時將會收到onRebind()呼叫(而不是收到onBind()呼叫)。onRebind()返回void,但客戶端仍然能在它的onServiceConnected()回撥方法中收到IBinder。圖1展示了這種生命週期的執行邏輯。

關於started服務生命週期的更多資訊,請參閱服務文件。

前一個後一個
Except as noted, this content is licens