Services
服务是一种应用程序组件,可以在后台执行长时间运行的操作,但不提供用户界面。另一个应用程序组件可以启动服务,即使用户切换到另一个应用程序,它也将继续在后台运行。此外,组件可以绑定到服务以与其交互,甚至可以执行进程间通信(IPC)。例如,服务可以从后台处理网络事务,播放音乐,执行文件I / O或与内容提供者交互。
服务基本上可以采用两种形式:
Started
当应用程序组件(例如Activity)通过调用startService()启动它时,服务“启动”。一旦启动,服务可以无限期地在后台运行,即使启动它的组件被销毁。通常,启动的服务执行单个操作,并且不会将结果返回给调用者。例如,它可能通过网络下载或上载文件。操作完成后,服务应自行停止。
Bound
当应用程序组件通过调用bindService()绑定到它时,服务被“绑定”。绑定服务提供客户端 - 服务器接口,允许组件与服务交互,发送请求,获取结果,甚至跨进程通信(IPC)进程。只要绑定了另一个应用程序组件,绑定服务就会运行。多个组件可以立即绑定到服务,但是当所有组件解除绑定时,服务将被销毁。
虽然本文档通常分别讨论这两种类型的服务,但您的服务可以双向工作 - 它可以启动(无限期运行)并允许绑定。这只是一个问题,你是否实现了一些回调方法:onStartCommand()允许组件启动它,和onBind()允许绑定它。
无论您的应用程序是启动,绑定还是两者兼而有之,任何应用程序组件都可以使用该服务(甚至来自单独的应用程序),就像任何组件可以使用Activity一样 - 通过使用Intent启动它。但是,您可以在清单文件中将服务声明为私有,并阻止从其他应用程序访问。有关在清单中声明服务的部分中将对此进行更多讨论。
警告:服务在其托管进程的主线程中运行 - 该服务不会创建自己的线程,也不会在单独的进程中运行(除非您另行指定)。这意味着,如果您的服务要进行任何CPU密集型工作或阻塞操作(例如MP3播放或网络),您应该在服务中创建一个新线程来完成这项工作。通过使用单独的线程,您将降低应用程序无响应(ANR)错误的风险,并且应用程序的主线程可以保持专用于用户与您的Activity的交互。
基础
你应该使用服务还是线程?
即使用户没有与您的应用程序交互,服务也只是可以在后台运行的组件。因此,只有在您需要的时候才应创建服务。
如果您需要在主线程之外执行工作,但只有在用户与您的应用程序交互时,您应该创建一个新线程而不是服务。例如,如果您想播放一些音乐,但只有在您的活动正在运行时,您可以在onCreate()中创建一个线程,在onStart()中开始运行它,然后在onStop()中停止它。还要考虑使用AsyncTask或HandlerThread,而不是传统的Thread类。有关线程的更多信息,请参阅进程和线程文档。
请记住,如果您确实使用了服务,它默认仍然在您的应用程序的主线程中运行,因此如果它执行密集或阻塞操作,您仍应在服务中创建新线程。
要创建服务,您必须创建Service的子类(或其现有的子类之一)。在您的实现中,您需要覆盖一些回调方法,这些方法处理服务生命周期的关键方面,并提供组件绑定到服务的机制(如果适用)。您应该覆盖的最重要的回调方法是:
onStartCommand()
当另一个组件(如活动)通过调用startService()请求启动服务时,系统会调用此方法。一旦执行此方法,服务就会启动并可以无限期地在后台运行。如果实现此功能,您有责任在完成工作后通过调用stopSelf()或stopService()停止服务。 (如果您只想提供绑定,则不需要实现此方法。)
onBind()
当另一个组件想要通过调用bindService()与服务绑定(例如执行RPC)时,系统会调用此方法。在此方法的实现中,您必须通过返回IBinder来提供客户端用于与服务通信的接口。您必须始终实现此方法,但如果您不想允许绑定,则应返回null。
onCreate()
系统在首次创建服务时调用此方法,以执行一次性设置过程(在调用onStartCommand()或onBind()之前)。如果服务已在运行,则不会调用此方法。
onDestroy()
当服务不再使用并被销毁时,系统会调用此方法。您的服务应实现此操作以清除任何资源,如线程,已注册的侦听器,接收器等。这是服务接收的最后一个调用。
如果组件通过调用startService()启动服务(这导致调用onStartCommand()),则服务将保持运行,直到它通过stopSelf()停止自身,或者另一个组件通过调用stopService()来停止它。
如果组件调用bindService()来创建服务(并且未调用onStartCommand()),则只要组件绑定到该组件,该服务就会运行。一旦服务从所有客户端解除绑定,系统就会销毁它。
Android系统将仅在内存不足并且必须为具有用户焦点的活动恢复系统资源时强制停止服务。如果服务绑定到具有用户焦点的activity,则它不太可能被杀死,并且如果声明服务在前台运行(稍后讨论),那么它几乎永远不会被杀死。否则,如果服务已启动并且长时间运行,则系统将随着时间的推移降低其在后台任务列表中的位置,并且该服务将极易受到查杀 - 如果您的服务已启动,则必须将其设计为优雅地处理系统重启。如果系统终止了您的服务,它会在资源再次可用时立即重新启动它(尽管这也取决于您从onStartCommand()返回的值,如后面所述)。有关系统何时可能销毁服务的详细信息,请参阅“进程和线程”文档。
在以下部分中,您将了解如何创建每种类型的服务以及如何从其他应用程序组件中使用它。
在清单中声明服务
与Activity(和其他组件)一样,您必须在应用程序的清单文件中声明所有服务。
要声明您的服务,请添加<service>元素作为<application>的子元素。例如:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
请参阅<service>有关在清单中声明服务的更多信息,请参阅元素参考。
您可以在<service>元素中包含其他属性。例如启动服务所需的权限以及运行服务的进程。 android:name属性是唯一必需的属性 - 它指定服务的类名。一旦发布了应用程序,就不应该更改此名称,因为如果这样做,您可能会因为依赖显式意图来启动或绑定服务而破坏代码(请参阅博客文章,Things That Cannot Change)。
为确保您的应用程序安全,在启动或绑定服务时始终使用明确的意图,并且不要为服务声明意图过滤器。如果允许对哪些服务启动存在一些歧义至关重要,则可以为服务提供意图过滤器并从Intent中排除组件名称,但是必须使用setPackage()为intent设置包,为目标服务提供足够的消歧。
此外,您可以通过包含android:exported属性并将其设置为“false”来确保您的服务仅适用于您的应用。即使使用明确的意图,这也可以有效阻止其他应用启动您的服务。
创建“启动”服务
启动服务是另一个组件通过调用startService()启动的服务,从而调用服务的onStartCommand()方法。
当服务启动时,它的生命周期独立于启动它的组件,并且服务可以无限期地在后台运行,即使启动它的组件被销毁。因此,当通过调用stopSelf()完成其工作时,服务应该自行停止,或者另一个组件可以通过调用stopService()来停止它。
诸如Activity之类的应用程序组件可以通过调用startService()并传递指定服务的Intent并包含要使用的服务的任何数据来启动服务。该服务在onStartCommand()方法中接收此Intent。
例如,假设某个Activity需要将一些数据保存到在线数据库中。该Activity可以启动协同服务,并通过将意图传递给startService()来传递要保存的数据。该服务在onStartCommand()中接收intent,连接到Internet并执行数据库事务。事务完成后,服务将自行停止并被销毁。
警告:默认情况下,服务在与声明它的应用程序相同的进程中运行,并在该应用程序的主线程中运行。因此,如果您的服务在用户与同一应用程序中的活动进行交互时执行密集或阻止操作,则该服务将降低活动性能。为避免影响应用程序性能,您应该在服务中启动一个新线程。
传统上,您可以扩展两个类来创建启动服务:
Service
这是所有服务的基类。扩展此类时,重要的是创建一个新线程来执行所有服务的工作,因为默认情况下,服务使用应用程序的主线程,这可能会降低应用程序运行的任何活动的性能。
IntentService
这是Service的子类,它使用工作线程来处理所有启动请求,一次一个。如果您不要求您的服务同时处理多个请求,这是最佳选择。您需要做的就是实现onHandleIntent(),它接收每个启动请求的意图,以便您可以执行后台工作。
以下部分描述了如何使用这些类中的任何一个来实现服务。
扩展IntentService类
因为大多数启动的服务不需要同时处理多个请求(实际上这可能是一个危险的多线程方案),所以如果使用IntentService类实现服务可能是最好的。
IntentService执行以下操作:
- 创建一个默认工作线程,该线程执行与应用程序主线程分开的传递给onStartCommand()的所有意图。
- 创建一个工作队列,一次将一个intent传递给onHandleIntent()实现,因此您不必担心多线程。
- 在处理完所有启动请求后停止服务,因此您永远不必调用stopSelf()。
- 提供返回null的onBind()的默认实现。
- 提供onStartCommand()的默认实现,该实现将意图发送到工作队列,然后发送到onHandleIntent()实现。
所有这些加起来,你需要做的就是实现onHandleIntent()来完成客户端提供的工作。 (但是,您还需要为服务提供一个小构造函数。)
这是IntentService的示例实现:
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();
}
}
}
这就是你所需要的:构造函数和onHandleIntent()的实现。
如果您决定也覆盖其他回调方法,例如onCreate(),onStartCommand()或onDestroy(),请务必调用超类实现,以便IntentService可以正确处理工作线程的生命周期。
例如,onStartCommand()必须返回默认实现(这是将intent传递给onHandleIntent()的方式):
@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);
}
除了onHandleIntent()之外,你不需要调用超类的唯一方法是onBind()(但是如果你的服务允许绑定,你只需要实现它)。
在下一节中,您将看到在扩展基本Service类时如何实现相同类型的服务,这是更多代码,但如果您需要处理同时启动请求,则可能是合适的。
扩展Service类
正如您在上一节中看到的那样,使用IntentService可以非常简单地实现已启动的服务。但是,如果您需要服务执行多线程(而不是通过工作队列处理启动请求),那么您可以扩展Service类来处理每个intent。
为了进行比较,以下示例代码是Service类的实现,它使用IntentService执行与上面示例完全相同的工作。也就是说,对于每个启动请求,它使用工作线程来执行作业,并且一次只处理一个请求。
public class HelloService extends Service {
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
// 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 will not 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
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@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 = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.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();
}
}
如您所见,这比使用IntentService要多得多。
但是,因为您自己处理对onStartCommand()的每次调用,所以您可以同时执行多个请求。这不是这个例子的作用,但是如果这是你想要的,那么你可以为每个请求创建一个新线程并立即运行它们(而不是等待先前的请求完成)。
请注意,onStartCommand()方法必须返回一个整数。整数是一个值,描述系统在系统杀死服务时应如何继续服务(如上所述,IntentService的默认实现为您处理此问题,尽管您可以对其进行修改)。 onStartCommand()的返回值必须是以下常量之一:
START_NOT_STICKY
如果系统在onStartCommand()返回后终止服务,则不要重新创建服务,除非传递的是待处理意图。这是最安全的选项,可以避免在不需要时,以及你的应用程序只是重新启动未完成的工作时运行你的服务。
START_STICKY
如果系统在onStartCommand()返回后终止服务,则重新创建服务并调用onStartCommand(),但不重新传递最后一个意图。相反,系统使用null值 intent调用onStartCommand(),除非带启动的意图来启动服务,在这种情况下,会传递这些意图。这适用于不需要执行命令并无限期运行并等待工作的媒体播放器(或类似服务)。
START_REDELIVER_INTENT
如果系统在onStartCommand()返回后终止服务,则重新创建服务并使用传递给服务的最后一个意图调用onStartCommand()。任何待处理的意图依次发送。这适用于主动执行应立即恢复的作业的服务,例如下载文件。
有关这些返回值的更多详细信息,请参阅每个常量的链接参考文档。
启动服务
您可以通过将Intent(指定要启动的服务)传递给startService()来从Activity或其他应用程序组件启动服务。 Android系统调用服务的onStartCommand()方法并将其传递给Intent。 (你不应该直接调用onStartCommand()。)
例如,Activity可以使用startService()的显式intent启动上一节(HelloService)中的示例服务:
Intent intent = new Intent(this, HelloService.class);
startService(intent);
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()的调用时自行停止服务。
有关服务生命周期的更多信息,请参阅以下有关管理服务生命周期的部分。
创建绑定服务
绑定服务是允许应用程序组件通过调用bindService()来绑定它以便创建长期连接(并且通常不允许组件通过调用startService()来启动它的服务)。
如果要从应用程序中的Activity和其他组件与服务交互,或者通过进程间通信(IPC)将某些应用程序的功能公开给其他应用程序,则应创建绑定服务。
要创建绑定服务,必须实现onBind()回调方法以返回定义用于与服务进行通信的接口的IBinder。然后,其他应用程序组件可以调用bindService()来检索接口并开始调用服务上的方法。该服务仅用于服务绑定到它的应用程序组件,因此当没有绑定到服务的组件时,系统会销毁它(您不需要停止绑定服务)。
要创建绑定服务,首先必须定义指定客户端如何与服务通信的接口。服务和客户端之间的这个接口必须是IBinder的实现,并且是您的服务必须从onBind()回调方法返回的。一旦客户端收到IBinder,它就可以开始通过该接口与服务进行交互。
多个客户端可以立即绑定到该服务。当客户端完成与服务的交互时,它会调用unbindService()来取消绑定。一旦没有客户端绑定到该服务,系统就会销毁该服务。
有多种方法可以实现绑定服务,并且实现比启动服务更复杂,因此绑定服务讨论将显示在有关绑定服务的单独文档中。
向用户发送通知
运行后,服务可以使用Toast Notifications或Status Bar Notifications通知用户事件。
Toast通知是当前窗口的表面上出现片刻然后消失的消息,而状态栏通知在状态栏中提供带有消息的图标,用户可以选择该消息以采取行动(例如作为开始活动)。
通常,状态栏通知是完成某些后台工作(例如文件已完成下载)并且用户现在可以对其进行操作时的最佳技术。当用户从展开视图中选择通知时,通知可以开始活动(例如查看下载的文件)。
有关详细信息,请参阅Toast Notifications或状态栏通知开发人员指南。
在前台运行服务
前台服务是一种服务,被认为是用户主动意识到的东西,因此不是系统在内存不足时杀死的候选者。前台服务必须提供状态栏的通知,该通知位于“正在进行”标题下,这意味着除非服务被停止或从前台移除,否则无法解除通知。
例如,应该将从服务播放音乐的音乐播放器设置为在前台运行,因为用户明确知道其操作。状态栏中的通知可以指示当前歌曲并允许用户发起与音乐播放器交互的活动。
要让您的服务在前台运行,请调用startForeground()。此方法有两个参数:唯一标识通知的整数和状态栏的通知。例如:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
警告:您为startForeground()提供的整数ID不能为0。
要从前台删除服务,请调用stopForeground()。此方法采用布尔值,指示是否也删除状态栏通知。此方法不会停止服务。但是,如果在服务仍在前台运行时停止服务,则也会删除通知。
有关通知的详细信息,请参阅创建状态栏通知。
管理服务的生命周期
服务的生命周期比Activity的生命周期简单得多。但是,密切关注服务的创建和销毁方式更为重要,因为服务可以在后台运行,而无需用户知晓。
服务生命周期 - 从创建时到销毁时 - 可以遵循两条不同的路径:
- A started service
当另一个组件调用startService()时,将创建该服务。然后该服务无限期地运行,并且必须通过调用stopSelf()来停止它自己。另一个组件也可以通过调用stopService()来停止服务。当服务停止时,系统会销毁它。
- A bound service
当另一个组件(客户端)调用bindService()时,将创建该服务。然后,客户端通过IBinder接口与服务进行通信。客户端可以通过调用unbindService()来关闭连接。多个客户端可以绑定到同一个服务,当所有客户端解除绑定时,系统会销毁该服务。 (该服务不需要自行停止。)
这两条路径并不完全分开。也就是说,您可以绑定到已经使用startService()启动的服务。例如,可以通过使用标识要播放的音乐的Intent调用startService()来启动背景音乐服务。之后,可能当用户想要对播放器进行一些控制或获取有关当前歌曲的信息时,活动可以通过调用bindService()绑定到服务。在这种情况下,stopService()或stopSelf()实际上不会停止服务,直到所有客户端解除绑定。
实现生命周期回调
与Activity一样,服务具有生命周期回调方法,您可以实施这些方法来监视服务状态的变化并在适当的时间执行工作。以下演示了每个生命周期方法:
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // 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 mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@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生命周期回调方法不同,您不需要调用这些回调方法的超类实现。
通过实现这些方法,您可以监视服务生命周期的两个嵌套循环:
- 服务的整个生命周期发生在调用onCreate()和onDestroy()返回的时间之间。与活动一样,服务在onCreate()中进行初始设置,并释放onDestroy()中的所有剩余资源。例如,音乐播放服务可以创建音乐将在onCreate()中播放的线程,然后在onDestroy()中停止该线程。无论是由startService()还是bindService()创建的,都会为所有服务调用onCreate()和onDestroy()方法。
- 服务的生存期始于对onStartCommand()或onBind()的调用。每个方法都分别传递给传递给startService()或bindService()的Intent。如果服务已启动,则生存期将在整个生命周期结束的同一时间结束(即使在onStartCommand()返回后服务仍处于活动状态)。如果绑定了服务,则当onUnbind()返回时,活动生存期将结束。
注意:虽然通过调用stopSelf()或stopService()来停止启动的服务,但是没有相应的服务回调(没有onStop()回调)。因此,除非服务绑定到客户端,否则系统会在服务停止时销毁它 - onDestroy()是收到的唯一回调。
图2说明了服务的典型回调方法。虽然该图将startService()创建的服务与bindService()创建的服务分开,但请记住,任何服务(无论它是如何启动的)都可能允许客户端绑定到它。因此,最初使用onStartCommand()(通过调用startService()的客户端)启动的服务仍然可以接收对onBind()的调用(当客户端调用bindService()时)。
有关创建提供绑定的服务的更多信息,请参阅“绑定服务”文档,其中包含有关管理绑定服务生命周期一节中有关onRebind()回调方法的更多信息。