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 也是如此。
注意:
用户可以查看其设备上运行的服务。如果用户看到自己无法识别或信任的服务,则可以停止该服务。为了避免用户意外停止您的服务,您需要向应用清单中的 <service>
元素添加 android:description
属性。请在说明中用一小句话解释服务的用途及其提供的优势。
创建启动服务
启动服务是另一个组件通过调用 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()
。该方法会创建一个后台服务,但该方法会向系统表明该服务会将自身提升到前台。创建服务后,该服务必须在五秒内调用其 startForeground()
方法。
例如,activity 可以结合使用显式 intent 与 startService()
来启动上一部分中的示例服务 (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()
回调方法。