Service详解_移动开发_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > 移动开发 > Service详解

Service详解

 2013/11/24 18:25:44  treesouth  博客园  我要评论(0)
  • 摘要:概述1.一个服务可以运行在后台执行工作甚至是当用户在不同的应用中。2.一个服务可以允许其他组件绑定到它,为了与它进行交互和执行进程间通信。3.默认情况下,一个服务运行在主机应用程序的主线程中。一个服务即一个应用组件,是可以长期在后台运行,而且不提供用户任何接口。即使启动了其它应用,之前启动的服务仍会继续运行。组件可以绑定服务并与之交互,甚至允许多进程交互(ipc)。例如一个服务可以后台联网,后台播放音乐,后台处理文件输入/输出(I/O),或者后台和内容提供者(contentprovider
  • 标签:详解 Service

概述

1.一个服务可以运行在后台执行工作甚至是当用户在不同的应用中。
     2.一个服务可以允许其他组件绑定到它,为了与它进行交互和执行进程间通信。
     3.默认情况下,一个服务运行在主机应用程序的主线程中。

一个服务即一个应用组件,是可以长期在后台运行,而且不提供用户任何接口。即使启动了其它应用,之前启动的服务仍会继续运行。组件可以绑定服务并与之交互,甚至允许多进程交互(ipc)。例如一个服务可以后台联网,后台播放音乐,后台处理文件输入/输出(I/O),或者后台和内容提供者(content provider)交互。

本质上一个服务有两种类型:

直接启动的服务

  • 应用组件(例如一个Activity)调用startService()方法就可以启动一个服务。一旦启动,服务就在后台无限运行,即使启动它的组件被销毁。通常,服务启动一个单操作并且不返回结果给调用者。例如,它会通过网络下载、上传文件,当一个操作结束,该服务应该自动结束。

绑定的服务

  • 应用组件调用bindService()绑定服务。绑定的服务提供一个客户端服务器(client)接口允许组件与之交互,发送请求,获得结果,甚至多进程交互执行这些操作。服务和另一个与之绑定的组件运行时间一样长。多个组件只能和一个服务绑定一次,但所有组件取消绑定之后,服务就会销毁。尽管本文档分开讨论两类服务(Started 和Bound),但服务可以同时用两种方式工作-可以启动之后无限期运行而且允许绑定。这只取决你有没有都实现这两类服务的回调接口:onStartCommand()启动服务或onBind()绑定服务。

不管应用请求的是哪一种服务,任何组件(非本应用的也可以)都可以使用该服务。同样,任何组件(包括其他应用)都可以使用 activity,用Intent启动。但是,你可以在配置文件manifest中把服务声明为私有的,阻止其他应用访问。详见在配置文件 manifest中声明一个服务.

注意:服务运行于主进程中-即服务不会自己创建另一个进程(除非你指定)。这意味着如果服务执行任何cpu耗时操作或异步操作(像MP3播放或联网),你应该创建一个新线程执行这类操作,这样,就可以减少死机的风险,主线程就可以专门负责和你的activity交互。

基础

使用线程还是服务?

即使用户没和应用交互,服务仍然会可以在后台运行。因此,你应该在需要时才使用它。如果你不想在主线程执行一个任务,而是仅当用户和应用交互时执行该任务,你应该创建新线程而不是服务。例如,你只想activity运行时才播放音乐,就可以在onCreate()方法里创建一个线程,在 onStart()方法里启动该线程,在onStop()方法里关闭它。也可以考虑用AsyncTask或者HandlerThread,代替传统的 Thread类。记住如果你使用了一个服务,默认它仍然会在主线程里运行,所以,遇到耗时或异步操作,可以创建一个新线程,然后再启动该服务。

要创建一个服务,先写一个子类继承Service。实现该子类时,需重载一些回调函数,这些函数处理一个服务的生命周期的各个关键部分,并为组件提供绑定该服务的机制,你可以有选择的重载这几个回调函数:

onStartCommand()

当一个组件(例如activity)调用startService()方法,系统就会调用该方法(onStartCommand()启动一个服务。一旦此方法被调用,服务立即启动,在后台一直执行。如果你实现该方法,该服务做的事情结束后,必须调用stopSelf()或者stopService(),停止该服务。

onBind()

当一个组件调用bindService()想绑定服务,系统就会调用本方法(onBind())。在你的应用中,你必须为客户端提供和该服务交互的接口,该接口返回一个IBinder对象。该方法必须实现。如果不允许绑定这个服务,可以返回null即可.

onCreate()

服务第一次创建,系统都会调用该方法,目的是一次性执行该过程(在调用onStartCommand()或onBind()方法前)。如果服务已经运行,方法不会被调用。

onDestroy()

当服务不再使用并被销毁时系统会调用该方法。应该实现该方法清除资源(例如线程,注册句柄,接收器等),该方法最后调用。

如果一个组件调用startService()启动一个服务(会导致调用onStartCommand()),那么服务会一直运行,直到自己调用stopSelf()或者其他组件调用stopService()停止服务。

如果一个组件调用bindService()创建服务(onStartCommand()没有调用),那么服务一直运行,直到一个组件绑定他。一旦服务未被客户端绑定,系统会销毁它。

因为一个activity获得用户焦点回收系统资源,或者因为内存不够,系统会强行关闭一个服务。如果该服务和该activity绑定,那该服务被关闭就比较小,如果服务在后台运行(稍后专门讨论),该服务几乎不可能被关闭,否则,如果服务已启动,运行了很长时间,随着时间的增加,系统就会降低该服务在后台任务队列中的级别,而且很有可能被干掉。如果一个服务已启动,你应该为该服务设计下重启时的相关处理。如果服务被关闭,当有可用资源时才会重启(尽管这依然依赖onStartComand()函数的返回值,稍后讨论)。

下面,你将会看到怎么创建各种类型的服务以及怎么在组件中使用服务。

在manifest中声明一个服务
<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>

</manifest>

定义形如请求启动服务的权限、服务所在的进程之类的属性,可以把这些属性包含在标签中。android:name属性是必须定义的属性-它指定了服务的类名。一旦发布应用,这个名字就不能改动。

就像activity,一个服务可以通过定义intent filters,允许其他组件使用隐式的intents激活一个服务。这样做,一个应用中的组件可能启动一个服务,而这个服务中声明的intent filter可能匹配另一个应用(通过传递参数给startService()函数启动服务)中的intent。

如果你只想本地使用服务(其他应用不使用他).你就不必声明任何过滤符.没有这些,你就必须使用一个intent启动此服务,而且要指定具体的服务类的名字。下面具体讨论关于如何启动服务:

此外,仅当android:exported="false"时你才能确保服务对应用来说是私有的.即使你的服务提供intent过滤器,服务仍然私有.

创建一个启动的服务

低于android1.6平台

如果系统版本低于1.6,你需要实现onStart()方法代替onStartCommand()。(在安卓2.0,onStart()不建议使用,更建议用onStartCommand().)

启动一个服务,通过另一个组件调用startService()方法即可.这导致调用onStartCommand()方法.

当一个服务启动,会有个生命周期,它独立于启动它的组件,服务可以后台一直运行,即使启动它的组件被销毁.因此,工作完成,服务应该调用stopSelf()自己关闭自己,或者另外一个组件调用stopService()函数停止该服务.

一个组件,像activity可以通过调用startService()方法启动服务,该方法须传递一个Intent对象,该Intent对象可以包含任何服务要使用的数据.服务在onStartCommand()方法中接受这个Intent对象.

举个例子,一些activity需要保存一些数据到一个在线数据库(onine database)。可以保存数据到一个intent对象,然后再把这个intent对象当做参数传给startService()方法,调用startService启动服务。该服务在onStartCommand()方法中接收这个intent对象,连接到互联网进行数据传输.传输完毕,服务就自动停止并销毁.

注意:默认情况下,服务运行于它声明所在的应用进程中,并且在主线程中。所以如果服务做的事情很耗时或者可能导致阻塞操作,这时用户刚好又需要和应用交互,这样肯定导致整个应用性能降低,要避免这样,你应该在服务中新启动一个线程.

通常,有两种可用于继承的类来创建service:

Service:

这是所有服务类的基类,继承该类,对于在服务中创建新线程很重要.因为默认服务使用应用的主线程,可能会降低程序的性能.

IntentService:

这是一个Service的子类,该子类使用线程处理所有启动请求,一次一个.这是不使用服务处理多任务请求的最佳选择.你需要做的只是实现onHandleIntent()方法即可.可以为每个启动请求接收到intent,放到后台工作即可.

继承IntentService类

因为大多数服务不必处理同时发生的多个请求.(多线程方案可能会很危险),所以最好用IntentService实现该服务.

IntentService类可以做这些事情:

  • 从应用的主线程当中创建一个默认的线程执行所有的intents发送给onStartCommand()方法,该线程从主线程分离.
  • 创建工作队列,每次传递一个intent 给onHandleIntent()方法实现,所以不必担心多线程.
  • 所有的请求被处理后服务停止,所以你永远都不必调用stopSelf()函数.
  • 默认实现onBind()方法返回null.
  • 默认实现onStartCommand()方法是发送一个intent给工作队列,然后发送给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.
      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类和继承IntentService类如此相似,可能要多费点儿劲,但是如果你同时处理多个服务请求,这些多些的代码是值得的。

继承Service类

正如之前所述,使用IntentService类,可以简化启动服务的代码.但是,请求启动服务的是多线程同时请求(代替用一个工作队列处理这种情况),那么你就应该继承Service类处理这种情况.

比较而言,下面的代码是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.
          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,使用Service是个苦力活.

但是,在onStartCommand()方法里因为是自己处理每个调用,所以你可以同时处理多个请求。例子中虽然没演示,但如果需要,你完全可以为每个请求创建一个线程,按正确的方式处理这些请求.(比起上面的IntentService,Service代替了需要等待上次请求处理完毕的方式。)

注意onStartCommand()方法必须返回一个整型变量.这个变量描述可能已被系统停止的服务,如果被停止,系统会接着继续服务(正如下面讨论,IntentService的实现中默认自动帮你做了个处理,尽管可以修改它),返回值必须是如下几个常量之一:

START_NOT_STICKY

如果系统在onStartCommand()返回后停止服务,系统不会重新创建服务,除非有等待的意图需要处理.如果想避免运行不需要的服务,或者让应用可以轻松的启动未完成的任务,这是一个安全的选择。

START_STICKY

如果系统在onStartCommand()返回后停止服务,系统重新创建服务并且调用onStartCommand()函数,但是不要传送最后一个意图。相反,系统用一个空意图调用onStartCommand(),除非还有想启动服务的意图,这种情况下,这些意图会被传递.这很适合多媒体播放的情况(或类似服务),这种情况下服务不执行命令,但是会无限运行下去,等待处理任务。

START_REDELIVER_INTENT

如果系统在onStartCommand()返回后停止服务,系统使用最后传递给service的意图,重新创建服务并且调用onStartCommand()方法,任何未被处理的意图会接着循环处理。所以用服务处理像下载之类的可能需要立即重启的任务,非常合适。

启动服务

你可以从一个activity或者其他应用组件通过传递一个intent(指定要启动的服务)给startService()方法.系统调用服务的onStartCommand()方法并且把意图传给此方法.(千万不能直接调用onStartCommand()方法).

下面,我们演示一下用具体的意图传递给方法startService()来启动一个服务:

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

startService()方法立即返回,安卓系统会调用onStartCommand()方法。如果服务没有运行,系统首先调用onCreate()方法,然后在调用onStartCommand()方法。

如果系统不提供绑定,通过传递意图给startService()方法是应用组件和服务唯一的沟通方式。但是如果你想服务发送一个返回结果,可以让客户端使用广播(调用getBroadcast()方法获取)创建一个PendingIntent启动一个服务,然后把该意图传递给要启动的服务,服务就可以使用此广播传递结果了。

并发请求启动服务导致并发响应调用onStartCommand()函数,但是,唯一停止服务的方法就是调用stopSelf()或者stopService()方法。

停止服务

系统不会停止或销毁服务,除非系统内存被覆盖或者服务在onStartCommand()返回之后还在运行。所以,服务自己调用stopSelf()或其他组件调用stopService()方法都会停止服务.

但是,当前如果服务在onStartCommand()方法中同时处理多个请求,那么当你正在处理这样的启动请求时,肯定不应该停止服务,因为很有可能在停止服务时刚好收到一个新的请求(在第一个请求的结尾停止服务会终止第二个请求).要避免这个问题,你可以使用stopSelf(int)方法确保服务被停止,而且这个服务是最经常启动的。就是说,当调用stopSelf(int),你需要传递给请求启动的ID(这个ID传递给 onStartCommand()方法)给对应的服务.如果在可以调用stopSelf(int)之前,服务接收到一个新的启动请求,服务就不会匹配,也就不会停止。

注意:当服务完成任务后停止它,非常重要,因为这样做可以避免浪费系统资源和节约电量.如果有必要,其他组件可以调用stopService()方法停止服务。即使你绑定服务,如果该服务收到一个onStartCommand()的调用,就必须停止服务。

前台运行一个服务

前台服务是一种考虑到系统内存不足,但是用户已经意识到而且不想关闭的服务。前台服务必须用状态条做出提示,表示该服务在持续进行中,意思是这种提示除非服务停止或者从所有的前台服务中移除才能消失.
例如,用服务操作音乐播放器播放音乐,应该在前台播放,因为用户明确指出了具体操作(就是播放音乐)。状态条可以包含当前播放的音乐,并且允许用户启动一个activity和音乐播放器交互.

要请求服务在前台运行,调用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().这个方法需要一个布尔参数,表示是否清除状态栏的信息.调用它,本身不会停止服务.但是,服务仍然在后台运行,你突然停止服务,状态烂的提示就会移除。
注意:startForeground()和stopForeground()在安卓2.0中介绍过(API 等级5).要在旧平台的前台运行服务,就必须事先调用setForeground(),关于如何向后兼容,请查看startForeground()。

管理服务的生命周期

启动的服务

当另一个组件调用startService(),服务就被创建,然后一直运行下去,直到服务自己调用stopSelf()方法停止该服务.其他组件也可以通过调用stopService()方法停止一个服务.系统会销毁该服务。

绑定的服务

当另一个组件(客户端)调用bindService(),服务就被创建,客户端然后通过IBinder接口和服务交互.客户端可以调用 unbindService()关闭和服务的连接.多客户端可以绑定到相同的服务,所有的客户端都解除绑定之后,系统销毁该服务.(服务不必像上面那样自己销毁).

这两种方式不是完全不同的,就是说,你绑定一个已经调用startService()启动的服务。例如,一个后台音乐服务可以通过把一个意图传递给 startService()启动。稍后,用户可能执行某些操作或获得当前歌曲的信息,一个activity通过调用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()方法返回结束.就像activity,一个服务是在 onCreate()方法中初始化,在onDestroy()方法中释放资源.例如,一个music后台播放的服务,会在onCreate()方法里创建线程播放音乐,在onDestroy()方法里停止该线程.

服务的活动周期是从调用onStartCommand()或者onBind()函数开始,这两个方法是当意图传给startService()或者bindService()才会被调用。

如果服务已经启动,活动周期和整个生命周期一起结束(即使onStartCommand()函数返回后,服务仍然处于活动状态)。如果服务被绑定,在onUnbind()返回后活动周期结束.

注意:尽管调用stopSelf()或者stopService()可以停止服务,但停止时不会有回调方法被调用.(没有onStop()回调),所以,除非服务绑定客户端,系统会销毁该服务,这时,onDestroy()方法是唯一被回调的方法.

图中展示了服务的两种典型的回调方法流程。尽管一个用startService()创建服务,一个用bindService()创建,但是任何服务,不论如何启动,都允许客户端绑定它。所以一个用onStartCommand()(客户端方式调用startService())初始化的服务可以调用onBind()方法(客户端方式就调用bindService())。

发表评论
用户名: 匿名