Android Service 详解

jopen 11年前

一个Service也是一种应用程序组件,它运行在后台以提供某种服务,通常不具有可见的用户界面。其它的应用程序组件可以启动一个Service,即使在用户切换到另外一个应用程序后,这个Service还是一直会在后台运行。此外,一个应用程序也可以绑定到一个Service然后使用进程间通信(IPC)方式与Service之间发生交互。例如一个Service可以处理网络事物,播放音乐,读写文件或者读写ContentProvider,所以这些都在后台运行。

   一个Service可以以以下两种形式存在:

·        Started(启动) 在一个应用程序以startService() 来启动一个Service时,这个Service将处于“Started”状态。一旦启动,这个Service可以在后台一直运行下去,即使启动它的应用程序已推出。通常,一个处于“started”状态的Service完成某个功能而不会给启动它的应用程序组件返回结果。比如,这个服务(Service)可能是上载或是下载一个文件,当任务完成后,服务自行退出。

·        Bound (绑定) 当一个应用程序组件以bindService() 绑定一个Service时,这个Service处于“Bound”状态。处于“Bound”状态的Service提供了一个客户/服务(C/S)调用接口支持其它应用程序组件和它交互,比如发生请求,返回结果,或者使用IPC完成跨进程间通信。一个处于“Bound”的Service只能和与其绑定的应用程序一起运行。多个应用程序组件可以绑定到同一个Service。当所有绑定的应用程序组件都退出绑定后,被“绑定”的Service将被销毁。

  对于一个Service来说,它可以是“Started”,“Bound”或者同时处于两种状态。其它任一应用程序组件(比如一个Activity)都可以使用这个Service,即使其它应用程序组件是在不同的应用程序中。当然你可以把Service定义为私有的,这样其它应用程序就无法使用你定义的Service。

  要注意的是,一个Service运行在其宿主进程的主线程中--服务不会自己创建新的线程也不运行在独立的进程中(除非你特别指明)。这意味着,如果你的Service需要完成一些很耗CPU资源的操作(比如播放MP3,或者使用网络),你应该在Service中创建新的线程来完成这些工作,从而降低出现程序无响应(ANR)的风险。

Service基础

   要创建一个Service,你必须从Service或是其某个子类派生子类。在你的Service子类实现中,你需要重载一些方法以支持Service重要的几个生命周期函数和支持其它应用组件绑定的方法。下面给出几个需要重载的重要方法:

·        onStartCommand()   Android系统在有其它应用程序组件使用startService()请求启动Service时调用。一旦这个方法被调用,Service处于“Started”状态并可以一直运行下去。如果你实现了这个方法,你需要在Service任务完成时调用stopSelf()或是stopService()来终止服务。如果你只支持“绑定”模式的服务,你可以不实现这个方法。

·        onBind()  Android系统中有其他应用程序组件使用bindService()来绑定你的服务时调用。在你实现这个方法时,你需要提供一个IBinder接口以支持客户端和服务之间通信。你必须实现这个方法,如果你不打算支持“绑定”,返回Null即可。

·        onCreate()  Android系统中创建Service实例时调用,一般在这里初始化一些只需单次设置的过程(在onStartCommand和onBind()之前调用),如果你的Service已在运行状态,这个方法不会被调用。

·        onDestroy() Android系统中Service不再需要,需要销毁前调用。在你的实现中你需要释放一些诸如线程,注册过的listener,receiver等,这是Service被调用的最后一个方法。

如果一个Service是由startService()启动的(这时 onStartCommand()将被调用),这个Service将一直运行直到调用stopSelf()或其它应用部件调用stopService()为止。

如果一个部件调用bindService()创建一个Service(此时onStartCommand()不会调用),这个Service运行的时间和绑定它的组件一样长。一旦其他组件解除绑定,系统将销毁这个Service。

Android系统只会在系统内存过低且不得不为当前活动的Activity恢复系统资源时才可能强制终止某个Service。如果这个Service绑定到一个活动的Activity,基本上不会被强制清除。如果一个Service被申明成“后台运行”,就几乎没有被销毁的可能。否则的话,如果Service启动后并长期运行,系统将随着时间的增加降低其在后台任务中的优先级,其被杀死的可能性越大。如过你的Service是做为“Started”状态运行,你必须设计后如果在系统重启服务时优雅退出。如果系统杀死你的服务,系统将在系统资源恢复正常时重启你的服务(当然这也取决于onStartCommand()的返回值)。

