繫結服務是用戶端伺服器介面中的伺服器。此 API 可讓元件 (例如繫結至服務的活動)、傳送要求、接收回應,以及執行處理序間通訊 (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 lateinit var mService: LocalService 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) { 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) 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 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); 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) { mBound = false; } }; }
上述範例顯示用戶端如何使用 ServiceConnection
和 onServiceConnected()
回呼,將用戶端繫結至服務。下一節將進一步說明這項與服務繫結的程序。
注意:在上述範例中,onStop()
方法會將用戶端與服務解除繫結。如「其他注意事項」一節所述,在適當時機解除用戶端與服務繫結。
如需更多程式碼範例,請參閱
LocalService.java
類別和 ApiDemos 中的
LocalServiceActivities.java
類別。
使用 Messenger
如果您需要讓服務與遠端程序通訊,可以使用 Messenger
為服務提供介面。這項技術可讓您執行處理序間通訊 (IPC),而無需使用 AIDL。
為介面使用 Messenger
比使用 AIDL 簡單,因為 Messenger
會將所有對服務的呼叫排入佇列。純 AIDL 介面同時將多個要求傳送至服務,接著服務必須處理多執行緒。
大多數應用程式都不需要執行多執行緒,因此使用 Messenger
可讓服務一次處理一個呼叫。如果服務必須採用多執行緒,請使用 AIDL 定義介面。
以下摘要說明如何使用 Messenger
:
- 服務會實作
Handler
,以便接收每次用戶端呼叫的回呼。 - 服務會使用
Handler
建立Messenger
物件 (此為Handler
的參照)。 Messenger
會建立服務從onBind()
傳回用戶端的IBinder
。- 用戶端會使用
IBinder
將Messenger
例項化 (參照服務的Handler
),藉此將Message
物件傳送至服務。 - 服務會在其
Handler
中接收每個Message
,特別是在handleMessage()
方法中。
如此一來,用戶端就沒有方法可以呼叫服務。而用戶端會改為傳送服務在 Handler
中接收的訊息 (Message
物件)。
以下是使用 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) 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); 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 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") 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"); 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()
方法時,如果您希望在下次用戶端繫結至服務時收到對 onRebind()
的呼叫,可選擇傳回 true
。onRebind()
會傳回空值,但用戶端仍會在 onServiceConnected()
回呼中收到 IBinder
。下圖說明這種生命週期的邏輯。
![](https://developer.android.com/static/images/fundamentals/service_binding_tree_lifecycle.png?authuser=9&hl=zh-tw)
圖 1 已啟動且允許繫結的服務生命週期。
如要進一步瞭解已啟動服務的生命週期,請參閱服務總覽。