服务概览

A Service 是一个 应用组件,可以在后台执行长时间运行的操作。它不提供用户界面。一旦启动,服务可能会持续运行一段时间,即使在用户切换到另一个应用后也是如此。此外,组件可以绑定到服务以与其交互,甚至执行进程间通信 (IPC)。例如,服务可以处理网络事务、播放音乐、执行文件 I/O 或与内容提供程序交互,所有这些操作都可以在后台进行。

注意:服务在其托管进程的主线程中运行;服务不会创建自己的线程,也不会在单独的进程中运行,除非您另行指定。您应该在服务的单独线程上运行任何阻塞操作,以避免应用无响应 (ANR) 错误。

服务类型

服务分为三种不同的类型

前台

前台服务执行一些用户可感知到的操作。例如,音频应用会使用前台服务来播放音轨。前台服务必须显示通知。即使在用户未与应用交互时,前台服务也会继续运行。

当您使用前台服务时,必须显示通知,以便用户主动了解服务正在运行。除非服务停止或从前台移除,否则此通知无法被解除。

详细了解如何在应用中配置前台服务

注意:WorkManager API 提供了一种灵活的任务调度方式,并且能够根据需要将这些作业作为前台服务运行。在许多情况下,使用 WorkManager 比直接使用前台服务更可取。

后台
后台服务执行的操作不会被用户直接注意到。例如,如果一个应用使用服务来压缩其存储空间,那通常会是一个后台服务。

注意:如果您的应用针对 API 级别 26 或更高版本,则当应用本身不在前台时,系统会对运行后台服务施加限制。在大多数情况下,例如,您不应从后台访问位置信息。相反,请使用 WorkManager 调度任务

绑定
当应用组件通过调用 bindService() 绑定到服务时,服务即为绑定状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务交互、发送请求、接收结果,甚至通过进程间通信 (IPC) 进行这些操作。绑定服务仅在其有其他应用组件绑定到它时运行。多个组件可以同时绑定到服务,但当所有组件都解除绑定时,服务将被销毁。

尽管本文档通常分别讨论启动服务和绑定服务,但您的服务可以同时以两种方式工作 — 它可以启动(无限期运行)并允许绑定。这仅仅是您是否实现一些回调方法的问题:onStartCommand() 允许组件启动它,以及 onBind() 允许绑定。

无论您的服务是启动、绑定还是两者兼而有之,任何应用组件(即使是来自单独的应用)都可以以任何组件使用 Activity 的相同方式使用服务 — 通过使用 Intent 启动它。但是,您可以在清单文件中将服务声明为私有,并阻止其他应用访问。这在在清单中声明服务部分中有更多讨论。

选择服务还是线程

服务只是一个可以在后台运行的组件,即使在用户不与您的应用程序交互时也是如此,因此只有当您需要时才应创建服务。

如果您必须在主线程之外执行工作,但仅在用户与您的应用程序交互时,则应在另一个应用程序组件的上下文中创建新线程。例如,如果您想播放一些音乐,但仅在您的 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(),则该服务仅在组件绑定到它时运行。当所有客户端都解除绑定时,系统将销毁该服务。

Android 系统仅在内存不足且必须为具有用户焦点的 activity 回收系统资源时才停止服务。如果服务绑定到具有用户焦点的 activity,则它被终止的可能性较小;如果服务声明为在前台运行,则它很少被终止。如果服务已启动且长时间运行,系统会随着时间的推移降低其在后台任务列表中的位置,并且该服务极易被终止——如果您的服务已启动,您必须设计它以优雅地处理系统的重新启动。如果系统终止您的服务,它会在资源可用时立即重新启动它,但这还取决于您从 onStartCommand() 返回的值。有关系统何时可能销毁服务的更多信息,请参阅进程和线程文档。

在以下部分中,您将看到如何创建 startService()bindService() 服务方法,以及如何从其他应用组件中使用它们。

在清单中声明服务

您必须在应用程序的清单文件中声明所有服务,就像您为 activity 和其他组件所做的那样。

要声明您的服务,请将 <service> 元素添加为 <application> 元素的子元素。这是一个示例

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

有关在清单中声明服务的更多信息,请参阅 <service> 元素参考。

您可以将其他属性包含在 <service> 元素中,以定义属性,例如启动服务所需的权限以及服务应运行的进程。android:name 属性是唯一必需的属性——它指定了服务的类名。发布您的应用程序后,请保持此名称不变,以避免因依赖显式 intent 来启动或绑定服务而导致代码中断的风险(阅读博客文章:不可更改的事物)。

注意:为确保您的应用安全,在启动 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 可以启动一个配套服务,并通过将 intent 传递给 startService() 来将要保存的数据传递给它。服务在 onStartCommand() 中接收 intent,连接到 Internet,并执行数据库事务。当事务完成后,服务会停止自身并被销毁。

注意:服务默认在其声明它的应用程序的同一进程中,并在该应用程序的主线程中运行。如果您的服务在用户与同一应用程序的 activity 交互时执行密集或阻塞操作,则服务会降低 activity 的性能。为避免影响应用程序性能,请在服务中启动新线程。

Service 类是所有服务的基础类。当您扩展此类时,创建一个新线程来让服务完成所有工作非常重要;服务默认使用您应用程序的主线程,这可能会降低您应用程序正在运行的任何 activity 的性能。

Android 框架还提供 Service 的子类 IntentService,它使用工作线程一次处理所有启动请求。对于新应用,不建议使用此类,因为它从 Android 8 Oreo 开始引入后台执行限制后将无法很好地工作。此外,它从 Android 11 开始已弃用。您可以使用 JobIntentService 作为 IntentService 的替代方案,它与更新的 Android 版本兼容。

