三十五、服务:理论

如前所述,Android 服务适用于长期运行的流程,即使与任何活动分离,这些流程也可能需要保持运行。例如,即使播放器活动被垃圾收集,也可以播放音乐;轮询互联网上的 RSS/Atom 提要更新;即使聊天客户端由于来电而失去焦点,也可以保持在线聊天连接。

当手动启动(通过 API 调用)或者当某个活动试图通过进程间通信(IPC)连接到服务时,就会创建服务。服务将一直存在,直到明确关闭,或者直到 Android 迫切需要 RAM 并过早地破坏它们。然而,长时间运行是有成本的,所以服务需要小心,不要使用太多的 CPU 或让无线电在太多的时间里处于活动状态,以免服务导致设备的电池过快耗尽。

本章概述了创建和消费服务背后的基本理论。下一章将介绍一些特定的服务模式,这些模式可能非常符合您的特定需求。因此,本章只有有限的代码示例,而下一章提供了几个代码示例。

为什么是服务?

对于许多不需要直接访问活动用户界面的功能来说,服务是一把“瑞士军刀”,例如:

  • 执行即使用户离开应用的活动也需要继续的操作,例如长时间下载(例如,从 Android Market 下载应用)或播放音乐(例如,Android 音乐应用)
  • 执行需要存在的操作,而不管活动来来去去,例如维护聊天连接以支持聊天应用
  • 向远程 API 提供本地 API,例如可能由 web 服务提供的 API
  • 在没有用户干预的情况下执行定期工作,类似于 cron 作业或 Windows 计划任务

甚至像主屏幕应用小部件这样的东西也经常涉及一项服务,以帮助长时间运行的工作。

许多应用不需要任何服务。很少有应用需要一个以上。然而,服务是 Android 开发人员工具箱中的强大工具,任何合格的 Android 开发人员都应该熟悉它们的功能。

建立服务

创建服务实现与构建活动有许多共同的特征。您从 Android 提供的基类继承,覆盖一些生命周期方法,并通过清单将服务挂接到系统中。

服务等级

正如你的应用中的活动扩展了Activity或者 Android 提供的Activity子类,你的应用中的服务扩展了Service或者 Android 提供的Service子类。最常见的Service子类是IntentService,主要用于命令模式。也就是说,许多服务只是简单地扩展了Service

生命周期方法

正如活动有onCreate()onResume()onPause()和类似的方法一样,Service实现也有自己的生命周期方法,如下所示:

  • onCreate():与活动一样,在创建服务流程时通过任何方式调用
  • onStartCommand():每次通过startService()向服务发送命令时调用
  • onBind():每当客户端通过bindService()绑定到服务时调用
  • onDestroy():服务关闭时调用

与活动一样,服务在onCreate()中初始化它们需要的任何东西,并在onDestroy()中清理这些项目。和活动一样,如果 Android 终止了整个应用进程,比如紧急 RAM 回收,服务的onDestroy()方法可能不会被调用。

我们之前提供的关于活动因内存不足而突然终止的警告同样适用于服务。然而,Android 4.0 冰淇淋三明治引入了一种新方法onTrimMemory(),允许系统更好地处理低内存情况,特别是服务,让它们有机会释放未使用或不需要的资源,然后不得不求助于onDestroy()(下一章将详细介绍)。

onStartCommand()onBind()生命周期方法将基于您与客户端通信的选择来实现,这将在本章稍后解释。

舱单录入

最后,您需要将服务添加到您的AndroidManifest.xml文件中,以便它被识别为可用的服务。这只是添加一个<service >元素作为application元素的子元素,提供android:name来引用您的服务类。所以在下面的清单中,你会看到android:name="Downloader"

如果你想要求那些希望启动或绑定到服务的人的一些权限,添加一个android:permission属性来命名你正在授权的权限——更多细节参见第 38 章

例如,下面的清单显示了<service>元素:

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"![images](img/U002.jpg)  package="com.commonsware.android.downloader" android:versionCode="1"![images](img/U002.jpg)  android:versionName="1.0">   <uses-permission android:name="android.permission.INTERNET"/>   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>     <application android:label="@string/app_name" android:icon="@drawable/cw">         <activity android:name="DownloaderDemo" android:label="@string/app_name">             <intent-filter>                 <action android:name="android.intent.action.MAIN"/>                 <category android:name="android.intent.category.LAUNCHER"/>             </intent-filter>         </activity>         <serviceandroid:name="Downloader"/>     </application>   <supports-screens android:largeScreens="true" android:normalScreens="true"![images](img/U002.jpg)  android:smallScreens="true" android:anyDensity="true"/> </manifest>

