绑定服务是客户端-服务器接口中的服务器。它允许活动等组件绑定到服务、发送请求、接收响应和执行进程间通信 (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
类和 LocalServiceActivities.java
类,位于ApiDemos。
使用Messenger
如果您需要您的服务与远程进程通信,那么您可以使用Messenger
为您的服务提供接口。此技术允许您执行进程间通信 (IPC),而无需使用AIDL。
使用Messenger
作为您的接口比使用AIDL更简单,因为Messenger
将所有对服务的调用排队。纯AIDL接口会向服务发送同时请求,然后服务必须处理多线程。
对于大多数应用程序,服务不需要执行多线程,因此使用Messenger
允许服务一次处理一个调用。如果您的服务必须是多线程的,请使用AIDL来定义您的接口。
以下是使用Messenger
的方法概要:
- 服务实现一个
Handler
,该处理器接收来自客户端的每个调用的回调。 - 服务使用
Handler
创建一个Messenger
对象(它是对Handler
的引用)。 Messenger
创建一个IBinder
,服务从onBind()
将其返回给客户端。- 客户端使用
IBinder
实例化Messenger
(它引用服务的Handler
),客户端使用它向服务发送Message
对象。 - 服务在其
Handler
中接收每个Message
——具体来说,是在handleMessage()
方法中。
通过这种方式,客户端没有可以调用服务的_方法_。相反,客户端传递_消息_(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(); } }
handleMessage()
方法位于Handler
中,服务在此接收传入的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
,其中包含replyTo
参数中的客户端Messenger
。send()
方法。
您可以查看如何在 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()
回调期间绑定和取消绑定,因为这些回调在每次生命周期转换时都会发生。请将这些转换中发生的处理保持在最低限度。此外,如果应用程序中的多个活动绑定到同一服务,并且在两个活动之间进行转换,则在下一个活动绑定(在恢复期间)之前,当前活动取消绑定(在暂停期间),服务可能会被销毁并重新创建。活动生命周期协调方式的这种活动转换在活动生命周期中进行了描述。
- 如果您只需要在活动可见时与服务交互,请在
有关显示如何绑定到服务的更多示例代码,请参阅 RemoteService.java
类中的ApiDemos。
管理绑定服务的生命周期
当从所有客户端取消绑定服务时,Android 系统会销毁它(除非它是使用startService()
启动的)。因此,如果服务纯粹是绑定服务,则无需管理服务的生命周期。Android 系统会根据它是否绑定到任何客户端来管理它。
但是,如果您选择实现onStartCommand()
回调方法,则必须显式停止服务,因为服务现在被认为是*已启动*的。在这种情况下,服务会一直运行,直到服务使用stopSelf()
自行停止或另一个组件调用stopService()
,而不管它是否绑定到任何客户端。
此外,如果您的服务已启动并接受绑定,则当系统调用您的onUnbind()
方法时,您可以选择返回true
,如果您希望在下一次客户端绑定到服务时接收对onRebind()
的调用。onRebind()
返回 void,但客户端仍在其onServiceConnected()
回调中接收IBinder
。下图说明了这种生命周期的逻辑。
有关已启动服务的生命周期的更多信息,请参阅服务概述。