服务Services
Service是一种应用组件,它可以在后台长时间地运行并且没有用户界面。其他的应用组件可以启动一个service,并且这个service会一直在后台运行下去,不论用户是否切换到了其他的应用。另外,其他的组件可以绑定一个service来进行交互,甚至进行跨进程通信(IPC)。例如服务可以处理网络传输,播放音乐,处理文件IO,或者和content provider进行交互,这些都是在后台进行。
服务有下面两种基本形式:
Started方式:
当一个组件(比如一个activity)使用startService启动一个服务的时候,就是用的started方式。一旦启动,服务就可以在后台无限期地运行,即使启动了service组件已经被销毁。通常一个service都是执行一个单一的任务,并且不向它的调用者返回结果。例如,它可以从网上下载,或者上传文件。当业务完成的时候,服务应该自发地结束。
Bound方式:
当一个应用组件使用bindService启动service的时候,就是bound方式。bound方式的service 可以以CS的方式进行交互,可以实现组件访问服务,发送请求,获取结果,甚至使用IPC进行跨进程通信。bound的service的生命周期等于其bound的组件的生命周期。一次可以有多个组件bind service,当所有都解绑的时候,service就被销毁了。
尽管前文将两种service分别讲解,但是你可以让一个service同时具有这两种特征,就是运行的时间无限制,同时被bind。你只要实现几个回调的方法就可以实现:onStartConmmand()可以让组件启动service,onBind方法可以实现bind。
无论你的service是怎样启动的,start,bind还是二合一,其他的应用组件都可以访问这个service,甚至是来自不同的应用的访问。访问的方式和组件使用activity的方式一样,使用一个Intent。但是你可以在manifest声明一个service为private,来阻塞同其他应用中的访问。更多的内容请参见Decaring the service in the manifest。
警告:service运行在宿主进程的主线程当中,它不会创建它自己的线程,而且如果不进行声明,它也不会在其他进程中。这就意味着如果你要在service中进行一些高CPU负载或者阻塞的操作,比如说后台播放MP3或者网络访问,你应该service中创建新的线程来完成这些工作。使用了分离的线程,你可减小应用无响应(ANR)的错误的风险,而且主线程可以专注于你的activity的交互。
基本知识 The Basics
应该通过创建一个继承自service的类来创建service(或者已经存在的子类)。在构建中,你需要根据需求重写其生命周期方法,并且提供组件bind service的机制。你需要重写的最重要的回调函数是:
onStartCommand()
这个函数当一个组件,例如activity调用startService()来启动service的时候,由系统进行调用。一旦这个方法执行,那么service就可以在后台中无限期地运
行。如果你创建了这个service,那么你要负责在它的工作结束的 时候将其止,通过调用stopSelf()或者stopService()。如果你仅仅想要提供bind功能,那么你不需要实现这个方法。
onCreat()
系统在service第一次创建的时候会调用这个方法,来进行一些一次性的步骤操作(在调用onStratCommand 和onBind()之前)。如果这个service已经在运行了,这个方法就不会被调用了。
onDestroy()
当service不再使用而被销毁的时候,系统会调用这个方法。你应该在这个方法中国来清理例如线程和注册的监听者和接收者等等的资源。这是service接收的最后的
一个调用方法。
*******
绿色侧栏:应该使用service呢,还是使用线程呢?
service仅仅是一个在后台中运行的组件,在你的应用没有和用户交互的时候仍然保持运行。你只有在有这样的需求的时候才需要创建一个service。
如果你只需要在用户和你的应用交互的时候,在主线程之外进行一些操作,那么你仅仅使用线程就可以了。例如你想要在activity运行的时候进行音乐播放,那么你只需要onCreate()中创建一个线程,在onStrat()中启动这个线程,在onStop()中结束这个线程。应该考虑使用AsyncTask或者HandlerThread来替代传统的Thread类。更多的信息请参见Processes and Thread文档。
要强调的是,你的service在默认情况实在你的应用的主线程中运行的,所以你仍然需要创建一个新的线程来处理高负荷或者阻塞的业务。
********
如果一个组件通过startService()来启动Service(与此同时会调用onStartCommand),这样的话service会一直运行下去,直到它调用stopSelf()方法自己结束,或者另一个组件中调用stopService来结束这个服务。
如果一个组件通过调用bindService()来创建服务(不会调用onStartCommand),那么这个service就会运行直到bind的组件结束bind。一旦一个这样service解除了所有组件的绑定,那么系统就会销毁service。
当系统内存低并且系统需要回收资源提供给用户正在关注的activity的时候,系统就会强制停止service。如果service和一个用户正在关注的activity绑定,那么久不容易被回收。如果这个service被声明为前台(run in background),那么这个service就几乎不会被回收。如果service被启动并且运行了很久,那么系统就会随着时间降低它在后台任务列表中的地位,更有可能被系统回收——如果你的service启动了,你必须设计让它优雅地被系统重新启动。如果系统杀死了你的service,只要系统的资源重新变得充裕,就会重新启动你的service(尽管还和onStartCommand返回值有关,后面讨论)。关于系统会杀死进程的说明,参见Process
and Threading。
下面将介绍如何创建不同类型的service,以及怎样从其他的组件中进行访问。
在manifest清单文件中声明一个service Declaring a service in the manifest
如同activity和其他组件,你需要在manifest中来声明service。
通过添加<service>元素作为<application>的子元素来声明service。例如:
<manifest ... > ... <application ... > <service android:name=".ExampleService" /> ... </application> </manifest>
更多关于在清单中声明service的内容参见<service>元素的参考,在教程的app manifest中。
在<service>的元素下还有一些属性,比如启动service的权限,和servcie在哪个进程中运行等。android:name属性是一个必须的属性,他指定了service的类名。一旦你发布了应用,你不应该改变这个名字属性,因为一旦这样,你就会破坏显式intent启动或者bind 这个service的代码。(参见博客文章,Things That Cannot Change)。
为了保证你的应用的安全,你应该使用显式intent来启动和bind你的service,同时不要给这个service声明intent filter过滤器。如果考虑到解决启动的service的歧义性的至关重要性,那么你可以给你的service提供intent filter,并且拓展Intent中的组件的名字,与此同时你必须使用setPackage()来设置包,来消除目标service的歧义。
另外,通过设置android:export设置为false来保障你的service只可以在你的应用中访问。这可以有效地阻止其他的应用对于你的service的访问,即使是使用了显式的intent。
创建一个Started方式的service Creating a Started Service
一个 started service是在另外一个组件中通过调用startService()启动的,同时会导致service的onStartCommand方法的调用。
这样的service启动以后,它的声明周期和启动它的组件无关,可以在后台中无限期地运行,尽管启动了这个service的组件已经被销毁。因此,这样service应该在业务结束以后调用stopSelf()自己结束,或者其他的组件中调用stopService()来结束。
一个应用组件,例如一个activity可以通过调用startServie并传入一个Intent参数来指定service,同时传入service需要的使用的数据。service在onStartCommand()方法中获得接收到Intent。
举个例子,假设一个activity需要在一个联机数据库上存储一些数据。activity可以启动一个伴随的service,并且把要存储的数据传入的到intent中传送给startService()中。service在onStartCommand()方法中接收这个intent,连接网络并执行数据库交互。当交互结束,service自发结束,然后被销毁。
********
警告:默认情况下,一个service在声明他的应用的进程中运行,而且在应用的主线程当中。所以当你的service执行高负载或者阻塞的操作的时候,用户正在和acivity交互,那么会降低你的activity的性能。避免这种影响,你应该在service中启动新的线程。
********
通常在创建一个started service的时候,需要拓展下面两个类。
Service
这是所有servcie的基类。拓展这个类的时候,要确保你的所有业务都是在新的线程中完成的,以为service使用的是应用的主线程,默认的设置会降低acitivity的性能。
IntentService
这是一种Service的子类,它使用工作线程来处理所有的启动请求,一次处理一个。如果你不需要你的service来同时处理过个请求,那么这种是最佳的选择。你只需要实现onHandleIntent(),它会处理每一个start 请求的intent,这样你就可以做后台工作了。
下面的章节讲述怎样使用其中的一些类来构建你的service。
拓展IntentService类 Extending the IntentService class
因为大多数的started service都不需要同时处理多请求(这其实是一个危险的多线程情景),这时你使用IntentService 类可能是最好的选择。
IntentService会完成以下业务:
- 接受在onStartCommand()中传入的intent并在主线程之外创建默认的工作线程来执行他们。
- 创建一个工作队列,一次向你构建的onHandleIntent()中传入一个intent,这样你不用担心多线程的问题。
- 在所有的start请求都被处理以后自动定制service,所以你不要调用stopSelf()。
- 提供一个默认返回null的onBind()函数。
- 提供一个默认的onStartCommand()的构造,并发送intent给工作队列,然后发送给你构建的onHandIntent()。
综上所述,你需要做的就是构建onHandleIntent(),来处理client提供的工作。(当然你自己还要写一个service的构造器)
下面是一个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.IntentServiec会使用启动service传来的intent,在默认的工作线程中调用这个方法。
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.这里是要完成的一些业务,比如说下载文件。这里用睡眠5秒演示。
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
}
}
所以你只需要一个构造器,以及onHandleIntent()方法的实现。
如果你决定重写其他的回掉函数,比如说onCreat(),onStartCommand()或者onDestroy(),确保同时调用父类的构造,这样IntnetService才能恰当地处理工作线程的生命。
比如说,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); }
除了onHandlerIntent(),唯一的另一个你不需要在其中调用父类的方法就是onBind()(但是如果你的service允许绑定,你只需实现它)
在下一节中,拓展基类Service怎样构造想通类型的service。里面需要更多的代码,但是对于处理同时的start请求,这种更适合。
拓展Service类 Extentds the Service class
如同你在上一节中看到的那样,使用IntentService让你的started类型的service的构建变得十分简洁。然而如果你需要在service中完成多线程的工作(而不是在工作线程中使用工作队列)。那么可以通过拓展Service类来处理每一个intent。
作为对比,下面的例子处理的表现和上面的IntentService的例子相同。也就是对于每一个start请求,使用工作线程来处理任务,并且一次处理一个请求。
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工作量要多得多。
但是因为你可以自己处理每一个onStarCommand()调用,你可以处理同时处理多个请求。这个例子中并没有提现,但是如果你想要这么做,那么你可以给每一个请求创建一个线程并立刻运行(而不是等待上一个请求完成)。
这里请注意onStartCommand()会返回一个整型值。这个数值表述了在这个服务被杀掉的情况下,系统如何从其启动它(如同上面讲到的,IntentService的默认构建会为你处理这个情况,当然你也可以修改它)。onStartCommand()的返回值一定要是下面几种常量:
START_NOT_STICKY
如果系统在onStartCommand()返回以后杀掉了这个service,那么这个service不会被重新创建,除非还存在要传递的pending intent。在不必要,以及你的应用可以简洁地重新启动未完成工作的情况下,使用这个选项是最安全的,可以避免对你的service的重复运行。
START_STICKY
如果在你的onStartCommand()返回以后service被杀掉,就会重新创建service并重新调用onStartCommand(),但是不会再次传递最后的intent。系统会用一个null intent来调用onStartCommand,除非有一个要启动的service的pending intent,这时,就会传递intent。这适合于音乐播放这种不需要执行命令但是会无限期地运行并等待命令的情况。
START_REDELIVER_INTENT
如果在onStartCommand()返回以后系统杀掉了service,那么就会重新创建service并使用最新的intent 调用onStartCommand()。任何的pending intent也会轮流被传递。这适用于为用户提供一个活跃的功能的service,比如说下载文件这种需要立即被重新开始的。
更多关于返回值的细节,参考每个常量的参考文档。
启动一个Service Starting a Service
你可以通过传入一个Intent(指定要启动的service)参数到startService()方法来从activity或者其他的应用组件中来启动service。Android系统会调用service的onStartCommand()方法并传入Intent。(不要直接调用onStartCommand)
举个例子,例如从activity中启动例子中的service,使用startService()和intent参数。
Intent intent = new Intent(this, HelloService.class); startService(intent);
startService()方法会立即返回,Android系统会调用service的onStartCommand()方法。如果这个service现在没有运行,系统会先调用onCreate(),然后调用onStartCommand()。
如果service不支持bind,那么使用startService和intent就是唯一的应用组件和service进行通信的方式。但是如果你想要service返回一个结果,那么启动service的组件可以创建一个broadcast的PendingIntent(使用getBroadcast()),然后在启动service的intent中传递这个PendingIntent。这样的话service就可以使用broadcast来传递结果了。
多个启动service的请求会导致多个对于service的onStartCommand() 的调用。但是,但是停止service只需要一个stop的请求(通过stopSelf()或者stopService())。
停止一个service Stopping a servcie
一个started service必须自己管理自己生命周期。也就是,系统不会停止或者销毁一个service,除非是为了获得内存空间,这是service会在onStartCommand()返回以后继续运行。所以service必须通过stopSelf自己停止,或者从另一个组件中调用stopService()。
一旦请求停止,使用stopSelf()或者stopService(),系统会尽快立即停止这个服务。
但是,如果你的服务现在正在处理onStartCommand的多个请求,那么那么你在处理完这个start请求以后不要停止这个service。因为你可以已经接受到了一个新的请求(在第一个请求的最后停止服务可能会终结第二个)。为了避免这个问题,你可以使用stopSelf(int)方法来确保你对于service的stop请求总是基于最近的start请求的。也就是当你调用stopService(int),把对应的start请求的id传入进去(conStartCommand方法中传入的startid)。这样如果在你调用stopSelf(int)以前service又接收到了一个新的start请求,那么ID就会不一致,service也不会停止。
******警告*******
在service工作结束以后把它结束掉十分重要,这样可以防止系统资源的浪费和电量的消耗。如果必要的话其他的组件可以使用stopService()方法来结束service。即使你可以bind service,如果service接收了onStartCommand()的调用,你就应该自己来管理结束service。
******************
创建一个BoundService Creating a Bound Service
一个boundService是可以让组件通过调用bindService()来进行bind的service,来创建一个长时间的通信(通常不允许组件使用startService来启动)。
你如果你想要你的组件和service交互,或者向其他的程序暴露你的应用的功能(比如通过IPC),你可以使用bound Service。
要创建一个boundService,你需要实现onBind()回调函数,它返回一个定义了和service交互的通信的接口。其他的应用组件可以调用bindServie()获取接口,并调用其中的方法。这种servcie的存在是为了服务绑定它的应用的组件,所以如果没有组件绑定的时候,系统就会销毁这个service(你不需要如同onStartCommand()方式启动的service那样来停止一个bound的service)。
要创建一个bound service,你必须要做的第一件事就是定义接口来说明客户端是如何访问service的。客户端和service之间的接口,就是实现一个IBinder,它也是你的service通过onBind()方法返回的对象。客户端一旦接收到IBinder对象,就可以开始使用接口和service进行交互了。
多个客户端可以 立刻和servcie bind。如果一个客户端结束了和service的交互,可以通过unbindService()方法来解除绑定。一旦没有对象绑定这种是service,那么他就被系统销毁。
有多中方式可以实现一个bound service,并且它的实现比启动一个service要复杂的多,所以boundService的更多的讨论在 Bound Service文章当中。
向用户发送通知 Sending Notifications to the User
一旦开始运行,一个service就可以使用Toast Notification或者 Status Bar Notification来通知使用者。
toast提醒是一个在当前窗口上出现的提示,一段时间后消失,而状态栏提醒在状态栏提产生一个图标和一个消息,用户可以选择他们并进行一定的处理(比如开启一个activiy)。
通常,状态栏对于完成任务的后台是一个最好的提醒技术(比如说完成了文件的下载)。当用户下拉通知中选择了一个提醒,这个提醒就可以启动一个activity(比如说下载了的文件的视图)。
更多的教程请查看 Toast Notification或者Status Bar Notification。
在前台运行一个Service
一个前台的service是一种用户可以察觉的到,并且当内存低的时候不会被回收的一种服务。一个前台服务必须要在状态栏显示一个通知,他一般会被设置一个“正在运行”的标题,这意味着这个通知不会被清除,除非service停止了或者同前台移除了。
例如,一个音乐播放器使用service进行播放就应该设置为一个前台的服务,因为用户需要一直清除他们的操作。在状态栏的提示可能会指明正在播放的歌曲,并允许用户启动一个activity来和音乐播放器进行交互。
如果想要你的service在前台运行,请调用startForegound()。这个方法获取两个参数,一个整数唯一标识了通知,还有一个配合状态栏的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_ID, notification);
****警告******
传递给startForegound的ID不可以是0。
***************
想要从前台移除servcie,调用stopForegound()方法。这个方法获取一个boolean类型,指明是否同时将状态栏的通知也移除。这个方法不会结束掉service。但是你如果在这种service在前台运行的时候停止service,那么通知也会被一同移除。
更多的关于你notification的内容请参见Creating Status Bar Notification。
管理service的生命周期 Managing the Lifecycle of a Service
service的生命周期要比activity简单地多。但是关注service是怎样被创建和销毁的更加重要,因为是在后台运行的,其行踪用户无法察觉。
service的生命周期,从他被创建和被销毁可以遵循下面两种路径:
- started类型的service
这种service在另一个组件调用startService的时候被创建。接下来service就可以无限期地运行了,只能通过调用stopSelf停止。也可以是另一个组件调用stopService啦进行停止。当service停止了,系统就会销毁它。
- bound类型的service
当另一个组件(客户端)调用bindService的时候,这种的service就会被创建。客户端接着就可以通过一个IBinder接口来和service通信。客户端可以通过调用unbindService()来结束通信。多个客户端可以绑定同一个service,当所有都解绑的时候,系统就会销毁service(不需要自己结束)。
这两种路径并不是完全分离的。也就是,你可以绑定一个已经使用startService()启动的服务。例如,一个后台的音乐服务可以通过startService(),传入一个指定播放什么音乐的Intent来进行创建。接下来,可能当用户想要控制播放器或者想获得一些当前播放的音乐的信息的时候,一个activity可以通过调用bindService来绑定service。这种情况下,stopService()或者stopSelf(),这种情况下stopService或者stopSelf就不能真正地停止一个service,直到所有的客户端都解绑它们才可以。
实现生命周期回调
如同一个activity,service拥有生命周期回调函数,你可以通过实现它们来检测service的状态变化,并且在合适的时候运行一些业务。下面的service框架证明了每一个生命周期方法。
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 voidonCreate
() { // The service is being created } @Override public intonStartCommand
(Intent intent, int flags, int startId) { // The service is starting, due to a call tostartService()
return mStartMode; } @Override public IBinderonBind
(Intent intent) { // A client is binding to the service withbindService()
return mBinder; } @Override public booleanonUnbind
(Intent intent) { // All clients have unbound withunbindService()
return mAllowRebind; } @Override public voidonRebind
(Intent intent) { // A client is binding to the service withbindService()
, // after onUnbind() has already been called } @Override public voidonDestroy
() { // The service is no longer used and is being destroyed } }
******注意******
与activity的生命周期的回调方法不同,你不需要调用超类的回调方法的实现。
******************
图2:service的生命周期。左边的图展示了使用startService()启动的service,右边的图展示了使用bindService创建的service的生命周期。
通过实现这些方法,你可以监控监控service生命周期的内部循环:
- entire lifetime:从service的onCreate被调用,到onDestroy()返回的这段整个的生命时间。如同一个activity,一个服务在onCreate中进行一些初始的步骤,在onDestroy()中释放所有的资源。例如,一个音乐重放service可以在onCreate创建一个播放音乐的线程,并且在onDestroy()中结束这个线程。
onCreate和onDestroy方法可以为所有的services调用,不论是使用startService()创建的还是使用bindService()创建的。
- active lifetime:从onStartCommand()或者onBind()被调用开始的生命时间。两个方法都是分别处理从startService()h和bindService()传来的Intent。
如果一个服务是started的,active lifetime 结束的时间就是entre lifetime结束的时间(这种service在onStartCommand()返回以后仍然活跃)。如果service是bound的,那么active lifetime在onUnbind()返回时结束。
******提示********
尽管一个started service可以通过stopSelf()或者stopService()结束,但是没有一个对应的回调函数(没有onStop回调函数)。所以除非这个service有绑定,否则如果service停止,就会被销毁——onDestroy()是接收到的唯一的回调函数。
*******************
图二展示了service的典型的回调方法。尽管途中将两种创建servcie的方法分开说明,但是你要清楚任何service,都可以用客户端绑定。也就是说使用onStartCommand()(客户端的startService())方式启动的service,也可以通过接受onBind()回调(当客户端调用bindService()的时候)。
更多关于创建一个可以接受绑定的service的内容,请参见Bound Service文档。其中包含更多关于onRebind()回调函数的内容,在Manageing the Lifecycle of a Bound Service一章中。