Service
是一种可在后台执行长时间运行的操作的应用组件。不提供界面。启动后,服务可能会继续运行一段时间,即使在用户切换到其他应用后也是如此。此外,组件可以绑定到服务以与其交互,甚至执行进程间通信 (IPC)。例如,服务可以在后台处理网络事务、播放音乐、执行文件 I/O 或与 content provider 交互。
注意:服务在其托管进程的主线程中运行;除非另行指定,否则服务不会创建自己的线程,也不会在单独的进程中运行。您应在服务内的单独的线程上运行任何阻塞操作,以免发生“应用无响应”(ANR) 错误。
Service 类型
以下是三种不同的服务类型:
- 前景
-
前台服务执行一些用户能够注意到的操作。例如,音频应用会使用前台服务来播放音轨。前台服务必须显示 Notification。即使用户没有与应用互动,前台服务也会继续运行。
使用前台服务时,您必须显示通知,让用户知道该服务正在运行。除非服务停止或从前台移除,否则无法关闭此通知。
详细了解如何在应用中配置前台服务。
注意: WorkManager API 提供了一种灵活的任务调度方法,并且能够根据需要将这些作业作为前台服务运行。在许多情况下,使用 WorkManager 比直接使用前台服务更可取。
- 背景
- 后台服务执行用户不会直接注意到的操作。例如,如果应用使用某项服务来压缩其存储空间,则该服务通常是后台服务。
注意:如果您的应用以 API 级别 26 或更高级别为目标平台,那么当应用本身未在前台运行时,系统会对运行后台服务施加限制。例如,在大多数情况下,您不应在后台访问位置信息。请改为使用 WorkManager 调度任务。
- 绑定
- 当应用组件通过调用
bindService()
绑定到服务时,即服务绑定。绑定服务提供了一个客户端-服务器接口,该接口允许组件与服务交互、发送请求、接收结果,甚至可以通过进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可以同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
虽然本文档大体上分别讨论了已启动的服务和绑定的服务,但您的服务可以同时以这两种方式运行:服务可以启动(无限期运行)且允许绑定。这仅仅取决于您是否实现一对回调方法:实现一对回调方法:onStartCommand()
以允许组件启动它,而 onBind()
允许绑定。
无论您的服务是处于启动状态、绑定状态还是同时处于这两种状态,任何应用组件都可以像使用 activity 一样(通过以 Intent
启动服务),像使用 activity 时一样(即使通过单独的应用使用服务)。不过,您可以在清单文件中将该服务声明为“专用”,并阻止其他应用访问。如需了解详情,请参阅在清单中声明服务部分。
在服务和线程之间进行选择
简单来说,服务是一种即使用户没有与应用交互也可以在后台运行的组件,因此,您应仅在有需要时才创建服务。
如果您必须在主线程之外执行工作,但只能在用户与应用交互时执行,则应改为在其他应用组件的上下文中创建新线程。例如,如果您想播放一些音乐,但仅当 activity 正在运行时,您可以在 onCreate()
中创建线程,在 onStart()
中开始运行线程,并在 onStop()
中停止线程。
此外,您还可以考虑使用 java.util.concurrent
软件包或 Kotlin 协程中的线程池和执行器,而不是传统的 Thread
类。如需详细了解如何将执行移至后台线程,请参阅 Android 上的线程处理文档。
请记住,如果您确实要使用某项服务,它在默认情况下仍会在应用的主线程中运行,因此,如果该服务执行密集型或阻塞性操作,您仍应在该服务中创建新线程。
基础知识
如需创建服务,您必须创建 Service
的子类或使用它的某个现有子类。在实现中,您必须替换一些回调方法,以便处理服务生命周期的一些关键方面,并提供一种可让组件绑定到服务的机制(如果适用)。以下是您应替换的最重要的回调方法:
onStartCommand()
- 当另一个组件(如 activity)请求启动服务时,系统会通过调用
startService()
来调用此方法。执行此方法时,服务会启动并可在后台无限期运行。如果您实现此方法,则在服务工作完成后,需要负责通过调用stopSelf()
或stopService()
来停止服务。如果您只想提供绑定,则无需实现此方法。 onBind()
- 当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用
bindService()
来调用此方法。在此方法的实现中,您必须通过返回IBinder
提供一个接口,供客户端用来与服务通信。您必须始终实现此方法;但是,如果您不想允许绑定,则应返回 null。 onCreate()
- 首次创建服务时(在调用
onStartCommand()
或onBind()
之前),系统会调用此方法来执行一次性设置过程。如果服务已在运行,则不会调用此方法。 onDestroy()
- 当不再使用服务并将其销毁时,系统会调用此方法。您的服务应实现此目标以清理所有资源,例如线程、注册的监听器或接收器。这是服务接收的最后一个调用。
如果某个组件通过调用 startService()
启动服务(这会导致对 onStartCommand()
的调用),则服务将持续运行,直到它使用 stopSelf()
自行停止或由其他组件通过调用 stopService()
将其停止。
如果某个组件通过调用 bindService()
来创建服务,但系统未调用 onStartCommand()
,则仅当该组件与其绑定时,服务才会运行。当服务与其所有客户端解除绑定后,系统会销毁它。
仅当内存较低且必须回收具有用户焦点的 activity 的系统资源时,Android 系统才会停止服务。如果服务绑定到以用户为焦点的 activity,则它不太可能被终止;如果将服务声明为在前台运行,则它很少被终止。如果服务已启动并长期运行,那么系统会随着时间的推移而降低其在后台任务列表中的位置,而服务极容易遭到终止。如果服务已启动,您必须将其设计为能够妥善处理系统执行的重启。如果系统终止您的服务,它会在资源可用后立即重启服务,但这还取决于您从 onStartCommand()
返回的值。如需详细了解系统会在何时销毁服务,请参阅进程和线程文档。
在以下部分中,您将了解如何创建 startService()
和 bindService()
服务方法,以及如何通过其他应用组件使用这些方法。
使用清单文件声明服务
正如对待 activity 和其他组件一样,您必须在应用的清单文件中声明所有服务。
如需声明服务,请添加 <service>
元素作为 <application>
元素的子元素。示例如下:
<manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application> </manifest>
如需详细了解如何在清单中声明服务,请参阅 <service>
元素参考。
您还可以在 <service>
元素中添加其他属性,以定义一些属性,例如启动服务所需的权限以及服务应在哪个进程中运行。android:name
属性是唯一的必需属性,用于指定服务的类名称。发布应用后,请勿更改此名称,以免因依赖显式 intent 来启动或绑定服务而破坏代码的风险(请阅读博文 Things That Cannot Change [不能更改的内容])。
注意:为确保您的应用安全无虞,请务必在启动 Service
时使用显式 intent,并且不要为您的服务声明 intent 过滤器。使用隐式 intent 启动服务存在安全隐患,因为您无法确定会响应 intent 的服务,而且用户也看不到启动的是哪项服务。从 Android 5.0(API 级别 21)开始,如果使用隐式 intent 调用 bindService()
,系统会抛出异常。
您可以添加 android:exported
属性并将其设置为 false
,以确保您的服务仅供您的应用使用。这样可以有效地阻止其他应用启动您的服务,即使使用显式 intent 也是如此。
注意:用户可以查看其设备上正在运行哪些服务。如果用户看到自己无法识别或信任的服务,则可以停止该服务。为了避免用户意外停止您的服务,您需要将 android:description
属性添加到应用清单中的 <service>
元素。在说明中,用一小句话说明服务的用途及其提供的优势。
创建启动服务
启动服务是由另一个组件通过调用 startService()
启动的服务,这会导致调用服务的 onStartCommand()
方法。
服务启动后,其生命周期将独立于启动服务的组件。服务可以在后台无限期运行,即使启动它的组件已被销毁。因此,服务应在作业完成时通过调用 stopSelf()
自行停止运行,或者由其他组件通过调用 stopService()
来将其停止。
应用组件(如 activity)可以通过调用 startService()
并传递 Intent
来启动该服务,该组件指定了该服务并包含要使用的服务的所有数据。服务会在 onStartCommand()
方法中收到此 Intent
。
例如,假设某个 activity 需要将一些数据保存到在线数据库。此 activity 可以启动一个配套服务,并通过向 startService()
传递一个 intent 来为其传递要保存的数据。服务在 onStartCommand()
中接收 intent,连接到互联网,并执行数据库事务。事务完成后,服务会自行停止并被销毁。
注意:默认情况下,服务在声明所在的应用所在的进程中运行,同时在该应用的主线程中运行。如果用户与同一应用中的 activity 交互时,您的服务执行密集型操作或阻塞性操作,则会降低 activity 性能。为避免影响应用性能,请在服务内启动新线程。
Service
类是适用于所有服务的基类。扩展此类时,请务必创建一个新的线程,让服务可在其中完成其所有工作;默认情况下,服务会使用应用的主线程,这可能会降低应用正在运行的任何 activity 的性能。
Android 框架还提供了 Service
的 IntentService
子类,该类使用工作器线程逐一处理所有启动请求。不建议新应用使用此类,因为它从 Android 8 Oreo 开始运行不稳定,这是因为引入了后台执行限制。此外,从 Android 11 开始,它已废弃。您可以使用 JobIntentService 来替代与较新版本 Android 兼容的 IntentService
。
以下部分介绍了如何实现您自己的自定义服务,但在大多数情况下,您应强烈考虑改用 WorkManager。请参阅 Android 平台上的后台处理指南,了解是否存在符合您需求的解决方案。
扩展服务类
您可以扩展 Service
类来处理每个传入的 intent。基本实现可能如下所示:
Kotlin
class HelloService : Service() { private var serviceLooper: Looper? = null private var serviceHandler: ServiceHandler? = null // Handler that receives messages from the thread private inner class ServiceHandler(looper: Looper) : Handler(looper) { override fun handleMessage(msg: Message) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. try { Thread.sleep(5000) } catch (e: InterruptedException) { // Restore interrupt status. Thread.currentThread().interrupt() } // Stop the service using the startId, so that we don't stop // the service in the middle of handling another job stopSelf(msg.arg1) } } override fun onCreate() { // Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. We also make it // background priority so CPU-intensive work will not disrupt our UI. HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND).apply { start() // Get the HandlerThread's Looper and use it for our Handler serviceLooper = looper serviceHandler = ServiceHandler(looper) } } override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show() // For each start request, send a message to start a job and deliver the // start ID so we know which request we're stopping when we finish the job serviceHandler?.obtainMessage()?.also { msg -> msg.arg1 = startId serviceHandler?.sendMessage(msg) } // If we get killed, after returning from here, restart return START_STICKY } override fun onBind(intent: Intent): IBinder? { // We don't provide binding, so return null return null } override fun onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show() } }
Java
public class HelloService extends Service { private Looper serviceLooper; private ServiceHandler serviceHandler; // Handler that receives messages from the thread private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. try { Thread.sleep(5000); } catch (InterruptedException e) { // Restore interrupt status. Thread.currentThread().interrupt(); } // Stop the service using the startId, so that we don't stop // the service in the middle of handling another job stopSelf(msg.arg1); } } @Override public void onCreate() { // Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. We also make it // background priority so CPU-intensive work doesn't disrupt our UI. HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // Get the HandlerThread's Looper and use it for our Handler serviceLooper = thread.getLooper(); serviceHandler = new ServiceHandler(serviceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); // For each start request, send a message to start a job and deliver the // start ID so we know which request we're stopping when we finish the job Message msg = serviceHandler.obtainMessage(); msg.arg1 = startId; serviceHandler.sendMessage(msg); // If we get killed, after returning from here, restart return START_STICKY; } @Override public IBinder onBind(Intent intent) { // We don't provide binding, so return null return null; } @Override public void onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); } }
示例代码会处理 onStartCommand()
中的所有传入调用,并将工作发布到后台线程上运行的 Handler
。它的工作原理就像 IntentService
一样,按顺序依次处理所有请求。例如,如果您想同时运行多个请求,就可以更改代码以在线程池上运行工作。
请注意,onStartCommand()
方法必须返回一个整数。该整数是一个值,用于描述系统应如何在系统终止服务的情况下继续运行服务。onStartCommand()
的返回值必须是以下常量之一:
START_NOT_STICKY
- 如果系统在
onStartCommand()
返回后终止服务,请不要重新创建服务,除非有待处理的 intent 需要传递。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 START_STICKY
- 如果系统在
onStartCommand()
返回后终止服务,请重新创建服务并调用onStartCommand()
,但不要重新提交最后一个 intent。相反,除非有待处理 intent 用于启动服务,否则系统会使用 null intent 调用onStartCommand()
。在这种情况下,系统会传递这些 intent。这适用于不执行命令但无限期运行并等待作业的媒体播放器(或类似服务)。 START_REDELIVER_INTENT
- 如果系统在
onStartCommand()
返回后终止服务,请重新创建服务并使用传递给服务的最后一个 intent 调用onStartCommand()
。所有待处理 intent 将依次传递。这适用于主动执行应立即恢复的作业(例如下载文件)的服务。
如需详细了解这些返回值,请参阅每个常量的链接参考文档。
启动服务
您可以通过将 Intent
传递给 startService()
或 startForegroundService()
,从 activity 或其他应用组件启动服务。Android 系统会调用服务的 onStartCommand()
方法,并向其传递 Intent
,后者用于指定要启动的服务。
注意:如果您的应用以 API 级别 26 或更高级别为目标平台,那么除非应用本身在前台运行,否则系统会对使用或创建后台服务施加限制。如果应用需要创建前台服务,则应调用 startForegroundService()
。该方法会创建后台服务,但会向系统表明该服务会将自身提升到前台。创建服务后,该服务必须在 5 秒内调用其 startForeground()
方法。
例如,activity 可以使用带有 startService()
的显式 intent 启动上一部分中的示例服务 (HelloService
),如下所示:
Kotlin
startService(Intent(this, HelloService::class.java))
Java
startService(new Intent(this, HelloService.class));
startService()
方法会立即返回,并且 Android 系统会调用服务的 onStartCommand()
方法。如果服务尚未运行,系统会先调用 onCreate()
,然后再调用 onStartCommand()
。
如果服务也没有提供绑定,则使用 startService()
传递的 intent 是应用组件与服务之间的唯一通信模式。但是,如果您希望服务返回结果,启动服务的客户端可以为广播创建 PendingIntent
(使用 getBroadcast()
),并将其传递给启动服务的 Intent
中的服务。然后,服务可以使用广播传递结果。
多个服务启动请求会导致多次对服务的 onStartCommand()
进行相应的调用。但是,只需一个服务停止请求(使用 stopSelf()
或 stopService()
)即可停止服务。
停止服务
启动的服务必须管理自己的生命周期。也就是说,除非必须回收系统内存,否则系统不会停止或销毁服务,并且服务在 onStartCommand()
返回后会继续运行。服务必须通过调用 stopSelf()
自行停止运行,或者由另一个组件通过调用 stopService()
来停止它。
一旦请求使用 stopSelf()
或 stopService()
停止服务,系统就会尽快销毁服务。
如果您的服务同时处理对 onStartCommand()
的多个请求,那么您不应在处理完一个启动请求后停止该服务,因为您可能收到了一个新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为避免此问题,您可以使用 stopSelf(int)
确保服务停止请求始终基于最近的启动请求。也就是说,调用 stopSelf(int)
时,您需要传递与停止请求对应的启动请求的 ID(传递给 onStartCommand()
的 startId
)。然后,如果服务在您能够调用 stopSelf(int)
之前收到新的启动请求,则 ID 不匹配,服务也不会停止。
注意:为了避免浪费系统资源和消耗电池电量,请确保您的应用在完成工作后停止其服务。如有必要,其他组件可以通过调用 stopService()
来停止服务。即使为服务启用了绑定,如果该服务收到对 onStartCommand()
的调用,您也必须始终自行停止该服务。
如需详细了解服务生命周期,请参阅下面有关管理服务生命周期的部分。
创建绑定服务
绑定服务允许应用组件通过调用 bindService()
与其绑定,以创建长期连接。它通常不允许组件通过调用 startService()
来启动它。
当您想与应用中的 activity 和其他组件提供的服务交互,或通过进程间通信 (IPC) 向其他应用公开应用的某些功能时,请创建绑定服务。
如需创建绑定服务,请实现 onBind()
回调方法以返回 IBinder
,用于定义与服务通信的接口。然后,其他应用组件可以调用 bindService()
来检索该接口,并开始对服务调用方法。服务仅用于提供与其绑定的应用组件,因此,如果没有任何组件绑定到该服务,系统就会销毁该服务。您无需像通过 onStartCommand()
启动绑定服务时那样,停止绑定服务。
如需创建绑定服务,您必须定义指定客户端如何与服务通信的接口。服务和客户端之间的这个接口必须是 IBinder
的实现,并且服务必须从 onBind()
回调方法返回该接口。客户端收到 IBinder
后,便可开始通过该接口与服务进行交互。
多个客户端可同时绑定到该服务。当客户端完成与服务的交互后,会调用 unbindService()
来解除绑定。如果没有绑定到服务的客户端,则系统会销毁该服务。
实现绑定服务有多种方法,并且实现比启动服务更复杂。出于这些原因,有关绑定服务的讨论会显示在有关绑定服务的单独文档中。
向用户发送通知
服务在运行时,可以使用信息提示控件通知或状态栏通知向用户通知事件。
信息提示控件通知是指在当前窗口表面显示一会后才会消失的消息。状态栏通知在状态栏中提供带有消息的图标,用户可以选择该图标以执行操作(例如启动 activity)。
通常,状态栏通知是后台工作(例如文件下载)完成且用户现在可以对其执行操作时的最佳方法。当用户从展开视图中选择通知时,通知可以启动一个 activity(例如显示已下载的文件)。
管理服务的生命周期
服务的生命周期比 activity 的生命周期简单得多。但是,密切关注如何创建和销毁服务更为重要,因为服务可以在用户不知情的情况下在后台运行。
服务生命周期(从创建到销毁)可以遵循以下两个路径之一:
- 启动的服务
该服务在其他组件调用
startService()
时创建。然后无限期运行,且必须通过调用stopSelf()
自行停止运行。另一个组件也可以通过调用stopService()
来停止服务。服务停止后,系统会将其销毁。 - 绑定服务
当另一个组件(客户端)调用
bindService()
时,系统将创建该服务。然后,客户端通过IBinder
接口与服务进行通信。客户端可通过调用unbindService()
关闭连接。多个客户端可以绑定到同一服务,当所有客户端均取消绑定时,系统会销毁该服务。该服务不需要自行停止运行。
这两条路径并非完全独立。您可以绑定到已使用 startService()
启动的服务。例如,您可以启动后台音乐服务,方法是使用 Intent
(用于标识要播放的音乐)调用 startService()
。之后,可能当用户想要对播放器进行某种控制或获取有关当前歌曲的信息时,activity 可以通过调用 bindService()
绑定到该服务。在这种情况下,在所有客户端取消绑定之前,stopService()
或 stopSelf()
实际上不会停止服务。
实现生命周期回调
与 activity 一样,服务也有生命周期回调方法,您可以实现这些方法来监控服务状态的变化并在适当的时间执行工作。以下框架服务演示了每种生命周期方法:
Kotlin
class ExampleService : Service() { private var startMode: Int = 0 // indicates how to behave if the service is killed private var binder: IBinder? = null // interface for clients that bind private var allowRebind: Boolean = false // indicates whether onRebind should be used override funonCreate
() { // The service is being created } override funonStartCommand
(intent: Intent?, flags: Int, startId: Int): Int { // The service is starting, due to a call to startService() return startMode } override funonBind
(intent: Intent): IBinder? { // A client is binding to the service with bindService() return binder } override funonUnbind
(intent: Intent): Boolean { // All clients have unbound with unbindService() return allowRebind } override funonRebind
(intent: Intent) { // A client is binding to the service with bindService(), // after onUnbind() has already been called } override funonDestroy
() { // The service is no longer used and is being destroyed } }
Java
public class ExampleService extends Service { int startMode; // indicates how to behave if the service is killed IBinder binder; // interface for clients that bind boolean allowRebind; // indicates whether onRebind should be used @Override public voidonCreate
() { // The service is being created } @Override public intonStartCommand
(Intent intent, int flags, int startId) { // The service is starting, due to a call tostartService()
return startMode; } @Override public IBinderonBind
(Intent intent) { // A client is binding to the service withbindService()
return binder; } @Override public booleanonUnbind
(Intent intent) { // All clients have unbound withunbindService()
return allowRebind; } @Override public voidonRebind
(Intent intent) { // A client is binding to the service withbindService()
, // after onUnbind() has already been called } @Override public voidonDestroy
() { // The service is no longer used and is being destroyed } }
注意:与 activity 生命周期回调方法不同,您不需要调用这些回调方法的父类实现。
图 2 说明了服务的典型回调方法。尽管下图将 startService()
创建的服务与 bindService()
创建的服务区分开来,但请注意,任何服务(无论以何种方式启动)都可能允许客户端与其绑定。最初使用 onStartCommand()
启动的服务(通过调用 startService()
的客户端)仍然可以接收对 onBind()
的调用(当客户端调用 bindService()
时)。
通过实现这些方法,您可以监控服务生命周期的以下两个嵌套循环:
- 服务的整个生命周期从调用
onCreate()
到onDestroy()
返回这段时间内开始。与 activity 一样,服务也会在onCreate()
中完成初始设置,并在onDestroy()
中释放所有剩余资源。例如,音乐播放服务可以在onCreate()
中创建用于播放音乐的线程,然后在onDestroy()
中停止该线程。注意:无论服务是通过
startService()
还是bindService()
创建,都会针对所有服务调用onCreate()
和onDestroy()
方法。 - 服务的活跃生命周期从调用
onStartCommand()
或onBind()
开始。每个方法都会获得传递给startService()
或bindService()
的Intent
。如果服务启动,有效生命周期与整个生命周期同时结束(即使在
onStartCommand()
返回后,服务仍处于活动状态)。对于绑定服务,有效生命周期在onUnbind()
返回时结束。
注意:虽然已启动的服务是通过调用 stopSelf()
或 stopService()
来停止,但服务并无相应的回调(没有 onStop()
回调)。除非服务绑定到客户端,否则系统会在服务停止时将其销毁(onDestroy()
是接收到的唯一回调)。
如需详细了解如何创建提供绑定的服务,请参阅绑定服务文档,该文档详细介绍了管理绑定服务的生命周期部分中的 onRebind()
回调方法。