[翻译]API Guides - Service
官方文档原文地址:http://developer.android.com/guide/components/services.html
Service是应用程序组件之一,它并不提供一个用户界面,可以负责一些在后台运行时间较长的操作。其它的应用程序组件可以启动它,就算用户切换到其它的应用上面,Service还将继续在后台运行着。另外,一个组件可以绑定一个Service并与之交互,甚至可以进行进程间通信。打个比方,service可以处理网络传输,播放音乐,执行文件IO,或者与Content Provider进行交互,这些都可以在后台进行。
一个Service从本质上来说有两种形式:
Started
当一个应用程序组件(像一个Activity)通过调用startService()来启动一个Service,这个Service就是"started"的。一旦启动,这个Service就会在后台不确定的运行着,虽然启动它的组件已经销毁了。通常,一个启动的Service会执行一个单独的操作,并且不会返回结果给它的调用者。比如说,它可能通过网络下载或者上传一个文件。当这个操作完成的时候,Service就会停止它自己。
Bound
当一个应用程序组件通过调用bindService()来绑定一个Service,这个Service就是“Bound”的。一个绑定的Service提供一个客户端-服务端的接口,允许组件与Service进行交互,发送个请求啦,获取结果啦,甚至是进程间通信。一个绑定的Service只会在另一个组件绑定它的时候运行,各种组件都可以绑定Service,但一旦全部解绑,Service就被销毁了。
虽然文档分别的简要说明了两种类型的Service,但你的Service可以同时工作在这两种方式上,它能被启动,也可以被绑定。关键在于你是否实现了下面两个方法:onStartCommand()用于组件启动Service,onBind()用于组件绑定Service。
不管你的应用是启动也好,绑定也好,或者两种都有,任何应用程序组件都可以使用Service(甚至是其它的应用),同样的任何组件也可以使用Activity,通过一个Intent来启动它。然后,你可以在manifest文件中把Service声明为私有的,阻碍其它的应用来使用它。关于这一点,更多的详情请参考:Declaring the service in manifest。
警告:一个Service将会在它所持有的进程中的主线程中运行,也就是说,Service不会创建属于它自己的线程,也不会单独运行在一个进程里(除非你指定),这意味着,如果你的Service要做任何消耗CPU的操作或者耗时操作(像回播MP3或网络请求),你应该给Service创建一个线程,让它在那里面去工作。通过使用独立的线程,这将减少应用程序无响应的可能性,并且,应用程序的UI将专注于对用户的响应。
基础
你必须创建一个Service的子类来创建自己的Service(或者使用一个已经存在的子类)。在你的实现里,你必须覆写处理Service生命周期的几个关键的回调方法,并且提供一个供其它组件绑定Service的机制,你最好把这些重要的回调都覆写了:
onStartCommand()
系统调用这个方法是在其它组件,像Activity,通过调用startService()来请求。一旦这个方法被执行,Service就被启动了,在后台不确定的运行着,如果你要实现这个,你需要在工作完成的时候停止Service,通过调用stopSelf()或者stopService()。(如果你使用绑定的方式,你不需要实现这个方法。)
onBind()
当另一个组件想通过bindService()绑定一个Service的时候,系统就会调用这个方法(比如执行RPC)。在你实现这个方法的时候,你必须通过返回一个IBinder对象来提供一个供客户端与service通信的接口。你必须总是实现这个方法,但如果你不想绑定,你应该返回null。
onCreate()
当service第一次被创建的时候,系统会调用这个方法,执行一次性的生产工作(在onStartCommand()和onBind()之前调用)。如果这个service已经在运行了,这个方法就不会被调用。
onDestroy()
当service不在被使用,将要被摧毁的时候,系统会调用这个方法。你的service应该实现这个来清除任何使用过的资源,像线程,注册的监听器,接收器等等。这是service结束时调用的最后一个方法。
你应该使用service还是thread?
Service是一个简单的组件,它可以运行在后台,尽管用户没有和应用程序的交互。所以,你在你需要的时候创建一个service。
如果你需要执行一个在主线程之外的操作,并且仅当用户仍然在使用你的应用程序的时候,那么你应该创建一个新的线程而不是Service。比如说,你想要在Activity运行的时候播放一些音乐,你可以在onCreate()方法里面创建一个新的线程,并且在onStart()方法里面运行它,在onStop()方法里面停止它。可以考虑使用AsyncTask和HandlerThread来替代使用传统的Thread类。查看Processes and Threading文档来查看更多关于线程的信息。
记住,如果你要使用service,它默认会运行在你应用程序的主线程里,如果你要做一些耗时的操作,你还是需要创建新的线程去完成它。
如果组件通过调用startService()来启动一个Service(实际上就是调用了onStartCommand()),然后service就会保持运行,知道它通过他自己调用stopSelf()来终止自己,或者另一个组件通过调用stopService()来停止它。
如果一个组件调用bindService()去创建一个Service(onStartCommand()不会被调用),Service将会只在组件绑定它的时候运行,一旦service被解绑,系统就会销毁它。
Android系统将会在低内存并且必须给用户当前使用的Activity恢复资源的时候强制停止一个Service。如果一个Service绑定在用户当前使用的Activity上,它就不太可能被杀掉,如果一个service声明是在前台运行(将在之后讨论),它也不太可能被杀掉。另外,如果service被启动并且运行了很长的一段时间,系统将会不断把它挪向后台任务列表的底部,这个service就很有可能被杀掉。所以你必须设计一套优雅的机制去重启你的服务。如果系统杀掉了service,它又会在有足够多的可用资源的时候重新启动(虽然这个也取决于onStartCommand()的返回值,这个在后面讨论),想获得更多关于系统如何杀掉service的内容,请参考Processes and Threading。
接下来的部分,你将看到如何去创建这两种service,并且如何在其他的组件里面使用它。
在Manifest中声明service
像activity一样(其它组件也是同样的),你必须在manifest文件中声明你所有的service。
给<application>标签添加子标签<service>来声明你的service,比如:
<manifest ... > ... <application ... > <serviceandroid:name=".ExampleService"/> ... </application> </manifest>
也有一些其它的属性,你可以放置在<service>标签里面去定义,像要求启动service的权限和service应该在哪个进程里运行等。android:name属性是唯一必须的属性,它指定了service的类名。一旦你发布了你的应用程序,你不应该再去修改这个名字,因为你这样做的话,你可能会破坏一些关联你的service的功能。
查看<service>标签的参考来获取更多关于如何在manifest文件中声明的细节。
就和activity一样,一个service可以定义intent filters,这允许其它的组件通过隐式的intent来调用service。通过声明intent filters,在用户设备上安装的任何应用都可能启动你的service,前提是你的service声明的intent filters可以匹配到其它应用程序发送过来的intent。
如果你只想在本程序内使用service(其它的应用程序使用不到),你不需要(或者说不应该)提供任何的intent filters。没有intent filters,你就必须通过使用指定的类名来启动你的service。更多的信息将在后面是starting a service被讨论。
另外,你可以通过android:exported属性,将其设置为false来确保你的service对你的程序来说是私有的。就算有intent filters,这个也会生效。
关于更多如何为你的service创建intent filters,请查看Intent and Intent Filters的文档。
创建一个Started Service
一个started service是另一个组件通过调用startService()来启动的,这导致了service调用了onStartCommand()方法。
当一个service启动了,它的生命周期就独立于启动它的组件,并且它将不确定的运行在后台,就算启动它的组件已经被销毁了。所以,service应该在工作完成的时候调用stopSelf()来停止自己,或者另一个组件调用stopService()来停止它。
一个应用程序组件像activity可以通过调用startService()来启动service,并且传递包含任何数据的Intent对象给Service使用。service将在onStartCommand()方法中接收到这个Intent。
举个实际的例子,要求一个activity保存一些数据到线上的数据库。这个activity可以启动一个相伴的servie,并且传递那些需要被保存的数据通过startService()方法。service将在onStartCommand()方法中接收到Intent,连接至互联网,并且完成数据传输的操作。当操作完成的时候,service将销毁自己。
警告:默认情况下,service是运行在应用程序的主线程里,所以,当用户在于你的应用程序交互的时候,你的service进行一些高消耗或者耗时的操作,这有可能会降低activity的性能。为了避免这种情况发生,你应该在service中开启一个新的线程。
传统上,这里有两个类可以供你来创建自己的service:
Service
这是所有service的基类。当你继承这个类的时候,你需要创建新的线程来完成你在service里面的操作。因为service默认是在主线程中运行的。这可能会降低你activity的性能。
IntentService
这是Service的子类,使用的工作线程挨个去处理所有的请求。如果你不要求你的service同时去处理多个任务的话,这个是最好的选择。你所要做的就是实现onHandleIntent()方法,这个方法将会获取每一个请求它的Intent,并在后台完成工作。
接下来的部分描述了你如何实现这两种service。
继承IntentService类
因为大部分的started service都不需要同时处理多个请求(实际上同时处理多个请求是很危险的),所以你继承IntentService来实现自己的Service是最好的选择。
IntentService将会完成以下事情:
- 创建一个区别于主线程的工作线程来处理从onStartCommand()获取到的所有的intent。
- 创建一个工作队列,一次只传递一个Intent给onHandleIntent()方法去处理,所以你不必担心多线程的问题。
- 自动的在所有工作完成后停止,所以你不必调用stopSelf()。
- 提供了返回null的onBind()方法的默认实现。
- 提供了发送Intent到工作队列的onStartCommand()方法的默认实现。
下面是一个IntentService的实现:
publicclassHelloIntentServiceextendsIntentService{ /** * 一个构造方法是必须的,它必须调用父类的IntentService(String),其中参数是工作线程的名字 */ publicHelloIntentService(){ super("HelloIntentService"); } /** * IntentService将在工作线程中调用这个方法,当方法return的时候,IntentService就会适当的停掉service。 */ @Override protectedvoid 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()方法必须要返回一个默认的实现(那里面处理了intent如何被传递到onHandleIntent()):
@Override publicint onStartCommand(Intent intent,int flags,int startId){ Toast.makeText(this,"service starting",Toast.LENGTH_SHORT).show(); returnsuper.onStartCommand(intent,flags,startId); }
除了onHandleIntent()之外,只有一个方法不需要你去调用其父类的方法,那就是onBind()(但如果你是用的绑定的方法来使用service的话,你就必须实现它)。
在下面的部分,你将看到如何继承Service类来实现一个同样的Service,代码会稍微多一些,但你要同时处理很多请求的话,可能就需要这么做了。
继承Service类
正如你说看到的,在上一块内容,通过使用IntentService非常简单的实现了一个Started Service。然后,你想要你的service去处理一个多线程(就是说请求不是在一个队列里面的),你可以继承Service来处理每一个intent。
作为比较,下面的代码使用Service类实现了一个与之前用IntentService类一样的功能。那就是,对每一个请求,它用一个工作线程去完成工作,进程每次只处理一个请求。
publicclassHelloServiceextendsService{ privateLooper mServiceLooper; privateServiceHandler mServiceHandler; // Handler that receives messages from the thread privatefinalclassServiceHandlerextendsHandler{ publicServiceHandler(Looper looper){ super(looper); } @Override publicvoid handleMessage(Message msg){ // Normally we would do some work here, like download a file. // For our sample, we just sleep for 5 seconds. long endTime =System.currentTimeMillis()+5*1000; while(System.currentTimeMillis()< endTime){ synchronized(this){ try{ wait(endTime -System.currentTimeMillis()); }catch(Exception e){ } } } // 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 publicvoid 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 =newHandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // Get the HandlerThread's Looper and use it for our Handler mServiceLooper = thread.getLooper(); mServiceHandler =newServiceHandler(mServiceLooper); } @Override publicint 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 publicIBinder onBind(Intent intent){ // We don't provide binding, so return null returnnull; } @Override publicvoid onDestroy(){ Toast.makeText(this,"service done",Toast.LENGTH_SHORT).show(); } }
正如你说看到的,这里做了更多的工作相对于使用IntentService。
然后,因为是自己来处理onStartCommand()方法,所以你可以同时处理多个请求。这里例子并没有显示这一点,但如果你想要这样做的话,你可以为每一个请求创建一个新的线程,让它们在自己线程里运行(而不是等着之前的任务完成)。
注意onStartCommand()方法必须返回一个整型,这个整型描述了当系统杀掉service的时候,应该如何处理它(如前面所讨论的,IntentService已经为你默认的实现了,虽然你可以去修改它)。onStartCommand()的返回值必须是以下几个常量之一。
START_NOT_STICKY
如果系统在onStartCommand()返回之后杀掉了service,不会重新创建service,除非这里有一个挂起的intent传递过去。当你的应用程序能够简单的重新开始未完成的工作,这是最安全的方式去避免在不必要的时候运行你的service。
START_STICKY
如果系统在onStartCommand()返回之后杀掉了service,会重新创建service并且调用onStartCommand(),但不会重新传递最后一个Intent。取而代之的,系统带着一个空的intent去调用onStartCommand(),除非这挂起的intent去启动service,在这种情况下,那些intent将会被传递。这对媒体播放器来说非常的合适(或者其它相似的service),因为它们不需要执行指令,但是会一直运行,并且等待新的任务。
START_REDELIVER_INTENT
如果系统在onStartCommand()返回之后杀掉了service,会重新创建service并且调用onStartCommand(),传递最后一个Intent给service。任何挂起的intent会在轮到它的时候被传递。这很适合去执行哪些可以被快速开始的任务,比如下载一个文件。
关于更多返回值的细节,可以查看每个常量的文档。
启动Service
你可以从一个Activity或者其它应用程序组件启动service,通过传一个Intent(指定哪个service被启动)给startService()方法。Android系统会调用service的onStartCommand()方法接受传过来的Intent。(你不应该直接调用onStartCommand()。)
比方说,在前面的例子里(HelloService)一个Activity可以启动示例service通过使用一个明确的Intent来调用startService()。
Intent intent =newIntent(this,HelloService.class); startService(intent);
startService()方法马上就返回了,然后Android系统调用了service的onStartComand()方法。如果service从来都没有运行过,系统会先调用onCreate(),然后是onStartCommand()。
如果service没有提供绑定,那么startService()是应用程序组件与service之间唯一的传递Intent的方法。然后,如果你想要service把结果返回,启动service的客户端可以为broadcast创建一个PendingIntent(通过使用getBroadcase()方法获取)然后传递Intent给service,service可以使用broastcast来传递结果。
多个请求去启动service就相当于多次调用service的onStartCommand(),然后只有一个去停止service的请求(通过stopSelf()或stopService())是必须的。
停止Service
一个started service必须自己去管理自己的生命周期。也就是说,系统不会停止或者销毁service,除非系统必须要恢复系统的内存,并且service会在onStartCommand()返回之后继续运行。所以service必须通过自己调用stopSelf()或者其他的组件调用stopService()来停止它。
一旦调用stopSelf()或者stopService()之后,系统将尽早的停止service。
然而,如果你的service在onStartCommand()中同时处理多个请求,那么当你完成一次操作之后,你不应该停止它,因为你可能又会接收到新的请求(在第一个请求结束的时候很可能会结束第二个请求)。为了避免这个问题,你可以使用stopSelf(int)来确保结束的总是最近一次的请求。也就是说,当你调用stopSelf(int)的时候,你传递的启动请求的ID(这个startId由onStartCommand()传递过来)将关联你想要结束的service。然后如果你的service在你调用stopSelf(int)之前接收到了新的请求,这个ID将不会被匹配到,service也不会被结束。
警告:在service完成工作之后把它销毁这一点非常重要,应尽量避免浪费系统资源和电量。如果有必要的话,其他组件可以调用stopService()来停止service。虽然你使用的是绑定的service,但如果它曾调用了onStartCommand()方法,你就必须自己去结束它。
关于更多service生命周期的细节,请参考下面的Managing the Lifecycle of a Service。
创建一个Bound Service
一个Bound Service允许应用程序组件通过调用bindService()来绑定它,为了是创建一个长时间保持的连接(这种方式下,通常就不允许组件通过startService()来启动它)。
当你想要在service与activity或者其它组件之间交互或者通过进程间通信开放自己应用的一些功能给其他的应用,你应该创建一个bound service。
想要创建一个bound service,你必须实现onBind()回调方法去返回一个IBinder对象,这个对象定义了与service通信的接口。其它应用程序组件可以调用bindService()来获取接口,然后开始调用service里的方法。service将仅存在与组件绑定它的时候,所以当没有任何组件绑定它的时候,系统就会销毁它(你不需要像停止started service那样停止一个bound service)。
创建一个bound service,第一件事情,你必须定义一个接口来指定客户端与service该如何通信。这个在service与客户端之间的接口必须是IBinder的一个实现,它是你的service必须在onBind()回调方法里返回的。一旦客户端接收了这个IBinder,它就可以与service进行通信了。
多个客户端都可以马上绑定service。当一个客户端完成了与service的交互,它就调用unbindService()来解绑,一旦没有任何客户端绑定service,系统就会销毁掉这个service。
有很多种实现bound service的方式,实现的方法都比started service复杂,所以bound service将在单独的文档中进行讨论。
给用户发送通知
一旦开始运行,service可以通过Toast Notification或者Status Bar Notification来通知用户。
一条toast通知是一条消息,会在当前的窗口上显示一会,然后就消失了,当一条状态栏通知在状态栏里提供一个图标和信息,用户可以点击它来执行一个操作(像启动一个activity)。
通常,状态栏通知是最好的方式去显示后台的一些工作完成了(像文件下载完成),然后用户可以去操作它。当用户从可展开的视图中选择一个通知,通知将可以启动一个activity(像查看一下下载好的文件)。
查看Toast Notification和Status Bar Notification开发指南来获取更多信息。
在前台运行的Service
在前台运行的service是考虑到用户会经常的与之交互,并且最好不要因为低内存的时候被系统杀掉。一个前台service必须在状态栏上提供一个通知,将被放在“运行中”的标签里面,这意味着通知将不会被消除,除非service被停止或者从前台移除。
比方说,一个音乐播放器,应该在前台播放音乐,因为用户可能会经常的使用它,状态掉里的通知应该显示当前播放的歌曲并且允许用户通过状态条里的通知来启动activity。
想要让你的service在前台运行,调用startForegroud()来完成。这个方法需要两个参数,一个整型用于唯一的标识符指向通知,另一个是在状态条里显示的Notification对象。举个例子:
Notification notification =newNotification(R.drawable.icon, getText(R.string.ticker_text), System.currentTimeMillis()); Intent notificationIntent =newIntent(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);
警告:传递给startForegound()的整型ID不能是0。
调用stopForeground()来将service从前台移除。这个方法需要一个布尔类型的参数,指明是否同时移除状态栏的通知。这个方法不会停止service。然而,如果你停止在前台运行的service,通知也会被移除。
更多有关通知的信息,请参考Creating Status Bar Notifications。
管理Service的生命周期
service的生命周期比activity的简单许多。然而,你需要更加关注service是如何被创建以及销毁的。因为service在后台里运行将不会被用户注意到。
一个service的生命周期,从它被创建到它被销毁,有两条不同的路径。:
A Started Service
通过其它组件调用startServie()启动的service。这个service将会不明确的运行着,并且必须调用stopSelf()来停止它,其它的组件也可以调用stopService()来停止它。当service被停止时,系统就会销毁它。
A Bound Service
通过其它组件调用bindService()来启动的service。客户端将会通过IBinder接口与之通信。客户端可以调用unbindService()来断开与service的连接。多个客户端都可以绑定在同一个service上,当它们解绑时,系统就会销毁它。
这两条路径并不是完全独立的。也就是说,你可以绑定一个已经通过startService()启动的service。比方说,一个后台音乐服务调用startService()来启动,接受的参数是一个指定了音乐来播放的Intent。随后,当用户想去执行一些控制或者看看当前歌曲的信息的时候,一个activity可以通过bindService()绑定service。在这种情况下,stopService()或者stopSelf()都不会停止service,直到所有的客户端解绑。
实现生命周期的回调
像activity一样,service有各种生命周期回调方法,你可以实现他们来监控service状态的改变,并且在合适的时机执行合适的操作。接下来的代码骨架示范了每一个生命周期方法:
publicclassExampleServiceextendsService{ 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 publicvoidonCreate(){ // The service is being created } @Override publicintonStartCommand(Intent intent,int flags,int startId){ // The service is starting, due to a call to startService() returnmStartMode; } @Override publicIBinderonBind(Intent intent){ // A client is binding to the service with bindService() returnmBinder; } @Override publicbooleanonUnbind(Intent intent){ // All clients have unbound with unbindService() returnmAllowRebind; } @Override publicvoidonRebind(Intent intent){ // A client is binding to the service with bindService(), // after onUnbind() has already been called } @Override publicvoidonDestroy(){ // The service is no longer used and is being destroyed } }
笔记:与activity的生命周期方法不同,你没必要调用这些方法的父类实现。
通过实现这些方法,你可以监控到两个内嵌于service生命周期的循环。
service的完整生命时间发生在onCreate()调用时与onDestroy()返回之间。像activity一样,service在onCreate()里面做一些初始化的操作,在onDestroy()里面释放掉一些资源。比方说,一个音乐播放的service可以创建播放音乐的线程在onCreate()里面,然后在onDestroy()中停止这个线程。
onCreate()和onDestroy()方法会在所有的service中调用,不管它是通过哪种方式创建的。
service的活动生命时间开始于onStartCommand()或者onBind()。这两个方法都是处理传递过来的Intent。如果service是started的,活动生命时间与完整生命时间同时结束。如果service是绑定的,活动生命时间在onUnbind()返回时结束。
笔记:虽然一个Started service是通过调用stopSelf()或者stopService()来停止的,但没有单独的回调方法(没有onStop()这个回调)。所以,除非service是绑定到客户端上,否则的话系统将会销毁那些停止的service。
上面的图表示了service里面几个典型的回调方法。虽然图示把两种不同的service分开表示,但是请记住,不管是哪种方式启动的,它都可以被客户端绑定。所以通过onStartCommand()初始化的service,仍然可以接受客户端的绑定。