以下是三种不同的服务类型:
前台
前台服务执行一些用户能注意到的操作。例如,音频应用会使用前台服务来播放音频曲目。前台服务必须显示通知。即使用户停止与应用的交互,前台服务仍会继续运行。
后台
后台服务执行用户不会直接注意到的操作。例如,如果应用使用某个服务来压缩其存储空间,则此服务通常是后台服务。
注意:如果您的应用面向 API 级别 26 或更高版本,当应用本身未在前台运行时,系统会对运行后台服务施加限制。在诸如此类的大多数情况下,您的应用应改为使用计划作业。
绑定
当应用组件通过调用 绑定状态。绑定服务会提供客户端-服务器接口,以便组件与服务进行交互、发送请求、接收结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。仅当与另一个应用组件绑定时,绑定服务才会运行。多个组件可同时绑定到该服务,但全部取消绑定后,该服务即会被销毁。
虽然本文档分开概括讨论启动服务和绑定服务,但您的服务可同时以这两种方式运行,换言之,它既可以是启动服务(以无限期运行),亦支持绑定。唯一的问题在于您是否实现一组回调方法:
无论服务是处于启动状态还是绑定状态(或同时处于这两种状态),任何应用组件均可像使用 Activity 那样,通过调用 私有服务,并阻止其他应用访问该服务。使用清单文件声明服务部分将对此做更详尽的阐述。
注意:服务在其托管进程的主线程中运行,它既不创建自己的线程,也不在单独的进程中运行(除非另行指定)。如果服务将执行任何 CPU 密集型工作或阻止性操作(例如 MP3 播放或联网),则应通过在服务内创建新线程来完成这项工作。通过使用单独的线程,您可以降低发生“应用无响应”(ANR) 错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。
在服务和线程之间进行选择
简单地说,服务是一种即使用户未与应用交互也可在后台运行的组件,因此,只有在需要服务时才应创建服务。
如果您必须在主线程之外执行操作,但只在用户与您的应用交互时执行此操作,则应创建新线程。例如,如果您只是想在 Activity 运行的同时播放一些音乐,则可在 进程和线程文档。
请记住,如果您确实要使用服务,则默认情况下,它仍会在应用的主线程中运行,因此,如果服务执行的是密集型或阻止性操作,则您仍应在服务内创建新线程。
基础知识
如要创建服务,您必须创建
当另一个组件(如 Activity)请求启动服务时,系统会通过调用
当另一个组件想要与服务绑定(例如执行 RPC)时,系统会通过调用
首次创建服务时,系统会(在调用
当不再使用服务且准备将其销毁时,系统会调用此方法。服务应通过实现此方法来清理任何资源,如线程、注册的侦听器、接收器等。这是服务接收的最后一个调用。
如果组件通过调用
如果组件通过调用 未调用
只有在内存过低且必须回收系统资源以供拥有用户焦点的 Activity 使用时,Android 系统才会停止服务。如果将服务绑定到拥有用户焦点的 Activity,则它其不太可能会终止;如果将服务声明为在前台运行,则其几乎永远不会终止。如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升—如果服务是启动服务,则您必须将其设计为能够妥善处理系统执行的重启。如果系统终止服务,则其会在资源可用时立即重启服务,但这还取决于您从 进程和线程文档。
下文将介绍如何创建
使用清单文件声明服务
如同对 Activity 及其他组件的操作一样,您必须在应用的清单文件中声明所有服务。
如要声明服务,请添加 元素作为 元素的子元素。下面是示例:
...
...
如需了解有关使用清单文件声明服务的详细信息,请参阅 元素参考文档。
您还可在 元素中加入其他属性,以定义一些特性,如启动服务及其运行时所在进程需要的权限。android:name 属性是唯一必需的属性,用于指定服务的类名。发布应用后,请保此类名不变,以避免因依赖显式 Intent 来启动或绑定服务而破坏代码的风险(请阅读博文 Things That Cannot Change [不能更改的内容])。
注意:为确保应用的安全性,在启动
您可以通过添加 android:exported 属性并将其设置为 false,确保服务仅适用于您的应用。这可以有效阻止其他应用启动您的服务,即便在使用显式 Intent 时也如此。
注意:用户可以查看其设备上正在运行的服务。如果他们发现自己无法识别或信任的服务,则可以停止该服务。为避免用户意外停止您的服务,您需要在应用清单的 元素中添加 android:description。请在描述中用一个短句解释服务的作用及其提供的好处。
创建启动服务
服务启动后,其生命周期即独立于启动它的组件。即使系统已销毁启动服务的组件,该服务仍可在后台无限期地运行。因此,服务应在其工作完成时通过调用
应用组件(如 Activity)可通过调用
例如,假设某 Activity 需要将一些数据保存到在线数据库中。该 Activity 可以启动一个协同服务,并通过向
注意:默认情况下,服务与服务声明所在的应用运行于同一进程,并且运行于该应用的主线程中。如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。为避免影响应用性能,请在服务内启动新线程。
通常,您可以扩展两个类来创建启动服务:
这是适用于所有服务的基类。扩展此类时,您必须创建用于执行所有服务工作的新线程,因为服务默认使用应用的主线程,这会降低应用正在运行的任何 Activity 的性能。
这是
下文介绍如何使用其中任一类来实现服务。
扩展 IntentService 类
由于大多数启动服务无需同时处理多个请求(实际上,这种多线程情况可能很危险),因此最佳选择是利用
创建默认的工作线程,用于在应用的主线程外执行传递给
创建工作队列,用于将 Intent 逐一传递给
在处理完所有启动请求后停止服务,因此您永远不必调用
提供
如要完成客户端提供的工作,请实现
Kotlin
/**
* A constructor is required, and must call the super [android.app.IntentService.IntentService]
* constructor with a name for the worker thread.
*/
class HelloIntentService : IntentService("HelloIntentService") {
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
override fun onHandleIntent(intent: Intent?) {
// 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()
}
}
}Java
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// 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();
}
}
}
您只需要一个构造函数和一个
Kotlin
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show()
return super.onStartCommand(intent, flags, startId)
}Java
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
return super.onStartCommand(intent,flags,startId);
}
除
在下一部分中,您将了解如何在扩展
扩展服务类
借助
为进行比较,以下示例代码展示了
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();
}
}
如您所见,相较于使用
但是,由于
请注意,
如果系统在 不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
如果系统在 不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务,否则系统会调用包含空 Intent 的
如果系统在
如需详细了解这些返回值,请查阅每个常量的链接参考文档。有关此服务实现类型的扩展示例,请参阅 GitHub 上的 MessagingService 示例中的 MessagingService 类。
启动服务
您可以通过将
注意:如果您的应用面向 API 级别 26 或更高版本,除非应用本身在前台运行,否则系统不会对使用或创建后台服务施加限制。如果应用需要创建前台服务,则其应调用
例如,Activity 可以结合使用显式 Intent 与 HelloService):
Kotlin
Intent(this, HelloService::class.java).also { intent ->
startService(intent)
}Java
Intent intent = new Intent(this, HelloService.class);
startService(intent);
如果服务亦未提供绑定,则应用组件与服务间的唯一通信模式便是使用
多个服务启动请求会导致多次对服务的
停止服务
启动服务必须管理自己的生命周期。换言之,除非必须回收内存资源,否则系统不会停止或销毁服务,并且服务在
一旦请求使用
如果服务同时处理多个对 startId)。此外,如果服务在您能够调用
注意:为避免浪费系统资源和消耗电池电量,请确保应用在工作完成之后停止其服务。如有必要,其他组件可通过调用
如需了解有关服务生命周期的详细信息,请参阅下方管理服务生命周期的相关部分。
创建绑定服务
绑定服务允许应用组件通过调用 启动它。
如需与 Activity 和其他应用组件中的服务进行交互,或需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。
如要创建绑定服务,您需通过实现 不必像通过
如要创建绑定服务,您必须定义指定客户端如何与服务进行通信的接口。服务与客户端之间的这个接口必须是
多个客户端可以同时绑定到服务。完成与服务的交互后,客户端会通过调用
实现绑定服务有多种方法,并且此实现比启动服务更为复杂。出于这些原因,请参阅另一份文档绑定服务,了解关于绑定服务的详细介绍。
向用户发送通知
处于运行状态时,服务可以使用 Toast 通知或状态栏通知来通知用户所发生的事件。
Toast 通知是指仅在当前窗口界面显示片刻的消息。状态栏通知在状态栏中提供内含消息的图标,用户可通过选择该图标来执行操作(例如启动 Activity)。
通常,当某些后台工作(例如文件下载)已经完成,并且用户可对其进行操作时,状态栏通知是最佳方法。当用户从展开视图中选定通知时,该通知即可启动 Activity(例如显示已下载的文件)。
如需了解详细信息,请参阅 Toast 通知或状态栏通知开发者指南。
在前台运行服务
前台服务是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。前台服务必须为状态栏提供通知,将其放在运行中的标题下方。这意味着除非将服务停止或从前台移除,否则不能清除该通知。
注意:请限制应用使用前台服务。
只有当应用执行的任务需供用户查看(即使该任务未直接与应用交互)时,您才应使用前台服务。因此,前台服务必须显示优先级为 状态栏通知,这有助于确保用户知道应用正在执行的任务。如果某操作不是特别重要,因而您希望使用最低优先级通知,则可能不适合使用服务;相反,您可以考虑使用计划作业。
每个运行服务的应用都会给系统带来额外负担,从而消耗系统资源。如果应用尝试使用低优先级通知隐藏其服务,则可能会降低用户正在主动交互的应用的性能。因此,如果某个应用尝试运行拥有最低优先级通知的服务,则系统会在抽屉式通知栏的底部调用出该应用的行为。
例如,应将通过服务播放音乐的音乐播放器设置为在前台运行,因为用户会明确意识到其操作。状态栏中的通知可能表示正在播放的歌曲,并且其允许用户通过启动 Activity 与音乐播放器进行交互。同样,如果应用允许用户追踪其运行,则需通过前台服务来追踪用户的位置。
注意:如果应用面向 Android 9(API 级别 28)或更高版本并使用前台服务,则其必须请求 FOREGROUND_SERVICE 权限。这是一种普通权限,因此,系统会自动为请求权限的应用授予此权限。
如果面向 API 级别 28 或更高版本的应用试图创建前台服务但未请求 FOREGROUND_SERVICE,则系统会抛出 SecurityException。
如要请求让服务在前台运行,请调用
Kotlin
val pendingIntent: PendingIntent =
Intent(this, ExampleActivity::class.java).let { notificationIntent ->
PendingIntent.getActivity(this, 0, notificationIntent, 0)
}
val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build()
startForeground(ONGOING_NOTIFICATION_ID, notification)Java
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification =
new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
.setContentTitle(getText(R.string.notification_title))
.setContentText(getText(R.string.notification_message))
.setSmallIcon(R.drawable.icon)
.setContentIntent(pendingIntent)
.setTicker(getText(R.string.ticker_text))
.build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
注意:提供给
如要从前台移除服务,请调用 不会停止服务。但是,如果您在服务仍运行于前台时将其停止,则通知也会随之移除。
如需了解有关通知的详细信息,请参阅创建状态栏通知。
管理服务的生命周期
服务的生命周期比 Activity 的生命周期要简单得多。但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户未意识到的情况下运行于后台。
服务生命周期(从创建到销毁)可遵循以下任一路径:
启动服务
该服务在其他组件调用
绑定服务
该服务在其他组件(客户端)调用 不必自行停止运行。)
这两条路径并非完全独立。您可以绑定到已使用
实现生命周期回调
与 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 mStartMode
}
override fun mBinder
}
override fun mAllowRebind
}
override funJava
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 mStartMode;
}
@Override
public IBinder mBinder;
}
@Override
public boolean mAllowRebind;
}
@Override
public void
注意:与 Activity 生命周期回调方法不同,您不需要调用这些回调方法的超类实现。
图 2. 服务生命周期。左图显示使用
图 2 展示服务的典型回调方法。尽管该图分开介绍通过
通过实现这些方法,您可以监控服务生命周期的以下两种嵌套循环:
服务的整个生命周期贯穿调用
服务的活动生命周期从调用
对于启动服务,活动生命周期与整个生命周期会同时结束(即便是在
注意:尽管您需通过调用 onStop() 回调)。除非服务绑定到客户端,否则在服务停止时,系统会将其销毁(
如需详细了解创建提供绑定的服务,请参阅绑定服务文档,该文档的管理绑定服务的生命周期部分提供有关