服务概述

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

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

服务类型

以下是三种不同类型的服务

前台服务

前台服务执行某些用户注意到的操作。例如,音频应用将使用前台服务播放音频曲目。前台服务必须显示 通知。即使用户未与应用交互,前台服务也会继续运行。

当您使用前台服务时,必须显示通知,以便用户能够积极地意识到服务正在运行。除非服务已停止或从前台移除,否则此通知无法被解除。

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

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

后台服务
后台服务执行用户没有直接注意到的操作。例如,如果应用使用服务压缩其存储,则这通常是后台服务。

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

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

尽管此文档通常会分别讨论已启动的服务和绑定服务,但您的服务可以同时使用这两种方式——它可以启动(无限期运行)并允许绑定。这仅仅取决于您是否实现了几个回调方法:onStartCommand() 以允许组件启动它,以及 onBind() 以允许绑定。

无论您的服务是启动、绑定还是同时启动和绑定,任何应用程序组件都可以像任何组件使用活动一样使用服务(即使来自单独的应用程序)——通过使用Intent启动它。但是,您可以在清单文件中将服务声明为私有并阻止其他应用程序访问。这将在关于在清单中声明服务的部分中详细讨论。

选择服务还是线程

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

如果您必须在主线程之外执行工作,但仅在用户与您的应用程序交互时执行,则应改为在另一个应用程序组件的上下文中创建一个新线程。例如,如果您想播放一些音乐,但仅在您的活动正在运行时播放,您可以在onCreate()中创建一个线程,在onStart()中开始运行它,并在onStop()中停止它。还可以考虑使用来自java.util.concurrent包的线程池和执行器或Kotlin协程,而不是传统的Thread类。有关将执行移至后台线程的更多信息,请参阅Android上的线程文档。

请记住,如果您确实使用了服务,它默认情况下仍然在应用程序的主线程中运行,因此如果它执行密集型或阻塞操作,您仍然应该在服务中创建一个新线程。

基础知识

要创建服务,您必须创建Service的子类或使用其现有的子类之一。在您的实现中,您必须覆盖一些处理服务生命周期关键方面的回调方法,并提供一种机制,允许组件在适当的情况下绑定到服务。这些是您应该覆盖的最重要的回调方法

onStartCommand()
当另一个组件(例如活动)请求启动服务时,系统通过调用startService()来调用此方法。当此方法执行时,服务将启动并可以无限期地在后台运行。如果您实现了此方法,则您有责任在服务工作完成后通过调用stopSelf()stopService()来停止服务。如果您只想提供绑定,则无需实现此方法。
onBind()
当另一个组件想要与服务绑定(例如执行RPC)时,系统通过调用bindService()来调用此方法。在您的此方法实现中,您必须通过返回IBinder来提供客户端用于与服务通信的接口。您必须始终实现此方法;但是,如果您不想允许绑定,则应返回null。
onCreate()
当服务最初创建时(在它调用onStartCommand()onBind()之前),系统会调用此方法来执行一次性设置过程。如果服务已在运行,则不会调用此方法。
onDestroy()
当服务不再使用并即将被销毁时,系统会调用此方法。您的服务应该实现此方法来清理任何资源,例如线程、已注册的侦听器或接收器。这是服务收到的最后一个调用。

如果组件通过调用startService()启动服务(这会导致调用onStartCommand()),则服务将继续运行,直到它使用stopSelf()自行停止或另一个组件通过调用stopService()停止它。

如果组件调用bindService()来创建服务并且调用onStartCommand(),则服务仅在组件绑定到它的时间内运行。在服务从所有客户端解除绑定后,系统会销毁它。

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

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

在清单中声明服务

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

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

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

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

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

注意:为了确保您的应用安全,在启动Service时始终使用显式意图,并且不要为您的服务声明意图过滤器。使用隐式意图启动服务是一种安全隐患,因为您无法确定响应意图的服务,并且用户也无法看到哪个服务启动。从 Android 5.0(API 级别 21)开始,如果您使用隐式意图调用bindService(),系统会抛出异常。

您可以通过包含android:exported属性并将其设置为false来确保您的服务仅对您的应用可用。这有效地阻止了其他应用启动您的服务,即使使用显式意图也是如此。

注意:用户可以看到其设备上正在运行的服务。如果他们看到一个他们不认识或不信任的服务,他们可以停止该服务。为了避免您的服务被用户意外停止,您需要在应用清单中的<service>元素中添加android:description属性。在说明中,提供一个简短的句子来解释服务的用途及其提供的益处。

创建启动的服务

启动的服务是指另一个组件通过调用startService()启动的服务,这会导致调用服务的onStartCommand()方法。

当服务启动时,它具有独立于启动它的组件的生命周期。即使启动它的组件被销毁,服务也可以无限期地在后台运行。因此,服务应该在完成其工作后自行停止,方法是调用stopSelf(),或者另一个组件可以通过调用stopService()来停止它。

应用程序组件(例如活动)可以通过调用startService()并传递一个Intent来启动服务,该Intent指定服务并包含服务要使用的任何数据。服务在onStartCommand()方法中接收此Intent