与服务通信

服务的客户端——通常是活动,但不是必须的——有两种主要的方式向服务发送请求或信息。一种方法是发送命令,这不会创建与服务的持久连接。另一种方法是绑定到服务,建立一个双向的通信通道,只要客户端需要,这个通道就会一直存在。

使用 startService()发送命令

使用服务最简单的方法是调用startService()startService()方法接受一个Intent参数,就像startActivity()一样。事实上,提供给startService()Intent具有与startActivity()相同的两部分作用:

  • 确定要与之通信的服务
  • Intent extras 的形式提供参数,告诉服务它应该做什么

对于一个本地服务(本书的重点),最简单的Intent形式是标识实现Intent的类(例如,new Intent(this, MyService.class);)。

startService()的调用是异步的,所以客户端不会阻塞。如果服务还没有运行,它将被创建,并通过调用onStartCommand()生命周期方法来接收Intent。在onStartCommand()中,服务可以做任何它需要做的事情,但是因为onStartCommand()是在主应用线程上被调用的,所以它应该很快完成它的工作。任何可能需要一段时间的事情都应该委托给后台线程。

onStartCommand()方法可以返回几个值中的一个,主要是向 Android 指示如果服务的进程在运行时被终止会发生什么。最有可能的返回值如下:

  • START_STICKY:服务应该被移回启动状态(就像onStartCommand()被调用一样),但是Intent不应该被重新交付给onStartCommand()
  • START_REDELIVER_INTENT:应该通过调用onStartCommand()来重启服务,提供与这次交付的相同的Intent
  • START_NOT_STICKY:服务应该保持停止状态,直到被应用代码明确启动

默认情况下,调用startService()不仅发送命令,还告诉 Android 保持服务运行,直到有东西告诉它停止。停止服务的一种方法是调用stopService(),提供与startService()使用的相同的Intent,或者至少是等效的(例如,标识相同的类)。此时,服务将停止并被销毁。注意,stopService()没有使用任何类型的引用计数,所以对startService()的三次调用将导致一个服务运行,这个服务将被对stopService()的调用停止。

停止服务的另一种可能性是让服务自己调用stopSelf()。如果您使用startService()让一个服务开始运行并在后台线程上做一些工作,然后让服务在后台工作完成时自行停止,您可能会这样做。

与 bindService()绑定

绑定允许服务向绑定到它的活动(或其他服务)公开 API。当一个活动(或其他客户端)绑定到一个服务时,它主要是请求能够通过服务的“绑定器”访问由该服务公开的公共 API,正如服务的onBind()方法所返回的那样。当这样做时,活动还可以通过BIND_AUTO_CREATE标志指示 Android 自动启动服务,如果它还没有运行的话。

服务的绑定器通常是Binder的一个子类,你可以在上面放置任何你想向客户公开的方法。对于本地服务,您可以拥有任意多的方法,无论方法签名是什么(参数、返回类型等等)。)那你要的。该服务返回onBind()Binder子类的一个实例。

客户端调用bindService(),提供标识服务的Intent、表示绑定客户端的ServiceConnection对象和可选的BIND_AUTO_CREATE标志。与startService()一样,bindService()是异步的。在用onServiceConnected()调用ServiceConnection对象之前,客户端不会知道任何关于绑定状态的信息。这不仅表明绑定已经建立,而且对于本地服务,它提供了服务通过onBind()返回的Binder对象。此时,客户机可以使用Binder请求服务代表它完成工作。注意,如果服务还没有运行,并且您提供了BIND_AUTO_CREATE,那么在绑定到客户端之前,服务将首先被创建。如果跳过BIND_AUTO_CREATE,则bindService()将返回false,表明没有要绑定的现有服务。

最终,客户端将需要调用unbindService(),以表明它不再需要与服务通信。例如,一个活动可能在其onCreate()方法中调用bindService(),然后在其onDestroy()方法中调用unbindService()。对unbindService()的调用最终会触发对ServiceConnection对象调用onServiceDisconnected()——此时,客户端不再能够安全地使用Binder对象。

如果该服务没有其他绑定客户端,Android 也会关闭该服务,释放其内存。因此,我们不需要自己调用stopService()——如果需要,Android 会处理它,作为解除绑定的副作用。Android 4.0 还为bindService()引入了一个额外的可能参数,叫做BIND_ALLOW_OOM_MANAGEMENT。熟悉 OOM 的人都知道它是 out of memory 的缩写,许多操作系统使用“OOM 杀手”来选择进程进行销毁,以避免完全耗尽内存。使用BIND_ALLOW_OOM_MANAGEMENT绑定到服务表明您认为您的应用及其绑定的服务是非关键的,允许在出现低内存问题时更积极地考虑杀死它和停止相关的服务。