在Manifest中申明Service

    和Activity一样,你必须在Manifest文件中申明应用中使用到的Service。为了声明一个Service,你需要定义<application>的子元素<service>,比如:

<manifest ... >    ...    <application ... >        <service android:name=".ExampleService" />        ...    </application>  </manifest>


        <service>还包括一些其它属性,android:name是唯一一个必须定义的属性,它定义了Service的类名。

和Activity一样,你可以为Service定义Intent Filter以支持其它部件隐式调用该服务。如果你只打算在你自己的应用中使用某个Service,那么就不要定义任何Intent Filter。在这种情况下,你必须明确指明Service的类名来启动这个Service。此外,你可以通过设置android:exported属性为false来明确说明该Service为私有,即使你为Service定义了Intent Filter ,其它应用也无法使用你的Service。

 创建一个“Started”的Service

当其它应用程序组件使用startService()来启动Service后,这个Service就成为“Started”的Service。这时Service的onStartCommand () 回调函数将会被调用。

当一个Service以startService()启动后,处于“Started”状态,其生命周期独立于启动这个Service的其它应用程序组件,并且可以在后台无限期运行,即使启动它的其它应用程序组件已经退出。在这种情况下,Service可以调用stopSelf()或者其它程序部分调用stopService()来结束这个Service。

一个应用程序组件比如一个Activity可以通过传入Intent使用startService()来启动一个Service,这个Intent指明需要的服务并包含了Service需要的数据。Service在onStartCommand回调函数中可以访问这个Intent。

要注意的是,Service缺省和申明它的应用程序使用同一个进程并且运行在主线程中,因此如果你的Service比较耗时的话,那么这个Service会影响到应用的用户响应性能。为避免这种现象,你应用在Service中创建新线程。

你可以使用下面两个基类来创建一个“Started”的Service。

·        Service 这是所有Service的基类,当你派生这个类时,使用新创建的线程来完成Service需要完成的工作非常重要,这是因为Service缺省也会使用你的应用程序的主线程,这可能会影响你应用程序的响应性能。

·        IntentService 为Service的一个子类,它会使用一个工作线程来处理所有的请求,每次处理一个请求。这在你无需实现并行处理多个请求时是最好的选择。你只需重载onHandleIntent()方法,这个方法接受每个请求,从而你可以在后台完成所需任务。

 派生IntentService类

因为大部分“Started”的Service不需要并行处理多个请求,这时最好的选择是派生自IntentService。

类IntentService可以完成以下工作:

·        创建一个工作线程来处理所有发送到onStartCommand()的请求(Intent),从而和应用的主线程分开。

·        创建一个工作队列来逐个处理每个请求,从而无需考虑多线程编程。

·        在处理完所有请求后中止Service的运行,你无需调用stopSelf()来终止Service。

·        提供onBind()的缺省实现,返回null。

·        提供onStartCommand()的缺省实现,将接受到的Intent发送到工作队列中,然后调用你的onHanleIntent()实现。

因此,对于你来说,只需要提供onHandleIntent()来处理发过来的Intent,当然你可能需要实现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.    */   @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.    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(),你必须返回基类的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类

   从上面可以看到使用IntentService可以让实现“Started”的Service变的非常简单,然而如果你需要支持使用多线程并行处理多个请求(而非使用工作队列来依次处理请求),这时你就需要从Service类来派生。

   作为比较,下面的例子使用Service实现和上面使用IntentService同样的功能,也是使用一个工作线程来逐个处理每个请求,可以看成是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.            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    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()方法必须返回一个整数,这个返回值定义了如果Android系统在杀死这个Service后又需要使用这个Service后的行为。这个返回值可以有下面几种情况:

·        START_NOT_STICKY 如果Android系统在onStartCommand() 返回后杀死了这个Service,系统不会重新创建这个Service,除非它是一个Pending Intent。这是在你的应支持简单的重启没有完成的任务避免无必要的Service运行时ic最好的选项。