例如,假设某个活动需要将一些数据保存到在线数据库中。活动可以启动一个配套服务并向其传递要保存的数据,方法是将意图传递给startService()。服务在onStartCommand()中接收意图,连接到互联网并执行数据库事务。事务完成后,服务自行停止并被销毁。

注意:服务默认情况下在声明它的应用程序的同一进程以及该应用程序的主线程中运行。如果您的服务在用户与来自同一应用程序的活动交互时执行密集型或阻塞操作,则服务会降低活动性能。为了避免影响应用程序性能,请在服务内部启动一个新线程。

Service 类是所有服务的基类。当您扩展此类时,务必创建一个新线程,以便服务能够在其中完成其所有工作;默认情况下,服务使用您的应用程序的主线程,这可能会降低应用程序正在运行的任何活动的性能。

Android 框架还提供了 IntentService,它是 Service 的子类,它使用工作线程按顺序逐一处理所有启动请求。**不建议**在新应用中使用此类,因为它在 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() 返回后终止服务,请勿重新创建服务,除非有待传递的 PendingIntent。这是最安全的选项,可以避免在不需要时运行服务,以及当您的应用程序可以简单地重新启动任何未完成的作业时。
START_STICKY
如果系统在 onStartCommand() 返回后终止服务,请重新创建服务并调用 onStartCommand(),但请勿重新传递最后一个 Intent。相反,系统会使用空 Intent 调用 onStartCommand(),除非有待启动服务的 PendingIntent。在这种情况下,将传递这些 Intent。这适用于媒体播放器(或类似服务),这些服务不是执行命令,而是无限期地运行并等待作业。
START_REDELIVER_INTENT
如果系统在 onStartCommand() 返回后终止服务,请重新创建服务并使用传递给服务的最后一个 Intent 调用 onStartCommand()。任何待处理的 Intent 将依次传递。这适用于正在积极执行应立即恢复的作业(例如下载文件)的服务。

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

启动服务

您可以通过将 Intent 传递给 startService()startForegroundService() 从 Activity 或其他应用程序组件启动服务。Android 系统会调用服务的 onStartCommand() 方法并将其传递给 Intent,该 Intent 指定要启动的服务。

注意:如果您的应用的目标 API 级别为 26 或更高,则系统会对使用或创建后台服务施加限制,除非应用本身位于前台。如果应用需要创建前台服务,则应用应调用 startForegroundService()。该方法创建后台服务,但该方法会向系统发出信号,表明服务将提升自身到前台。创建服务后,服务必须在五秒钟内调用其 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),您的停止请求与此 ID 对应。然后,如果在您能够调用 stopSelf(int) 之前服务收到了新的启动请求,则 ID 不匹配,服务不会停止。

注意:为了避免浪费系统资源和消耗电池电量,请确保您的应用程序在完成工作后停止其服务。如有必要,其他组件可以通过调用 stopService() 停止服务。即使您为服务启用了绑定,如果服务曾经接收到对 onStartCommand() 的调用,也必须始终自行停止服务。

有关服务生命周期的更多信息,请参阅下面有关 管理服务生命周期 的部分。

创建绑定服务

绑定服务是一种允许应用程序组件通过调用 bindService() 来绑定到它以创建长期连接的服务。它通常不允许组件通过调用 startService()启动它。

当您希望从 Activity 和应用程序中的其他组件与服务交互,或通过进程间通信 (IPC) 向其他应用程序公开应用程序的一些功能时,请创建绑定服务。

要创建绑定服务,请实现 onBind() 回调方法以返回 IBinder,该回调方法定义了与服务通信的接口。然后,其他应用程序组件可以调用 bindService() 以检索接口并开始在服务上调用方法。服务仅存在于服务绑定到的应用程序组件,因此当没有组件绑定到服务时,系统会销毁它。您无需以与通过 onStartCommand() 启动服务时相同的方式停止绑定服务。

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

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

实现绑定服务有多种方法,并且实现比启动服务更复杂。由于这些原因,绑定服务的讨论出现在有关 绑定服务 的单独文档中。

向用户发送通知

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

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

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

管理服务的生命周期

服务的生命周期比活动的生命周期简单得多。但是,更重要的是要注意服务的创建和销毁方式,因为服务可以在后台运行,而用户并不知情。

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

  • 启动的服务

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

  • 绑定服务

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

这两种路径并非完全独立。您可以绑定到已经使用startService()启动的服务。例如,您可以通过调用startService()并使用一个Intent来标识要播放的音乐,从而启动后台音乐服务。稍后,可能当用户想要对播放器进行一些控制或获取有关当前歌曲的信息时,活动可以通过调用bindService()来绑定到服务。在这种情况下,stopService()stopSelf()实际上不会停止服务,直到所有客户端都解除绑定。

实现生命周期回调

与活动类似,服务具有生命周期回调方法,您可以实现这些方法来监视服务状态的变化并在适当的时间执行工作。以下骨架服务演示了每个生命周期方法

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
    }
}

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

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

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

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

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

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