服务

服务是一种可以在后台执行长期运行操作而不提供用户界面的应用程序组件。另外的某一个应用程序组件可以启动一个服务,之后该服务会一直运行,即使用户切换到了另外一个程序。此外,一个组件可以与服务绑定,与之交互,甚至进行进程间通信(IPC,interprocess communication)。例如,一个服务可以在后台完成处理网络事务、播放音乐、执行文件I/O操作,或是与一个内容提供者(content provider)交互等工作。

服务通常有两种存在形式:

启动的(started)

一个服务在某个应用程序组件(例如活动)通过调用startService()启动它之后成为“启动的”。一旦被启动,服务可以永远运行于后台,即使启动它的组件本身已被销毁。通常,一个启动的服务将执行单一的操作且不返回其调用者结果。例如,它可以通过网络下载或上传一个文件。当操作完成后,服务应当能结束自己。

绑定的(bound)

一个服务在某个应用程序组件通过调用bindService()绑定它之后成为“绑定的”。一个绑定的服务将提供一个客户端-服务器接口,允许组件与之交互,发送请求,接收结果,甚至通过IPC执行跨进程工作。一个绑定的服务将在另一个应用程序组件与之绑定时运行。多个组件可以同时绑定单个服务,不过当所有的组件都解除绑定时,该服务将会销毁。

尽管本文档原则上将分开讨论这两种类型的服务,一个服务可以同时以这两种形式存在——它可以被启动(而永久运行下去)同时也允许被绑定。这一切只不过取决于是否使用了回馈方法onStartCommand()允许组件启动服务并且还使用了onBind()允许绑定。

无论程序是启动的、绑定的,或者两者都是,任何应用程序都可以像使用一个活动那样使用一个服务——即通过Intent来启用。不过,服务可以在manifest文件中被声明为私有的,以阻断其他程序对其的读取。这点将在“在manifest中声明服务”一节中更加详细地进行讨论。

注意:一个服务运行于其宿主进程的主线程中——服务不会创建其自有的线程,也不会运行于另外的进程(除非你特别指定)。这意味着,如果一个服务要执行CPU高占用工作或是阻断性的操作(如MP3播放或是网络任务),就应该新建一个线程令服务在其中工作。使用另外的线程将减低程序不响应(ANR,Application Not Responding)的风险,使得程序的主线程中的活动对用户交互保持流畅。

基本内容

应该使用服务还是线程?

服务只是一个在用户没有与程序进行交互时也能于后台运行的组件而已。因此,只应当在必要时才创建一个服务。

如果仅仅需要在用户与程序进行交互时于主线程之外执行工作,应当创建一个新的线程而不是一个服务。例如,如果希望在程序正在运行时播放音乐,可以在onCreate()中创建一个线程,在onStart()中运行它,之后在onStop()中停止它。还可以考虑使用AsyncTask或是HandlerThread来取代传统的Thread类。关于线程的更多信息请参见“进程和线程”文档。

需要记得的是,如果使用了一个服务,它默认是运行于程序的主线程。因此应当在需要执行密集或是阻塞性的操作时为服务另外创建一个线程。

要创建一个服务,就必须创建一个Service(或它的一个现有子类)的子类。在实现时,需要正确覆盖一些控制服务的生命周期关键方面并且提供了与其他组件相绑定的机制的回馈方法。最为重要的一些需要被覆盖的方法有:

onStartCommand()

系统在另一个组件,例如一个活动,通过调用startService()来请求启动服务时将调用这个方法。一旦这个方法被执行,服务将被启动并永久地运行于后台。如果使用了它,就必须负责在工作完成后通过调用stopSelf()或stopService()来终止服务。(如果只是想要提供绑定,不需要使用这个方法。)

onBind()

系统在另一个组件要通过调用bindService()来与服务相绑定(例如执行RPC)时将调用这个方法。在使用这个方法时,必须提供一个接口供客户端返回IBinder来与服务通信。这个方法总是需要被使用,不过要是不允许绑定的话,则应当返回null值。

onCreate()

系统在服务初次被创建时将调用这个方法,来执行一次性设置步骤(这会在调用onStartCommand()或onBind()之前)。如果服务已经在运行,这个方法则不会被调用。

onDestroy()