·        START_STICKY 在Android系统在onStartCommand () 返回后,选择重新创建这个Service并调用Service的onStartCommand () 方法,但不会重新发送上一次的请求。Android系统使用空Intent调用onStartCommand,除非它是一个PendingIntent。这种情况适用于一些媒体播放器服务。

·        START_REDELIVER_INTENT在Android系统在onStartCommand() 返回后,选择重新创建这个Service并调用Service的onStartCommand () 方法,并且重新发送上一次的请求。这适用一些比较活跃地进行某些工作的服务,需要立即恢复活动比如下载文件的服务。

 启动Service

   在Activity或是其他应用程序组件可以通过传递Intent调用startService()方法来启动一个Service。Android系统然后会调用Service的onStartCommand()方法并传入这个Intent对象。

   比如,一个Activity可以通过明确指明调用的服务来调用前面创建的HelloService:

Intent intent = new Intent(this, HelloService.class);  startService(intent);


    startService() 调用后立即返回,Android系统调用Service的onStartCommand(),如果这个Service之前没有运行,系统还将先调用Service的onCreate()方法,然后再调用onStartCommand()。

   如果Service没有提供绑定支持,那么使用startService()是唯一可以启动Service的方法。然而,如果你需要Service返回一个结果,那么客户端可以通过创建一个用于广播的延迟Intent(PendingIntent)然后通过Intent发送请求到Service。Service可以利用这个广播(Broadcast)返回结果。

   多个启动Service请求将导致多次调用Service的onStartCommand()方法。然而只要一个stopSelf或stopService来停止一个Service的运行。

 停止Service

一个“Started”的Service必须管理自己的生命周期,也就是说,除非系统资源不足,Android系统不终止或停止一个Service。因此Service必须调用stopSelf () 方法来终止自身的运行,或者其它部件可以通过stopService () 方法来中止一个Service的运行。

一旦Service被请求终止,系统将尽快销毁这个Service。

但是,如果你的Service需要在onStartCommand()并行处理多个请求,那么你不应再处理完一个请求后就停止Service的运行,因为你可能还会接受到新的请求(在第一个请求处理完就结束则会终止第二个请求的执行)。为避免这种情况,你可以使用带参数的stopSelf(int) 来确保终止Service的请求总是对应于最近的请求。也就是说,在调用stopSelf(int) 时,你通过传入请求的Id (传给onStartCommand 方法的 startId )来终止对应的请求处理。那么如果Service在调用stopSelf (int) 前接受到一个新的请求,那么Id将不会匹配,因此Service也就不会结束。

创建一个支持“绑定”的Service

一个支持“绑定”的Service是指运行其它应用程序之间通过调用bindService() 来绑定到它的Service。支持“绑定”的Service在设计时通常不允许直接使用startService() 来启动它。

在Activity或其它组件需要和一个Service发生交互时,你应该创建一个“绑定”的Service,在这个Service中你可以通过进程间通信接口(IPC)来提供可供其它应用程序组件使用的功能接口。

为了创建一个支持“绑定”的Service,你需要实现onBind() 回调函数并返回一个IBind接口对象,这个对象提供了其它部分可以访问这个Service的服务接口。其它应用程序组件然后可以通过bindService () 方法获得Service的服务接口对象,然后使用这个对象的方法来调用服务。通常“绑定”的Service的运行周期和绑定它的其它应用程序组件的生命周期是一样的。因此如果这个Service不再有应用程序组件与之绑定,Android系统会自动销毁这个Service,而你自己无需停止这个Service。

为了创建一个支持“绑定”的Service所需做的第一件事是定义可供客户端调用的服务接口。这个接口必须为一个IBinder接口,并且你的Service必须通过onBind () 返回这个接口对象。一旦客户端获得这个IBinder接口,就可以通过这个接口对象来使用服务。

多个客户端可以同时绑定到同一个Service,当一个客服端使用好所需的服务后,调用onbindService() 来解除与Service之间的绑定关系。当一个Service没有和它绑定的客户端后,Android系统销毁这个Service。

