文章译自:http://developer.android.com/guide/components/services.html
快速预览
服务 是可以在后台执行耗时操作且不提供用户界面的应用组件。其它的应用组件可以启动服务并且它将继续运行在后台,即使用户切换至其它应用。此外,一个组件可以绑定到服务来与之交互,甚至是执行进程间通信(IPC)。例如,一个服务可能处理网络事务,播放音乐,执行文件I/O,或者与一个内容提供者交互,所有这些都在后台执行。
服务基本上可以采用两种形式:
启动的
当一个应用组件(比如一个活动)通过调用startService()来启动服务时,此时服务为“启动的”。一旦启动,服务就可以无限期地运行在后台,即便是启动它的组件被销毁了。通常,一个启动的服务执行一个单一操作且不向调用者返回结果。例如,它可能通过网络下载或上传文件。当操作完成时,服务应该自己停止。
绑定的
当一个应用组件通过调用bindService()绑定到它时,此时服务为“绑定的”。绑定的服务提供了一个客户端-服务器接口,它允许组件与服务进行交互:发送请求,获取结果,甚至是通过进程间通信(IPC)跨进程地做到这些。绑定的服务仅在其它应用组件绑定到其上时才运行。多个组件可以同时绑定到服务上,仅当它们都取消绑定时,服务才被销毁。
尽管该文档单独地概括性地论述了这两种类型的服务,不过你的服务可以以两种方式工作 —— 它可以被启动(来无限期地运行)并且也允许进行绑定。这仅仅是你是否实现一对回调方法的问题:onStartCommand()允许组件来启动它,onBind()则允许绑定。
无论你的应用是否是被启动,被绑定,或二者皆是,任何应用组件都能够以任何组件使用活动(通过使用Intent启动它)的相同方式来使用该服务(甚至是来自不同的应用)。不过,你可以在清单文件内将服务声明为私有的,并阻止来自其他应用的访问。这一点在关于在清单文件中声明服务的章节内有更多讨论。
注意: 服务运行在它的寄宿进程的主线程内,就是说,服务不会创建它自己的线程,并且不会运行在一个单独的进程内(除非你另外指定)。这意味着,如果你的服务将要执行任何CPU密集型工作或者阻塞操作(诸如MP3回放或网络)。通过使用一个单独的线程,你将降低应用程序没有响应(ANR)的错误风险,并且应用的主线程能够依然致力于用户与你的活动间的交互。
1. 基础知识
为了创建服务,你必须创建一个Service的子类(或一个它的现有子类)。在你的实现中,你需要重写一些处理服务生命周期关键方面的回调方法以及为组件绑定到该服务提供一种机制,如有需要的话。你应该重写的最重要的回调方法是:
onStartCommand()
当另外的组件,诸如一个活动,通过调用startService()来请求启动服务时,由系统调用这个方法。一旦执行此方法,服务被启动并可以在后台无限期地运行。如果你实现了这个方法,在服务的工作完事时,由你负责通过调用stopSelf() 或stopService()来停止服务。(如果你仅想提高绑定,则不必实现该方法。)
onBind()
当其它组件通过调用bindService()打算与服务绑定时(譬如执行RPC),由系统调用这个方法。在你的此方法实现里,通过返回一个IBinder,你必须提供一个客户端使用的来与服务器进行通话的接口。你通常必须实现这个方法,除非你不打算允许绑定,那么你应该返回null。
onCreate()
当服务首次被创建来执行一次性设置过程时,由系统调用这个方法(此前它调用onStartCommand()或onBind())。如果服务已经运行了,该方法不会被调用。
onDestroy()
当服务不再被使用并且正被销毁时,由系统调用这个方法。你的服务应该实现这方法来清理任何诸如线程,注册的监听器,接收器等等。这是最后一个服务收到的调用。
如果组件通过调用startService()(
它会导致一个对
onStartCommand()的调用)启动服务,那么该服务会保持运行直到它使用
stopSelf()结束其自己或者其它组件通过调用
stopService()停止它。
如果组件调用bindService()来创建服务(
onStartCommand()不会被调用),那么该服务仅在组件绑定在其上时才运行。一旦该服务与全部客户端脱离绑定,系统则销毁它。
仅当在低内存时Android系统才将强制停止服务,同时它必须为拥有用户焦点的活动恢复系统资源。如果服务被绑定到拥有用户焦点的活动上,那么它不大可能被杀死,以及如果服务被声明为在前台运行(稍后讨论),那么它将几乎从不会被杀死。另外,如果服务被启动了并且是长时间运行的,那么随着时间的流逝,系统将降低它在后台任务列表中的位置并且该服务将变得非常容易杀死,因此,如果你的服务被启动了,那么你必须设计它来得体地处理由系统导致的重启。如果系统杀死了你的服务,资源一旦再次变得可用(尽管这还取决于你从onStartCommand()返回的值,如稍后的讨论
)系统就重新启动该服务。更多关于当系统可能销毁一个服务的信息,请参阅进程和线程文档。
在接下来的章节里,你将看到如何能够创建每种类型的服务以及如何由其他应用组件来使用它。
1.1 在清单文件中声明服务
像活动(及其它组件)一样,你必须在应用的清单文件中声明所有的服务。
为了声明你的服务,添加<service>
元素作为<application>
元素的子元素。例如:
<manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application> </manifest>
还有其它的你可以包含在
<service>
元素内的属性来定义诸如启动服务所需要的权限及服务应该运行于此的进程等属性。android:name
是唯一必需的属性,因为它指定了服务的类名。一旦你发布了你的应用,你就不应该改变这个名字,因为如果你这样做了,可能会破坏在那里使用显式意图引用你的服务的某些功能(阅读博客文章,不能改变的东西)。
更多关于在清单文件内声明服务的信息,请参阅<service>
元素参考。
正如活动一样,服务可以定义允许其它组件使用显式意图来调用服务的意图过滤器。通过声明意图过滤器,来自安装于用户设备上的任何应用组件都可能启动你的服务,如果你的服务声明了匹配其它应用递送给startService()的意图的意图过滤器。
如果你打算只在本地使用你的服务(其它应用不使用它),那么你不必(也不应该)提供任何意图过滤器。没有任何意图过滤器,你必须使用一个显式地指定服务类的意图来启动服务。更多关于启动服务的信息将在下面讨论。
此外,你可以确保你的服务是你的应用私有的,只要你包含android:exported
属性并设置它为“false”。即使你的服务提供了意图过滤器,此设置也是有效的。
更多关于为你的服务创建意图过滤器的信息,请参阅意图和意图过滤器文档。
你应该使用服务还是线程?
服务是一个简单的组件,它可以在后台运行即使当用户没有与你的应用进行交互时。因此,只要这正是你所需要的,你就应该创建服务。
如果你需要在你的主线程之外执行操作,但仅当用户与你的应用交互时,那么你很可能应该创建一个新的线程而不是一个服务。例如,如果你想播放一些音乐,但仅当你的活动正在运行的时候,你可能在onCreate()内创建一个线程,并在onStart()内开始运行它,然后在onStop()里停止它。还可以考虑使用AsyncTask或HandlerThread而不是传统的Thread类。更多关于线程的信息,请参阅进程和线程文档。
记住,如果你使用服务,默认情况下,它依然运行在你的应用的主线程内,所以,如果服务执行密集或阻塞操作的话,你还是应该在服务内部创建一个新的线程。
启动的服务就是其他组件通过调用startService()(导致对服务的onStartCommand()的方法的调用)启动的服务。当服务被启动时,它便拥有一个独立于启动它的组件的生命周期并且无限期地运行于后台,即便是启动它的组件被销毁。因此,服务应该在完成其工作时通过调用stopSelf()停止自己,或者,其它组件可以通过调用stopService()停止它。诸如活动这样的应用组件可以通过调用startService()并传递一个指定了服务和包含了任何供服务使用的数据的Intent来启动服务。服务则在onStartCommand()内接收这个Intent。
例如,假设一个活动需要把一些数据保存到网上数据库。该活动可以通过向startService()传递一个意图来启动一个配套服务并向其递送要保存的数据。服务在onStartCommand()内接收该意图,然后连接互联网并执行数据库事务。当事务完成时,服务停止其自己然后被销毁。
注意: 默认情况下,服务运行在与声明它的应用程序相同的进程里,并且在该应用程序的主线程里。所以,当用户与来自相同应用内的活动交互时,如果你的服务执行密集或阻塞操作,则该服务将减缓活动执行。为了避免影响应用程序执行,你应该在该服务的内部启动一个新的线程。
针对android 1.6或更低版本
如果你正在构建针对Android 1.6或更低版本的应用程序,你需要实现onStart(),而不是onStartCommand()(在Android 2.0上,onStart()已被弃用,推荐使用onStartCommand())。
更多关于为早于Android2.0的版本提供兼容的信息,请参阅onStartCommand()文档。
传统上,有两个你可以扩展的类来创建启动的服务:
Service
它是所有服务的基类。当你扩展此类时,重要的是,你要创建一个在其内完成服务的所有工作的新线程,因为,默认情况下,服务使用应用的主线程,它可能阻碍任何你的应用正在运行的活动的执行。
IntentService
它是Service的一个子类,它使用一个辅助线程来逐一地处理所有的启动请求。这是最好的选择,如果你不需要你的服务同时处理多个请求的话。 所有你需要做的就是实现onHandleIntent(),它接收每个启动请求的意图以便你可以执行后台操作。
2.1 扩展IntentService 类
因为绝大多数启动的服务不需要同时处理多个请求(这实际上是一个危险的多线程情况), 如果你使用IntentService类来实现你的服务,这可能是最好的。
IntentService 执行以下操作:
所有的这一切表明了这样的事实,你需要做的就是实现onHandleIntent() 来完成由客户端的制定的工作。(不过,你还需要为服务提供一个小的构造函数。)
下面是一个IntentService实现的例子:
public class HelloIntentService extends IntentService { /** * 构造器是必的,同时必须使用辅助线程的名字调用基类IntentService(String) 构造器。 */ public HelloIntentService() { super("HelloIntentService"); } /** * IntentService从默认的辅助线程里使用启动服务的意图调用这个方法。当该方法返回时, * IntentService 适当地停止服务。 */ @Override protected void onHandleIntent(Intent intent) { //通常,我们在这里执行某些操作,如下载文件。 // 对于我们的例子来说,我们只是休眠5秒钟。 long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } } }
这就是你所需要的:一个构造函数和一个onHandleIntent()实现。
如果你还打算重写其他的回调方法,譬如onCreate(),onStartCommand(), 或onDestroy(),请务必要调用它们在基类中的实现,这样做是为了IntentService可以恰当地处理辅助线程的生命。
例如,onStartCommand()必须返回默认的实现(它就是意图如何被递送给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的代码要多很多,但是如果你需要处理同步的启动请求,扩展Service可能是合适的选择。
正如上一章节所见,使用IntentService可以非常简单地实现一个启动的服务。然而,如果你要求你的服务去执行多线程(而不是通过工作队列来处理启动请求),那么你可以扩展Service类来处理每一个意图。
为了便于比较,下面的示例代码是一个Service类的扩展实现,它执行同上面使用IntentService的示例完全一样的工作。也就是说,对于每个启动请求,它使用一个辅助线程来完成工作并且一次只处理一个请求。
public class HelloService extends Service { private Looper mServiceLooper; private ServiceHandler mServiceHandler; // 接收来自辅助线程的处理器 private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { //通常,我们将会在此处完成某些工作, 例如下载一个文件. // 对于我们示例来说,我们在此只是休眠5秒钟. long endTime = System.currentTimeMillis() + 5*1000; while (System.currentTimeMillis() < endTime) { synchronized (this) { try { wait(endTime - System.currentTimeMillis()); } catch (Exception e) { } } } // 利用启动ID停止服务,以便我们在处理其它工作期间不停止该服务 stopSelf(msg.arg1); } } @Override public void onCreate() { //启动运行服务的线程。注意,我创建一个独立的线程,因为服务通常运行于进程的主线程里, //我们不想阻塞它。我们还赋予它后台优先权,这样CPU密集型的工作将不会中断我们的UI. HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); //获取HandlerThread的Looper,接着把它用于我们的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(); // 对于每个启动请求, 发送一条消息来启动工作并递送启动ID,所以当我们完成该工作时, //我们知道我们正在停止的是哪个请求 Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; mServiceHandler.sendMessage(msg); //如果服务被杀死了,从这返回后会从新启动 return START_STICKY; } @Override public IBinder onBind(Intent intent) { //我们没有提供绑定,所以返回null return null; } @Override public void onDestroy() { Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); } }
正如你所看到的,它比使用IntentService有更多的操作。
然而,由于你亲自处理每个onStartCommand()调用,因此你可以同时执行多个请求。这个示例并不是那样的,但是如果你打算那样,那么你可以为每个请求创建一个新的线程,然后立即运行它们(而不是等待先前的请求结束)。
START_NOT_STICKY
在onStartCommand()返回后,如果系统将服务杀死,则不再重新创建该服务,除非有未决的意图递送。当在没必要以及你的应用可以简单地重启任何未完成的任务时,它是避免运行你的服务的最安全的选项。
START_STICKY
在onStartCommand()返回后,如果系统将服务杀死,则重建该服务并调用onStartCommand(),但是不递送最后的意图。相反,系统使用一个空的意图来调用onStartCommand(),除非有未决的意图来启动该服务,在这种情况下,递送那些意图。这适合于不执行命令的媒体器(或类似的服务),而是无限期地运行并等待着工作。
START_REDELIVER_INTENT
在onStartCommand()返回后,如果系统将服务杀死,则重建服务并使用最后递送给该服务的意图调用onStartCommand()。任何未决的意图依次地递送给服务。这适合于主动执行应该立即被恢复的工作的服务,譬如下载文件。
更多关于这些返回值的细节,请参阅每个常量连接的参考文档。
可以通过向startService()传递一个Intent(其指定了启动的服务)从活动或其他应用组件内启动服务。Android系统调用服务的onStartCommand()方法并把Intent传递给它。(绝不应该直接调用onStartCommand()。)
HelloSevice
)。
Intent intent = new Intent(this, HelloService.class); startService(intent);
startService()方法立刻返回,接着android系统调用服务的onStartCommand()方法。如果服务尚未运行,系统则首先调用onCreate(),然后调用onStartCommand()。
如果服务也没有提供绑定,通过startService()递送的意图则是应用组件与服务间通信的唯一方式。然而,如果你打算让服务返回一个结果,那么启动服务的客户端创建一个广播(使用getBroadcast())PendingIntent,然后把它递送给在意图内启动的服务。于是服务可以利用广播来传递结果。
多个启动服务的请求会导致多个相应的对服务的onStartCommand()的调用。但是仅需要一个停止服务的请求(通过stopSelf() or stopService())便可以停止它。
启动的服务必须管理它自己的生命周期。就是说,系统不会停止或销毁服务,除非系统必须恢复系统内存然后服务在onStartCommand()返回后继续运行。因此,服务必须通过调用stopSelf()来停止自己,或其他组件可以通过调用stopService()来停止它。
一旦使用stopSelf() 或stopService()请求停止服务,系统会竟可能快地销毁它。
然而,如果你的服务同时处理多个onStartCommand()请求,那么你不应该在完成了处理一个启动请求时停止该服务,因为自那以后你也许已经收到新的启动请求(在第一个请求之后的停止将终止第二个请求)。为了避免这个问题,可以使用stopSelf(int)来确保你的停止服务的请求总是基于最近的启动请求。也就是说,当你调用stopSelf(int)时,传送启动请求的ID给停止请求对应的该方法。在调用stopSelf(int)时,如果服务收到一个新的启动请求,那么ID将不匹配,则服务将不会停止。
注意: 这点很重要,为了避免浪费系统资源和消耗电池电量,在当服务完成工作时,你的应用停止它自己的服务。如有必要,其他组件可以通过调用stopService()来停止服务。甚至如果你启用服务绑定,即使它从未收到onStartCommand()调用,你通常必须自己停止该服务。
更多关于服务生命周期的信息,请参阅下面的关于管理服务生命周期的章节。
绑定的服务是允许应用组件通过调用bindService()绑定到它以便创建一个长期连接(并且通常不允许组件通过调用startService()来启动它)的服务。
当你打算通过进程间通讯(IPC)的方式从应用内的活动和其他组件与服务交互或者把应用的功能暴露给其他应用时,你应该创建一个绑定的服务。
为创建绑定的服务,你必须实现onBind()回调方法以返回一个定义了与服务沟通的接口的IBinder。于是其他应用组件就可以调用bindService()来取回该接口,并开始调用服务上的方法。该服务仅为服务于绑定到其上的应用组件而存活,所以当没有组件绑定到服务时,系统便销毁它(你不必以当通过onStartCommand()启动服务时你必须停止的方式来停止绑定的服务)。
为创建绑定的服务,首先必须做的是定义指定客户端如何可以与服务进行通信的接口。这个在服务与客户端之间的接口必须是一个IBinder的实现,并且是你的服务从onBind()回调方法返回的东西。一旦客户端收到IBinder,它便可以通过这个接口开始与服务进行交互。
多个客户端可以同时绑定到服务上。当一个客户端完成同服务交互时,它调用unbindService()来解除绑定。一旦没有客户端绑定到服务上时,系统销毁服务。
有多种方式实现绑定的服务,并且实现起来比实现启动的服务要复杂的多,所以关于绑定的服务的讨论放在一个单独的关于绑定的服务的文档里。
一旦运行,服务可以通过使用消息框通知(Toast Notifications)或状态栏通知(Status Bar Notifications)通知用户。
消息框通知是一条短暂出现在当前窗口表面上的消息,然后便消失了,而状态栏通知则使用消息在状态栏上提供一个图标,用户可以进行选择它以便采取行动(比如开启一个活动)。
通常,当一些后台工作完成 (譬如文件下载完成)并且用户现在可以在其上执行操作时,状态栏通知是最好的方式。当用户从展开的视图中选择通知时,此通知就可以启动活动(比如查看下载的文件)。
更多信息请参与消息框通知(Toast Notifications)或状态栏通知(Status Bar Notifications)开发者指南。
例如,从服务开始播放音乐的音乐播放器应该被设置运行在前台,因为用户清楚地知道它的操作。位于状态栏上的通知可能提示当前的歌曲,并允许用户启动一个活动来与音乐播放器交互。
为使你的服务运行在前台,调用startForeground()。这个方法携带两个参数:一个唯一地标识通知的整数和状态栏的通知(Notification)。例如:
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, notification);
为从前台移除服务,调用stopForeground()。这个方法携带一个boolean,暗指是否也移除状态栏通知。这个方法不会停止服务。然而,当服务正在前台运行时,如果你停止了它,那么通知也会被移除。
注意: startForeground()和stopForeground()方法是在Android 2.0(API Level5)中被引入的。为在较老版本平台的前台运行你的服务,你必须使用先前的setForeground()
方法 — 更多关于如何提供向后兼容的信息,参阅startForeground()文档。
更多关于通知的信息,参阅创建状态栏通知。
服务的生命周期比活动的生命周期简单的多。可是,它甚至更重要,你需要密切注意你的服务是如何被创建及被销毁的,因为运行于后台的服务而没让用户时刻注意到。
服务生命周期 —— 从它被创建时到它被销毁时 —— 可以遵循两个不同的路经:
当其他组件调用startService()时服务被创建。然后服务无限期地运行,并必须通过调用stopSelf()来停止自己。其他组件也可以通过调用stopService()停止服务。当服务被停止时,系统销毁它。
当其他组件(客户端)调用bindService(),服务被创建。接着,客户端通过一个IBinder接口与服务进行通信。客户端可以通过调用unbindService()关闭连接。多个客户端可以绑定到相同的服务上,并且当他们全部都解除绑定时,系统销毁该服务。(服务不必停止它自己。)
这两个路径不是完全独立的。也就是说,你可以绑定到一个已经通过startService()启动的服务。例如,可以通过使用指明要播放的音乐的意图来调用startService()启动后台音乐服务。之后,可能当用户想在播放器上行使某些控制或获取关于当前曲目信息时,活动可以通过调用bindService()绑定到服务上。在类似于此的情况下,stopService()或stopSelf()实际上不会停止服务,知道全部客户端解除绑定。
如同活动一样,服务拥有生命周期回调方法,你可以实现它们来控制服务状态的变化以及在适当的时候执行操作。接下来的框架服务展示了生命周期的每个方法:
public class ExampleService extends Service { int mStartMode; // 指明如果服务被杀后该如何执行 IBinder mBinder; //客户端绑定的接口 boolean mAllowRebind; //指明是否应该使用onRebind @Override public void onCreate() { // 服务正被创建 } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 服务正在启动,由于一个 startService() 的调用 return mStartMode; } @Override public IBinder onBind(Intent intent) { // 客户端 正通过bindService()绑定到服务上 return mBinder; } @Override public boolean onUnbind(Intent intent) { //全部的客户端通过 unbindService()已经解除绑定 return mAllowRebind; } @Override public void onRebind(Intent intent) { // onUnbind() 已经被调用后, // 客户端正通过bindService()绑定服务上 } @Override public void onDestroy() { // 不再使用服务,并正在被销毁 } }
注意: 不像活动的生命周期回调方法, 你不必调用那些回调方法的基类的实现。
表 2. 服务的生命周期。左图显示了当服务通过startService()被创建时的生命周期,右图显示当服务通过bindService()被创建时的生命周期。
通过实现这些方法,你可以控制服务生命周期的两个嵌套的循环:
所有服务都要调用onCreate() 和onDestroy()方法,无论它们是通过startService() 还是bindService()创建的。
如果服务被启动了,活动生命期与整个生命期同时结束(服务依然是活动的,甚至在onStartCommand()返回之后)。如果服务被绑定了,活动生命期在onUnbind()返回时结束。
注意: 尽管启动的服务可以通过调用stopSelf()或stopService()被停止,但是服务并没有各自的回调(没有onStop()回调)。所以,除非服务被绑定到客户端上,否则系统在当服务被停止时(onDestroy()是唯一接收的回调)销毁它。
图2说明了服务的标准回调函数。尽管该图把通过startService()创建的服务从那些通过bindService()创建的服务中分开了,但是请记住,任何服务,不管是它是如何被创建的,都可能允许客户端绑定到它。所以,一个最初由onStartCommand()启动的服务(通过客户端调用startService())依然可以接收一个onBind()调用(当客户端调用bindService()时)。
更多关于创建一个提供绑定的服务的信息,请参阅绑定的服务文档,它在关于管理绑定的服务的生命周期章节里包括了更多关于onRebind()回调方法的信息。
2012年12月17日 毕