系统将在服务不再被使用,即将被销毁时调用该方法。服务应当使用它来清除线程、已注册的监听器(registered listener)、接收者(receiver)等资源。这将是最后一次调用服务接收者。

如果一个组件通过调用startService()(这将会调用onStartCommand())来启动一个服务,服务会不停地运行直至有stopSelf()来中止其自身或是有另一个组件通过调用stopService()来中止它。

如果一个组件调用bindService()来创建服务(此时onStartCommand()不会被调用),那么服务只会在组件与之绑定时运行。一旦服务与所有的客户端解除绑定,系统就会销毁它。

Android系统会在内存低时强制终止一个服务,因为它必须保有处于用户焦点的活动的系统资源。如果一个服务与用户焦点下的活动相绑定,那就不太容易被杀除。如果该服务被声明于前台运行(之后会讨论该情况),那它就几乎不可能被杀除了。反过来说,如果服务是启动的并且长期运行,系统将会在一段时间后降低其在后台任务列表中的位置,这样服务会变得较为可能被杀除——如果这个服务是启动的,那就必须做好精心设计以使系统能够重新启动它。如果系统杀除了这个服务,它将在资源可用后马上再次启动(尽管如后面将要提到的那样,这也取决于onStartCommand()所返回的值)。关于系统如何销毁服务的更多信息,参阅“进程和线程”文档。

在下面的小节中,将能看到如何创建各种类型的服务以及如何在其他应用程序组件中使用它们。

在manifest中声明一个服务

就像活动(和其他组件)一样,必须在程序的manifest文件中声明所有的服务。

要声明一个服务,添加<service>作为<application>元素的子元素。例如:

在<service>元素中可以包含其他的一些属性以定义其特性,例如启动服务所需的许可和服务所要运行于的进程等。更多信息请参见<service>元素的参考文档。

正如活动一样,一个服务可以定义其意图过滤器以允许其他组件使用非显式的意图来激活它。通过声明意图过滤器,用户设备上所安装的其他程序中的组件可以在服务声明的意图过滤器与该程序传递给startService()的意图相匹配时启动这个服务。

如果只打算在本地使用服务(其他的程序不会使用该服务),那就不必(而且不应该)提供任何意图过滤器。没有任何意图过滤器时,必须使用一个显式地标明了服务类的名称的意图来启动该服务。关于启动一个服务的更多信息将在之后讨论。

此外,当包含了android:exported属性且将其设置为“false”后就可以确保该服务是本程序私有的。这种做法在服务支持意图过滤器时将会很有效。

关于为服务创建意图过滤器的更多信息,参见“意图和意图过滤器”文档。

创建一个启动的服务

一个启动的服务是由另一个组件通过调用startService()来启动的,这也将会调用该服务的onStartCommand()方法。

当一个服务是启动的之时,它拥有一个和启动它的组件不相关的生命周期,因此这个服务可以在后台无限期地运行,即使那个启动它的组件被销毁。因此,这个服务应当在工作完成后通过调用stopSelf()来终止自己,或者被别的组件调用stopService()来中止。

诸如活动这样的应用程序组件可以通过调用startService()并传递指向该服务的意图以及服务所需的数据来启动一个服务。这个服务将在onStartCommand()方法中接收这个Intent。

比如说,假定有一个活动需要在一个在线数据库中保存一些信息。这个活动可以启动一个协同服务,将需要保存的数据通过一个意图传递给startService()。该服务会在onStartCommand()中接收这个意图,与因特网连接并执行数据库事务。当事务完成后,服务将终止自身并销毁。

注意:服务与声明它的程序在同一个进程中,且默认运行于同一个线程。因此,如果服务要在用户和该程序的一个活动进行交互时执行密集型或阻塞性的操作,就会降低活动的性能表现。要避免这样的程序性能下降,就应当为服务新建一个线程。

一般来说,有两个类可以继承以创建一个启动的服务:

Service

这是所有服务的基类。在继承这个类之后,由于服务默认运行于程序的主线程而会降低程序内正在运行的活动的性能表现,因此必须创建一个新的线程供服务运行。

IntentService

这是Service的一个子类,使用一个工作线程(worker thread)来依次处理所有的请求。如果服务不需要同时处理多个请求的话,这将是最好的选择。只需要使用onHandleIntent()来接收每一个启动请求便可以在后台完成工作。

