繫結服務是用戶端-伺服器介面中的伺服器。可讓活動等元件繫結至服務、傳送要求、接收回應,以及執行處理序間通訊 (IPC)。繫結服務通常只在為其他應用程式元件提供服務時存在,不會無限期在背景執行。
本文說明如何建立繫結服務,包括如何從其他應用程式元件繫結至服務。如要進一步瞭解一般服務,例如如何從服務傳送通知,以及將服務設為在前景執行,請參閱「 服務總覽」。
基本概念
繫結服務是 Service 類別的實作,可讓其他應用程式繫結並與其互動。如要為服務提供繫結,請實作 onBind() 回呼方法。這個方法會傳回 IBinder 物件,定義用戶端可用來與服務互動的程式設計介面。
繫結至已啟動的服務
如「服務總覽」所述,您可以建立已啟動且已繫結的服務。也就是說,您可以呼叫 startService() 啟動服務,讓服務無限期執行。您也可以呼叫 bindService(),讓用戶端繫結至服務。
如果您允許服務啟動並繫結,則服務啟動時,系統「不會」在所有用戶端解除繫結時刪除服務。您必須呼叫 stopSelf() 或 stopService(),明確停止服務。
雖然您通常會實作 onBind()「或」onStartCommand(),但有時必須同時實作兩者。舉例來說,音樂播放器可能會發現,讓服務無限期執行並提供繫結功能,會很有幫助。這樣一來,活動就能啟動服務來播放音樂,即使使用者離開應用程式,音樂也會繼續播放。接著,當使用者返回應用程式時,活動可以繫結至服務,重新取得播放控制權。
如要進一步瞭解在已啟動的服務中新增繫結時的服務生命週期,請參閱「管理繫結服務的生命週期」一節。
用戶端會呼叫 bindService(),繫結至服務。此時,它必須提供 ServiceConnection 的實作,以監控與服務的連線。bindService() 的傳回值會指出要求的服務是否存在,以及用戶端是否有權存取該服務。
Android 系統建立用戶端與服務之間的連線時,會呼叫 ServiceConnection 的 onServiceConnected()。onServiceConnected() 方法包含 IBinder 引數,用戶端會使用該引數與繫結服務通訊。
您可以同時將多個用戶端連線至服務。不過,系統會快取 IBinder 服務通訊管道。換句話說,只有在第一個用戶端繫結時,系統才會呼叫服務的 onBind() 方法來產生 IBinder。接著,系統會將相同的 IBinder 傳送至繫結至該服務的所有其他用戶端,而不會再次呼叫 onBind()。
當最後一個用戶端與服務解除繫結時,系統會刪除服務,除非服務是使用 startService() 啟動。
實作繫結服務時,最重要的部分是定義 onBind() 回呼方法傳回的介面。下一節將說明定義服務 IBinder 介面的幾種方式。
建立繫結服務
建立提供繫結的服務時,您必須提供 IBinder,提供用戶端可用於與服務互動的程式設計介面。定義介面的方式有三種:
- 擴充 Binder 類別
- 如果服務是應用程式專用,且與用戶端在同一個程序中執行 (這是常見情況),請擴充
Binder類別並從onBind()傳回該類別的例項,藉此建立介面。用戶端會收到Binder,並可使用該物件直接存取Binder實作或Service中可用的公開方法。如果服務只是應用程式的背景工作站,建議採用這項技術。只有在其他應用程式或不同程序使用您的服務時,才不建議使用這種方式建立介面。
- 使用 Messenger
- 如需讓介面在不同程序中運作,可以使用
Messenger為服務建立介面。透過這種方式,服務會定義Handler,回應不同類型的Message物件。這個
Handler是Messenger的基礎,可與用戶端共用IBinder,讓用戶端使用Message物件傳送指令給服務。此外,用戶端可以定義自己的Messenger,以便服務傳回訊息。這是執行處理序間通訊 (IPC) 最簡單的方法,因為
Messenger會將所有要求排入單一執行緒,因此您不必將服務設計為執行緒安全。 - 使用 AIDL
- Android 介面定義語言 (AIDL) 會將物件分解為作業系統可理解的原語,並跨程序封送處理,以執行 IPC。先前使用
Messenger的技術,實際上是以 AIDL 做為基礎結構。如上一節所述,
Messenger會在單一執行緒中建立所有用戶端要求的佇列,因此服務會一次接收一個要求。不過,如果您希望服務同時處理多個要求,則可以直接使用 AIDL。在這種情況下,服務必須具備執行緒安全和多執行緒功能。如要直接使用 AIDL,請建立定義程式設計介面的
.aidl檔案。Android SDK 工具會使用這個檔案產生抽象類別,用於實作介面及處理 IPC,您可以在服務中擴充這個類別。
注意:對大多數應用程式而言,AIDL 並非建立繫結服務的最佳選擇,因為這可能需要多執行緒功能,且可能導致實作更複雜。因此,本文不會討論如何將其用於您的服務。如果您確定需要直接使用 AIDL,請參閱 AIDL 文件。
擴充 Binder 類別
如果只有本機應用程式使用您的服務,且不需要跨程序運作,則可實作自己的 Binder 類別,讓用戶端直接存取服務中的公開方法。
注意:只有在用戶端和服務位於同一個應用程式和程序時,這種做法才有效,這也是最常見的情況。舉例來說,如果音樂應用程式需要將活動繫結至在背景播放音樂的服務,就很適合使用這種方法。
設定方式如下:
- 在服務中建立
Binder執行個體,並執行下列其中一項操作:- 包含用戶端可呼叫的公開方法。
- 傳回目前的
Service執行個體,其中包含用戶端可呼叫的公開方法。 - 傳回服務託管的其他類別例項,其中包含用戶端可呼叫的公開方法。
- 從
onBind()回呼方法傳回這個Binder例項。 - 在用戶端中,從
onServiceConnected()回呼方法接收Binder,並使用提供的方法呼叫繫結服務。
注意:服務和用戶端必須位於同一個應用程式中,用戶端才能轉換傳回的物件,並正確呼叫其 API。服務和用戶端也必須位於同一個程序中,因為這項技術不會在程序之間執行任何封送處理。
舉例來說,以下服務可讓用戶端透過 Binder 實作存取服務中的方法:
Kotlin
class LocalService : Service() { // Binder given to clients. private val binder = LocalBinder() // Random number generator. private val mGenerator = Random() /** Method for clients. */ val randomNumber: Int get() = mGenerator.nextInt(100) /** * 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. */ inner class LocalBinder : Binder() { // Return this instance of LocalService so clients can call public methods. fun getService(): LocalService = this@LocalService } override fun onBind(intent: Intent): IBinder { return binder } }
Java
public class LocalService extends Service { // Binder given to clients. private final IBinder binder = 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 binder; } /** Method for clients. */ public int getRandomNumber() { return mGenerator.nextInt(100); } }
LocalBinder 提供 getService() 方法,讓用戶端擷取 LocalService 的目前例項。這樣一來,用戶端就能呼叫服務中的公開方法。舉例來說,用戶端可以從服務呼叫 getRandomNumber()。
以下活動會繫結至 LocalService,並在使用者點選按鈕時呼叫 getRandomNumber():
Kotlin
class BindingActivity : Activity() { private var mService: LocalService? = null private var mBound: Boolean = false /** Defines callbacks for service binding, passed to bindService(). */ private val connection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // We've bound to LocalService, cast the IBinder and get LocalService instance. val binder = service as LocalService.LocalBinder mService = binder.getService() mBound = true } override fun onServiceDisconnected(arg0: ComponentName) { mService = null mBound = false } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) } override fun onStart() { super.onStart() // Bind to LocalService. Intent(this, LocalService::class.java).also { intent -> bindService(intent, connection, Context.BIND_AUTO_CREATE) } } override fun onStop() { super.onStop() unbindService(connection) // Release the reference so the service can be garbage-collected once it's // destroyed. onServiceDisconnected() isn't called when the client unbinds. mService = null mBound = false } /** Called when a button is clicked (the button in the layout file attaches to * this method with the android:onClick attribute). */ fun onButtonClick(v: View) { if (mBound) { // Call a method from the LocalService. // However, if this call is something that might hang, then put this request // in a separate thread to avoid slowing down the activity performance. val num: Int = mService?.randomNumber ?: return Toast.makeText(this, "number: $num", Toast.LENGTH_SHORT).show() } } }
Java
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, connection, Context.BIND_AUTO_CREATE); } @Override protected void onStop() { super.onStop(); unbindService(connection); // Release the reference so the service can be garbage-collected once it's // destroyed. onServiceDisconnected() isn't called when the client unbinds. mService = null; 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 is something that might hang, then put this request // 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 connection = 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) { mService = null; mBound = false; } }; }
上述範例說明用戶端如何使用 ServiceConnection 的實作和 onServiceConnected() 回呼繫結至服務。下一節將進一步說明繫結至服務的程序。
注意:在上述範例中,onStop() 方法會解除用戶端與服務的繫結,並釋出用戶端對服務的參照。在適當的時間取消用戶端與服務的繫結,如「其他注意事項」一節所述。
如需更多程式碼範例,請參閱 ApiDemos 中的
LocalService.java 類別和
LocalServiceActivities.java 類別。
使用 Messenger
如需讓服務與遠端程序通訊,可以使用 Messenger 為服務提供介面。這項技術可讓您執行處理序間通訊 (IPC),而不必使用 AIDL。
使用 Messenger 做為介面比使用 AIDL 簡單,因為 Messenger 會將所有對服務的呼叫排入佇列。純 AIDL 介面會同時向服務傳送要求,服務則必須處理多執行緒。
對大多數應用程式來說,服務不需要執行多執行緒,因此使用 Messenger 可讓服務一次處理一個呼叫。如果服務必須採用多執行緒,請使用 AIDL 定義介面。
以下是 Messenger 的使用方式摘要:
- 服務會實作
Handler,接收來自用戶端的每個呼叫回呼。 - 服務會使用
Handler建立Messenger物件 (這是Handler的參照)。 Messenger會建立IBinder,服務會從onBind()將其傳回用戶端。- 用戶端會使用
IBinder例項化Messenger(參照服務的Handler),用戶端會使用該物件將Message物件傳送至服務。 - 服務會在
Handler中 (具體來說,是在handleMessage()方法中) 接收每個Message。
這樣一來,用戶端就沒有可呼叫服務的方法。而是由用戶端傳送訊息 (Message 物件),由服務在 Handler 中接收。
以下是使用 Messenger 介面的服務範例:
Kotlin
/** Command to the service to display a message. */ private const val MSG_SAY_HELLO = 1 class MessengerService : Service() { /** * Target we publish for clients to send messages to IncomingHandler. */ private lateinit var mMessenger: Messenger /** * Handler of incoming messages from clients. */ internal class IncomingHandler( context: Context, private val applicationContext: Context = context.applicationContext ) : Handler() { override fun handleMessage(msg: Message) { when (msg.what) { MSG_SAY_HELLO -> Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show() else -> super.handleMessage(msg) } } } /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. */ override fun onBind(intent: Intent): IBinder? { Toast.makeText(applicationContext, "binding", Toast.LENGTH_SHORT).show() mMessenger = Messenger(IncomingHandler(this)) return mMessenger.binder } }
Java
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. */ static class IncomingHandler extends Handler { private Context applicationContext; IncomingHandler(Context context) { applicationContext = context.getApplicationContext(); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_SAY_HELLO: Toast.makeText(applicationContext, "hello!", Toast.LENGTH_SHORT).show(); break; default: super.handleMessage(msg); } } } /** * Target we publish for clients to send messages to IncomingHandler. */ Messenger mMessenger; /** * 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(); mMessenger = new Messenger(new IncomingHandler(this)); return mMessenger.getBinder(); } }
Handler 中的 handleMessage() 方法是服務接收傳入 Message 的位置,並根據 what 成員決定要執行的動作。
用戶端只需要根據服務傳回的 IBinder 建立 Messenger,並使用 send() 傳送訊息。舉例來說,以下活動會繫結至服務,並將 MSG_SAY_HELLO 訊息傳送至服務:
Kotlin
class ActivityMessenger : Activity() { /** Messenger for communicating with the service. */ private var mService: Messenger? = null /** Flag indicating whether we have called bind on the service. */ private var bound: Boolean = false /** * Class for interacting with the main interface of the service. */ private val mConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { // 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 = Messenger(service) bound = true } override fun onServiceDisconnected(className: ComponentName) { // This is called when the connection with the service has been // unexpectedly disconnected—that is, its process crashed. mService = null bound = false } } fun sayHello(v: View) { if (!bound) return // Create and send a message to the service, using a supported 'what' value. val msg: Message = Message.obtain(null, MSG_SAY_HELLO, 0, 0) try { mService?.send(msg) } catch (e: RemoteException) { e.printStackTrace() } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) } override fun onStart() { super.onStart() // Bind to the service. Intent(this, MessengerService::class.java).also { intent -> bindService(intent, mConnection, Context.BIND_AUTO_CREATE) } } override fun onStop() { super.onStop() // Unbind from the service. if (bound) { unbindService(mConnection) mService = null bound = false } } }
Java
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 bound; /** * 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); bound = 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; bound = false; } }; public void sayHello(View v) { if (!bound) 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 (bound) { unbindService(mConnection); mService = null; bound = false; } } }
這個範例並未說明服務如何回應用戶端。如要讓服務回應,您也必須在用戶端中建立 Messenger。用戶端收到 onServiceConnected() 回呼時,會將 Message 傳送至服務,其中包含 send() 方法的 replyTo 參數中的用戶端 Messenger。
您可以參考
MessengerService.java (服務) 和
MessengerServiceActivities.java (用戶端) 範例,瞭解如何提供雙向訊息。
繫結至服務
應用程式元件 (用戶端) 可以呼叫 bindService(),繫結至服務。接著,Android 系統會呼叫服務的 onBind() 方法,該方法會傳回 IBinder,用於與服務互動。
繫結是非同步的,bindService() 會立即傳回,而「不會」將 IBinder 傳回用戶端。如要接收 IBinder,用戶端必須建立 ServiceConnection 的例項,並傳遞至 bindService()。ServiceConnection 包含回呼方法,系統會呼叫該方法來傳送 IBinder。
注意:只有活動、服務和內容供應器可以繫結至服務,您無法從廣播接收器繫結至服務。
如要從用戶端繫結至服務,請按照下列步驟操作:
- 實作
ServiceConnection。實作項目必須覆寫兩個回呼方法:
onServiceConnected()- 系統會呼叫這個方法,傳送服務的
onBind()方法傳回的IBinder。 onServiceDisconnected()- 當與服務的連線意外中斷時 (例如服務當機或終止),Android 系統會呼叫這個方法。用戶端解除繫結時,系統不會呼叫這個方法。
- 呼叫
bindService(),傳遞ServiceConnection實作。注意:如果方法傳回 false,表示用戶端與服務的連線無效。不過,請務必在用戶端呼叫
unbindService()。否則,用戶端會阻止服務在閒置時關閉。 - 系統呼叫
onServiceConnected()回呼方法時,您可以使用介面定義的方法,開始呼叫服務。 - 如要中斷與服務的連線,請呼叫
unbindService()。如果應用程式刪除用戶端時,用戶端仍繫結至服務,刪除作業會導致用戶端解除繫結。建議您在用戶端與服務互動完畢後,立即解除繫結。這樣一來,閒置的服務就會關閉。如要進一步瞭解繫結和解除繫結的適當時機,請參閱「其他注意事項」一節。
以下範例透過擴充 Binder 類別,將用戶端連結至先前建立的服務,因此只需要將傳回的 IBinder 轉換為 LocalBinder 類別,並要求 LocalService 執行個體:
Kotlin
var mService: LocalService? = null val mConnection = object : ServiceConnection { // Called when the connection with the service is established. override fun onServiceConnected(className: ComponentName, service: IBinder) { // 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. val binder = service as LocalService.LocalBinder mService = binder.getService() mBound = true } // Called when the connection with the service disconnects unexpectedly. override fun onServiceDisconnected(className: ComponentName) { Log.e(TAG, "onServiceDisconnected") mService = null mBound = false } }
Java
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"); mService = null; mBound = false; } };
有了這個 ServiceConnection,用戶端就能將其傳遞至 bindService(),藉此繫結至服務,如以下範例所示:
Kotlin
Intent(this, LocalService::class.java).also { intent -> bindService(intent, connection, Context.BIND_AUTO_CREATE) }
Java
Intent intent = new Intent(this, LocalService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE);
bindService()的第一個參數是Intent,明確指定要繫結的服務。注意:如果您使用意圖繫結至
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 系統會將其刪除 (除非服務是使用 startService() 啟動)。因此,如果服務純粹是繫結服務,您就不必管理服務的生命週期。Android 系統會根據服務是否繫結至任何用戶端,為您管理服務。
不過,如果您選擇實作 onStartCommand() 回呼方法,則必須明確停止服務,因為服務現在會視為「已啟動」。在這種情況下,無論服務是否繫結至任何用戶端,都會持續執行,直到服務本身透過 stopSelf() 停止,或另一個元件呼叫 stopService() 為止。
此外,如果服務已啟動並接受繫結,當系統呼叫 onUnbind() 方法時,您可以選擇傳回 true,以便在下次用戶端繫結至服務時接收 onRebind() 呼叫。onRebind() 會傳回 void,但用戶端仍會在 onServiceConnected() 回呼中收到 IBinder。下圖說明這類生命週期的邏輯。

如要進一步瞭解已啟動服務的生命週期,請參閱「服務總覽」。