如果客户端是一个活动,那么需要采取两个重要步骤来确保绑定在配置更改(如屏幕旋转)后仍然有效:

  1. 不要在活动本身上调用bindService(),而是在ApplicationContext(通过getApplicationContext()获得)上调用bindService()
  2. 确保ServiceConnection从活动的旧实例转移到新实例,可能是通过onRetainNonConfigurationInstance()

这允许绑定在活动实例之间持续。

从服务中交流

当然,上一节中列出的方法只适用于调用服务的客户机。反过来也是经常需要的,因此服务可以让活动或某些东西知道异步事件。

回调/监听器对象

活动或其他服务客户端可以向服务提供某种回调或侦听器对象,然后服务可以在需要时调用这些对象。要实现这一点,您需要执行以下操作:

  1. 为监听器对象定义一个 Java 接口。
  2. 给服务一个公共 API 来注册和收回监听器。
  3. 让服务在适当的时候使用这些侦听器,通知那些注册了侦听器的人一些事件。
  4. 让活动注册并根据需要收回侦听器。
  5. 让活动以某种适当的方式响应基于侦听器的事件。

最大的问题是确保活动完成后收回侦听器。侦听器对象通常明确地(通过数据成员)或隐含地(通过实现为内部类)知道它们的活动。如果服务持有失效的监听器对象,相应的活动将在内存中逗留,即使 Android 不再使用这些活动。这代表了一个大的内存泄漏。您可能希望使用WeakReference s、SoftReference s 或类似的构造来确保如果一个活动被销毁,它向您的服务注册的任何侦听器都不会将该活动保存在内存中。

广播意图

第 21 章中第一次提到的另一种方法是让服务发送一个广播Intent,这个广播可以被活动接收到...假设活动仍然存在并且没有暂停。该服务可以调用sendBroadcast(),提供一个Intent来识别广播,设计为由BroadcastReceiver接收。如果在清单中注册了BroadcastReceiver,这可以是特定于组件的广播(例如new Intent(this, MyReceiver.class))。或者,它可以基于某个动作字符串,甚至可能是一个为第三方应用监听而记录和设计的字符串。

反过来,活动可以通过registerReceiver()注册一个BroadcastReceiver,尽管这种方法只适用于指定某些动作的Intent对象,而不适用于标识特定组件的对象。但是,当活动的BroadcastReceiver接收到广播时,它可以通知用户或者更新自己。

待定结果

您的活动可以调用createPendingResult()。这将返回一个PendingIntent,一个表示一个Intent的对象,以及要在那个Intent上执行的相应动作(例如,用它来启动一个活动)。在这种情况下,PendingIntent将导致一个结果被交付到您的活动的实现onActivityResult(),就好像另一个活动被startActivityForResult()调用,然后被调用setResult()发送回一个结果。

由于一个PendingIntentParcelable,因此可以放入一个额外的Intent,您的活动可以将这个PendingIntent传递给服务。反过来,服务可以在PendingIntent上调用几种send()方法中的一种,通知活动(通过onActivityResult())一个事件,甚至可能提供表示该事件的数据(以Intent的形式)。

信使

还有一种可能是使用一个Messenger对象。一个Messenger向一个活动的Handler发送消息。在一个单独的活动中,Handler可以用来给自己发送消息,如第 20 章中的所示。然而,在组件之间——比如在活动和服务之间——您将需要一个Messenger作为桥梁。

PendingIntent一样,MessengerParcelable,因此可以放入一个额外的Intent。调用startService()bindService()的活动会在Intent上附加一个Messenger作为额外的。服务将从Intent中获得Messenger。当需要提醒某个事件的活动时,该服务将执行以下操作:

  1. 调用Message.obtain()来获得一个空的Message对象。
  2. 根据需要用服务希望传递给活动的任何数据填充那个Message对象。
  3. Messenger上调用send(),提供Message作为参数。

然后,Handler将通过主应用线程上的handleMessage()接收消息,从而能够更新 UI 或做任何必要的事情。

通知

另一种方法是让服务让用户直接知道已经完成的工作。要做到这一点,服务可以引发一个Notification——在状态栏中放置一个图标,并可选地摇动、发出蜂鸣声或给出一些其他信号。这项技术在第 37 章中有所介绍。