下面的小节将介绍如何利用这两个类来实现一个服务。

继承IntentService类

由于大部分启动的服务不需要同时处理多个请求(这将是一种危险的多线程情况),通过IntentService类来实现一个服务可说是最佳选择。

IntentService将会做一下工作:

  • 创建一个不同于程序主线程的默认工作线程以执行所有传送至onStartCommand()的意图。
  • 创建一个工作队列以依次传递意图至onHandleIntent(),这样就不必再担心多线程问题。
  • 在所有请求被处理后终止服务,这样就不必再调用stopSelf()。
  • 提供一个返回null值的onBind()默认实现。
  • 提供一个能发送意图至工作队列的onStartCommand()默认实现(意图之后将被传送至onHandleIntent())。

这样一来就只需实现onHandleIntent()以执行客户端提供的工作即可。(不过,仍然需要为服务提供一个简单的构造函数。)

这里有一个使用IntentService的例子:

所有需要的就只是一个构造函数以及一个onHandleIntent()。

如果还要覆盖其他的回馈方法,比如onCreate(),onStartCommand()或是onDestroy(),就必须要调用其父类的实现,这样IntentService才能正确处理工作线程的生命周期。

例如,onStartCommand()必须返回一个默认的实现(就像被意图获取并传递给onHandleIntent()的那样):

除了onHandleIntent(),仅有的另一个不必调用其父类的方法是onBind()(不过这个方法只有在服务允许绑定时才会被使用)。

下一节中将会介绍在继承Service基类时相同类型的服务是如何被使用的。代码会稍有些长,不过在处理同步开始的请求的情况下很有用。

继承Service类

正如在之前的小节中所见到的,使用IntentService可以很轻松地实现一个启动的服务。不过,如果需要一个服务可以多线程工作(而不是通过工作队列处理启动请求),就要继承Service类来处理每一个意图。

为了做对比,下面的范例代码中Service类的实现和之前的IntentService将执行相同的工作。也就是说,对于每一个启动请求,将依次一个个地使用一个工作线程来执行任务:

可以看到,这比使用IntentService要复杂许多。

不过,因为是由自己来处理每一个onStartCommand()的调用而可以同时执行多个请求。本范例并没有这么做,但是在需要的情况下可以为每一个请求创建新的线程来运行(而不需要等待前一个请求完成)。

注意,onStartCommand()方法必须返回一个整型数。该整型数是用以描述系统应当如何在要杀除该服务时进行操作的值(就像之前所讨论的,IntentService的默认实现已经处理了这个问题,当然也可以修改其行为)。onStartCommand()的返回值必须是以下常量之一:

START_NOT_STICKY

系统在onStartCommand()返回后杀除服务,且不重建该服务(除非有未处理的意图要传递)。要避免服务在不需要时或是程序可以轻松地重启任何未完成的工作时运行,这将是最安全的选项。

START_STICKY

系统在onStartCommand()返回后杀除服务,重建该服务并调用onStartCommand()。不过系统不会重新传递最后一个意图,而是以一个空(null)意图调用onStartCommand()(除非有未处理的意图要启动服务。这种情况下,那些意图将被传递)。这个选项适用于媒体播放器之类的不执行指令但是要永远运行以等待任务的服务。

START_REDELIVER_INTENT

系统在onStartCommand()返回后杀除服务,以最后传递给服务的那个意图来重建该服务并调用onStartCommand()。所有未处理的意图将被依次传递。这适用于主动执行那些需要迅速被继续的工作(例如下载文件)之类的服务。

关于这些返回值的更多信息,请参见每一个常量的链接参考文档。

启动一个服务

通过向startService()传递一个Intent(来指定要启动的服务),可以在一个活动或是其他的应用程序组件中启动一个服务。Android系统将调用服务的onStartCommand()方法并将该Intent传递给它。(不应当直接调用onStartCommand()。)

例如,一个活动可以显式地使用意图和startService()来启动前面一节的范例服务(helloService):

startService()方法会立即返回,之后Android系统将调用服务的onStartCommand()方法。如果服务没有正在运行,系统将先调用onCreate(),然后再调用onStartCommand()。

