绑定服务是客户端-服务器接口中的服务器。它允许诸如活动之类的组件绑定到该服务、发送请求、接收响应以及执行进程间通信 (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
给客户端。- 客户端使用
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()
回调时,它会向服务发送一条包含客户端Messenger
的Message
,该消息包含在replyTo
参数中,作为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
。下图说明了这种生命周期的逻辑。
有关启动服务的生命周期的更多信息,请参阅 服务概述。