给用户发送通知

   Service通常是在后台运行,它可以通过Toast 通知或是状态条通知来提醒用户发生了某种事件。

   一个Toast通知为覆盖在当前屏幕上短时间显示的消息,而状态条通知可以在状态条以图标和文字的方法显示,用户可以选择是否查阅这个通知并作出处理(比如启动一个Activity)。

   一般来说,使用状态条通知是在某个后台工作完成后(比如文件下载完毕)通知用户的最好方法。用户可以在扩展后的通知窗口选择某个通知,然后可以点击是否启动一个Activity(比如查看下载好的文件)

在前台运行Service

通常Service在后台运行,但有时某些Service是用户明确知道在运行,即使在系统资源不足时,也不该做为销毁的候选Service,这种Service可以设置成在前台运行,在前台运行的Service必须在状态条显示通知,除非Service停止或者转入后台,这种通知不可以被忽视。

比如,如果一个音乐播放器使用Service来播放,这个Service应该被放在前台运行,因为用户很清楚的知道音乐在播放,而在状态条显示的通知理当显示当前播放的乐曲并且允许用户启动一个Activity来操作这个音乐播放器。

要把一个Service放在前台运行,可以调用startForeground()。这个方法接受两个参数:一个整数来唯一标识一个通知,和显示在状态条上的通知对象。

例如:

要使一个Service推出前台运行,调用stopForeground(). 这个方法接受一个boolean参数,指明是否同时移除状态条上的通知。 这个方法并不会停止Service的运行。但如果你停止一个在前台运行的Service,那么会同时移除状态条的通知。

管理Service的生命周期

和Activity相比,Service的生命周期要简单很多。但你更要关注你的Service是如何创建和销毁的,这是因为Service可以在用户不知道的情况下在后台运行。

Service的生命周期,从其创建后到销毁,可以有两条不同的路径:

“Started”的Service 这个Service是由其它的应用程序组件调用startService()创建的,这个service 可以在后台无限期运行直到调用 stopSelf()或者其它组件调用stopService()来停止它。当Service停止时,系统将销毁这个Service。

支持“绑定”的Service 这种Service是在其它组件调用 bindService()绑定它时创建,客户端然后可以通过服务接口和Serivce通信。客户端可以调用 unbindService () 解除与Service的绑定。多个客户端可以同时绑定同一Service,当一个Service没有客户端和它绑定时,系统则销毁这个Service。

这两条路径并不是完全分开的,也就是说,你可以去绑定一个已经是“Started”状态的Service。比如,一个通过媒体播放的Service可能通过指明需要播放音乐的Intent然后调用startService() 启动的。再往后,用户可能打算来操作媒体播放器,那么一个Activity可以调用 bindServive()来绑定这个Service。在这种情况下,stopSelf()或stopSelf()并不真正停止Service直到所有的客户端都解除与Service的绑定。

实现生命周期回调函数

和Activity类似,Service也提供了一些回调函数接口允许你重载这些方法来监视Service状态的变化并添加合适的处理,下面代码给出一个Service生命周期回调函数实现的框架:

package com.example.bookdemo;    import android.app.Service;  import android.content.Intent;  import android.os.IBinder;      public class ExampleService extends Service {   // indicates how to behave if the service is killed      int mStartMode;        // interface for clients that bind      IBinder mBinder;           // indicates whether onRebind should be used      boolean mAllowRebind;         @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      }  }


  和Activty不同的是,Service的生命周期回调函数没有必要调用基类的对于的方法。

1364004813_6162.png 

图 1.                     Servcie的生命周期

通过实现这些回调函数,你可以监视Service生命周期中两个嵌套的循环:

·        整体生命周期 指Service在onCreate()和onDestroy()之间。和Activity类似,Service可以在onCreate () 进行一些初始化工作,而在onDestroy () 中释放资源。比如,一个音乐播放器可以在onCreate() 创建用来播放音乐的线程,而在onDestroy() 停止这个线程。

·        活动生命周期 Service 在 onStartCommand() 或 onBind() 后开始活动,每个方法分别处理来自 startService和 bindService() 发过来请求 Intent。如果是“Started”的Service,那么它活动的生命周期和Service的整个生命周期是一致的。如果是“绑定”的Service,那么它的活动生命周期终止与unbind()。

来自:http://blog.csdn.net/mapdigit/article/details/8709039