如果服务没有提供绑定,那么传递给startService()的意图就成为应用程序组件和服务之间唯一的通信模式。不过,如果希望服务回送一个结果,那么可以让启动了这个服务的客户端创建一个PendingIntent(配合getBroadcast())来广播,之后将其传递给启动了服务的Intent内的这个服务。这样这个服务就可以用广播来传送结果。

多个服务启动请求会引起服务的onStartCommand()方法多次响应调用。不过,要终止一个服务则只需要一个请求(配合stopSelf()或stopService())。

终止一个服务

一个启动的服务必须自己管理自己的生命周期。也就是说,除非系统内存不足并且服务在onStartCommand()返回后继续运行,否则系统不会终止或是销毁该服务。所以,服务必须通过调用stopSelf()来自己终止自己,或者让另一个组件调用stopService()来终止它。

一旦用stopSelf()或是stopService()来请求终止,系统会立即销毁这个服务。

不过,如果服务要同时处理多个onStartCommand()请求,那就不应该终止这个服务,因为可能还接收到了一个新的启动请求(在一个请求的最后终止这个服务也将会终止后一个请求)。要避免这个问题,可以使用stopSelf(int)来确保终止服务的请求是总之基于最后接受的启动请求的。也就是说,当调用stopSelf(int)时,需要将终止请求相对应的启动请求的ID(即传递给onStartCommand()的startID)传入。这样如果服务在调用stopSelf(int)之前接收到了一个新的启动请求,ID就会不符合,服务便不会被终止。

注意:程序要在服务不工作时终止它是很重要的。这样以避免浪费系统资源及消耗电力。如果必要,可以让其他组件通过调用stopService()来终止服务。即使允许了服务的绑定,也必须总是在服务接收过onStartCommand()的调用时终止服务。(抱歉这句的翻译有点问题,请参考原文 Even if you enable binding for the service, you must always stop the service yourself if it ever received a call to onStartCommand().)

关于服务生命周期的更多信息,请参见之后关于“管理服务生命周期”的小节。

创建绑定的服务

绑定的服务允许应用程序组件通过调用bindService()与之绑定来建立一个长期连接(并且通常不允许组件通过调用startService()来启动它)。

应当在希望通过程序内的活动或其他组件与服务交互,或者是以进程间通信(interprocess communication,IPC)让本程序的部分功能开放给其他程序时,创建绑定的服务。

要创建一个绑定的服务,必须使用onBind()回馈方法来返回一个定义了与服务通信的接口的IBinder。这样其他应用程序组件可以调用bindService()来检索这个接口并调用服务的方法。该服务只为了与之绑定的应用程序组件而存在,因此当没有组件与之绑定时,系统将销毁它(不需要像通过onStartCommand()启动一个服务时那样来终止一个绑定的服务)。

要创建一个绑定的服务,要做的第一件事是定义一个指定了客户端如何与服务通信的接口。这个服务与客户端之间的接口必须是IBinder的一种实现,并且必须在服务的onBind()回馈方法中被返回。一旦客户端接收到了IBinder,它将能够通过这个接口开始与服务交互。

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

有多种方法来实现一个绑定的服务,它们比实现一个启动的服务要复杂得多,因此将在一篇独立的文档中来讨论关于绑定的服务的问题。

向用户发送通知

一个服务一旦运行,它就可以通过Toast通知或是状态栏通知来提示用户当前事件。

Toast通知是一种在当前窗口表层出现一段时间后便会消失的消息,而状态栏通知则在状态栏显示一个带有消息的图标,用户可以选择以进行处理(例如启动一个活动)。

通常, 状态栏通知是后台工作完成时(例如文件下载完成)最佳的通知方式,用户可以对完成的工作进行处理。当用户从扩展视图中选择该通知,它就会启动一个服务(比如来查看下载的文件)。

参见Toast通知或是状态栏通知的开发指南以获取更多信息。

在前台运行服务

前台服务是一种用户能够意识到的服务,且不会再内存不足时被系统杀除。一个前台服务必须提供状态栏通知并归于“正在运行”标题之下,这意味着除非该服务被终止或是移出前台,它将一直存在于通知窗口中。

例如,一个通过服务来播放音乐的音乐播放器应当被设置为运行于前台,因为用户将明确地意识到它正在运行。状态栏中的通知应该显示当前歌曲,并允许用户由此通知启动一个活动来与音乐播放器交互。