以下部分描述了如何实现您自己的自定义服务,但对于大多数用例,您应强烈考虑改用 WorkManager。请查阅Android 后台处理指南,以查看是否有适合您需求的解决方案。

扩展 Service 类

您可以扩展 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。相反,系统会使用 null intent 调用 onStartCommand(),除非有待启动服务的 intent。在这种情况下,这些 intent 会被传递。这适用于媒体播放器(或类似服务),它们不执行命令,而是无限期运行并等待作业。
START_REDELIVER_INTENT
如果系统在 onStartCommand() 返回后终止服务,则重新创建服务并使用传递给服务的最后一个 intent 调用 onStartCommand()。任何待处理的 intent 都会依次传递。这适用于正在积极执行应立即恢复的作业的服务,例如下载文件。

有关这些返回值的更多详细信息,请参阅每个常量的链接参考文档。

启动服务

您可以从 activity 或其他应用组件通过将 Intent 传递给 startService()startForegroundService() 来启动服务。Android 系统会调用服务的 onStartCommand() 方法,并向其传递 Intent,该 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,该 IBinder 定义了与服务通信的接口。其他应用组件然后可以调用 bindService() 来检索接口并开始在服务上调用方法。服务仅在其绑定到的应用组件存在时运行,因此当没有组件绑定到服务时,系统会销毁它。您无需以启动服务(通过 onStartCommand())的相同方式停止绑定服务。

要创建绑定服务,您必须定义指定客户端如何与服务通信的接口。服务和客户端之间的此接口必须是 IBinder 的实现,并且是您的服务必须从 onBind() 回调方法返回的内容。客户端收到 IBinder 后,即可通过该接口开始与服务交互。

多个客户端可以同时绑定到服务。当客户端完成与服务的交互后,它会调用 unbindService() 以解除绑定。当没有客户端绑定到服务时,系统会销毁服务。

实现绑定服务有多种方法,其实现比启动服务更复杂。因此,绑定服务的讨论出现在一个单独的文档中,即绑定服务

向用户发送通知

当服务运行时,它可以使用浮动通知状态栏通知来通知用户事件。

浮动通知是显示在当前窗口表面片刻后消失的消息。状态栏通知在状态栏中提供一个带有消息的图标,用户可以选择该图标以执行操作(例如启动 activity)。

通常,当后台工作(如文件下载)完成后,并且用户现在可以对其进行操作时,状态栏通知是最佳技术。当用户从展开视图中选择通知时,通知可以启动 activity(例如显示下载的文件)。

管理服务的生命周期

服务的生命周期比 activity 简单得多。然而,密切关注服务的创建和销毁方式更为重要,因为服务可以在后台运行而用户不知情。

服务的生命周期——从创建到销毁——可以遵循以下两种路径之一

  • 已启动服务

    当另一个组件调用 startService() 时,服务就会被创建。然后服务会无限期运行,并且必须通过调用 stopSelf() 来停止自身。另一个组件也可以通过调用 stopService() 来停止服务。当服务停止时,系统会销毁它。

  • 绑定服务

    当另一个组件(客户端)调用 bindService() 时,服务就会被创建。然后客户端通过 IBinder 接口与服务通信。客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到同一个服务,当所有客户端都解除绑定时,系统会销毁该服务。服务无需自行停止。

这两条路径并非完全独立。您可以绑定到已通过 startService() 启动的服务。例如,您可以通过调用 startService() 并传入标识要播放音乐的 Intent 来启动后台音乐服务。稍后,可能当用户想要控制播放器或获取当前歌曲信息时,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 fun onCreate() {
        // The service is being created
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // The service is starting, due to a call to startService()
        return startMode
    }

    override fun onBind(intent: Intent): IBinder? {
        // A client is binding to the service with bindService()
        return binder
    }

    override fun onUnbind(intent: Intent): Boolean {
        // All clients have unbound with unbindService()
        return allowRebind
    }

    override fun onRebind(intent: Intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }

    override fun onDestroy() {
        // 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 void onCreate() {
        // The service is being created
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // The service is starting, due to a call to startService()
        return startMode;
    }
    @Override
    public IBinder onBind(Intent intent) {
        // A client is binding to the service with bindService()
        return binder;
    }
    @Override
    public boolean onUnbind(Intent intent) {
        // All clients have unbound with unbindService()
        return allowRebind;
    }
    @Override
    public void onRebind(Intent intent) {
        // A client is binding to the service with bindService(),
        // after onUnbind() has already been called
    }
    @Override
    public void onDestroy() {
        // The service is no longer used and is being destroyed
    }
}

注意:与 activity 生命周期回调方法不同,您无需调用这些回调方法的超类实现。

图 2. 服务生命周期。左图显示了服务通过 startService() 创建时的生命周期,右图显示了服务通过 bindService() 创建时的生命周期。

图 2 说明了服务典型的回调方法。尽管图中将通过 startService() 创建的服务与通过 bindService() 创建的服务分开,但请记住,任何服务,无论其如何启动,都可能允许客户端绑定到它。最初通过 onStartCommand() 启动的服务(通过客户端调用 startService())仍然可以收到对 onBind() 的调用(当客户端调用 bindService() 时)。

通过实现这些方法,您可以监控服务生命周期的这两个嵌套循环

注意:尽管已启动的服务通过调用 stopSelf()stopService() 停止,但服务没有相应的回调(没有 onStop() 回调)。除非服务绑定到客户端,否则当服务停止时系统会销毁它——onDestroy() 是收到的唯一回调。

有关创建提供绑定服务的更多信息,请参阅绑定服务文档,其中包含有关管理绑定服务的生命周期部分中 onRebind() 回调方法的更多信息。