要请求服务运行于前台,需要调用startForeground()。该方法使用两个参数:一个唯一识别了通知的整型数以及一个状态栏的Notification。例如:

要把服务从前台移出,需要调用stopForeground()。该方法需要一个布尔量来确定是否需要同时移除状态栏通知。这个方法并不会终止服务。不过,如果终止了仍在前台运行的服务,那么通知也将会被一同移除。

注意:startForegound()和stopForeground()方法是在Android 2.0(API Level 5)中被引入的。为了在较早版本的平台上使服务在前台运行,比如使用较早的setForeground()方法——参见startForegound()文档以获取关于提供向下兼容性的更多信息。

要获取关于通知的更多信息,请参见“创建状态栏通知”。

管理服务的生命周期

服务的生命周期要比活动的简单许多。不过,需要更加地注意一个服务是怎样被创建和销毁的,因为服务可以不被用户发觉地在后台运行。

服务生命周期——从它被创建直到被销毁——可以沿着两条不同的路线发展:

  • 一个启动的服务
    • 服务会在另一个组件调用startService()时被创建。之后服务将会永久运行,必须调用stopSelf()来终止它自己。也可以由另一个组件调用stopService()来终止服务。当服务被终止后,系统将销毁它。
  • 一个绑定的服务
    • 服务会在另一个组件(客户端)调用bindService()时被创建。客户端之后通过一个IBinder接口与服务通信。客户端可以通过调用unbindService()来关闭连接。多个客户端可以与同一个服务绑定。当所有客户端都与之解除绑定后,系统将销毁这个服务。(服务不必终止自己。)

这两条路线并不是完全独立的。也就是说,可以与一个通过startService()启动的服务相绑定。例如,一个后台音乐服务可以通过以一个指定了所要播放的音乐的Intent来调用startService()启动。之后,当用户想要控制音乐播放或是获取当前音乐的信息时,可以通过调用bindService()来将其与一个活动相绑定。在这样的情况下,直到所有的客户端与之解除绑定之前,stopService()或是stopSelf()并不会真正终止这个服务。

使用生命周期回馈

和活动一样,服务也有生命周期回馈方法。可以使用它们来监视服务的状态的改变以在恰当的情况下做出恰当的动作。下面的一个服务的框架演示了每一种生命周期方法:

图2. 服务的生命周期。左边的图表展示了由startService()创建的服务的生命周期,右边的图标则是由bindService()创建的服务的生命周期。

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

通过使用这些方法,可以监视服务生命周期中的两个嵌套循环:

  • 一个服务的整个生命期从onCreate()开始,至onDestroy()返回时结束。和活动一样,一个服务在onCreate()中进行初始化工作并在onDestroy()中释放所有剩余资源。例如,一个音乐播放服务在onCreate()中创建音乐播放线程,在onDestroy()中终止线程。onCreate()和onDestroy()方法能被所有的服务调用,不论它是由startService()还是bindService()创建的。
  • 一个服务的活动生命期(active lifetime)从onStartCommand()或是onBind()的调用开始。这两种方法将分别处理传递给startService()或是bindService()的Intent。如果服务是启动的(started),则活动生命期的结束和整个生命期相同(服务在onStartCommand()返回后依然是活动的)。如果服务是绑定的(bound),那么活动生命期将会在onUnbind()返回时结束。

注意:虽然一个启动的服务在调用stopSelf()或stopService()时终止,但对此却没有一个专门的回馈方法(没有onStop()回馈方法)。所以,除非服务与客户端相绑定,否则系统将会在服务终止时销毁它——onDestroy()是唯一会被接收的回馈方法。

图2描绘了一个服务的典型回馈方法。尽管它区分了通过startService()和bindService()创建的服务,要注意任何服务,无论是如何被创建的,都允许被客户端绑定。因此,一个最初由onStartCommand()启动的服务(由一个客户端调用startService())也可以接收onBind()调用(在客户端调用bindService()时)。

关于创建可以绑定的服务的更多信息,请参见“绑定的服务”文档。其中包含了“管理绑定的服务的生命周期”一节中onRebind()回馈方法的更多信息。

本页部分内容根据Android Open Source Project创作并共享的内容修改,并在知识共享 署名2.5许可协议中所述条款的限制下使用。

发表评论

电子邮件地址不会被公开。 必填项已用*标注