服务 - Services

Services

Android中的Service是一种应用组件,它可以长时间的在后台运行并且不提供任何用户界面。Service可以由其他应用组件启动并在后台运行,即使用户切换到其他应用程序,该Service也会一直运行。另外,其他组件可以绑定到Service上与之交互,甚至能够进行进程间通信(IPC)。 例如,一个服务可能处理网络事务、播放音乐、进行I/O操作或者跟内容提供者交互,这些过程都是在后台进行的。

Service一般有两种表现形式

通过启动

一个Service可以被其他应用组件(比如一个Activity)通过调用startService方法来启动。一旦启动了,一个Service就能到后台无限期的运行,就算启动它的组件已经销毁(它也不会停止)。
通常情况下,一个已经启动的Service会执行单一的操作并且不给启动它的组件任何返回值。例如,它可能通过网络下载或者上传一个文件。当这个操作结束后,Service就自行停止。

通过绑定

一个Service可以被其他应用组件通过调用bindService方法来绑定。一个被绑定的Service和提供了客户端-服务器接口,允许绑定它的组件和它进行交互,比如发送请求、获取结果甚至进行跨进程的进程间通信(IPC)。一个被绑定的Service只在绑定它的应用组件的生命周期中运行。多个组件可以同时绑定到一个Service,但是如果这些组件全部的解除绑定,那么这个Service也就被销毁了。

虽然这份文档分开讨论这两种类型的Service,你的Service可以通过这两种方式工作--它既可以被启动也可以被绑定。它唯一需要关注的是两个回调方法是怎么实现的:允许组件启动它的onStartCommand()方法和允许组件绑定它的onBind()方法。

无论你的应用是否已经启动、绑定或者两者兼有,任何其他的应用组件都能够用这个service(甚至从别的应用程序里面--也就是不同的apk),使用方法和其他组件使用activity的方法一样--通过启动一个Intent。虽然如此,你也可以通过在清单文件里面申明这个service是私有的,那么别的应用程序就不能访问你的这个service了。这个将在介绍清单文件的service申明部分进行更多的讨论。


注意:一个service在他的宿主进程的主线程中运行---service不会创建他自己的线程也不会在单独的进程中运行(除非你另行指定)。这意味着,如果你的服务会做任何CPU密集型工作或阻塞操作(如MP3播放或网络操作),你应该在该service内创建一个新线程来做完成这些任务。通过使用一个单独的线程,你会减少应用程序无响应(ANR)的错误,让应用程序的主线程可以保持与activity的交互工作。

基础知识-The Basics

要创建一个service,你必须创建一个Service(或者其现有的子类之一)的子类。在实现时,你需要重写一些service生命周期关键环节的的回调方法,并提供组件绑定到服务的机制,如果适当的话。最重要的需要重写的回调方法如下:

onStartCommand()

当其他的组件比如activity会通过调用方法startService()来请求这个service启动时,系统回调该方法。一旦这个方法执行了,该service就启动了,并且能够在后台无限期的运行下去。如果实现了该回调方法,你就需要在这个service工作完成之后通过调用stopSelf()或者stopService()方法来停止它。(如果你只是提供了绑定服务的话,就没必要实现这个方法)

onBind()

当其他的组件想要通过调用方法bindService()来绑定到这个service(如执行RPC)的时候,系统回调该方法。实现该方法的时候,你必须提供一个让客户端和服务器通信的接口,即返回一个IBinder对象。你必须经常实现这个方法,但是如果你不希望别的组件绑定到你的service,那么返回null即可。

onCreate()

当该service第一次创建的时候,系统回调该方法,执行一次性创建的流程(在onStartCommand() 或者 onBind()之前)。如果该service已经在运行的话,这个方法不会被调用。

onDestroy()

当该service不在被使用并需要销毁的时候,系统回调这个方法。你的service应该事先这个方法来清理一些资源,比如线程、注册的监听器和接收器等等。这个是service能接受到得最后一个回调函数。

如果一个组件通过调用startService()来启动service(这会导致调用onStartCommand()方法),这个service会移植运行知道它自己调用stopSelf()或者其他组件通过调用stopService()来停止它。

如果一个组件通过调用bindService()来创建一个service(这个时候onStartCommand()方法不会被调用),这个service只会在该组件绑定期间运行。一旦这个service与其所有的客户端失去绑定,那么系统将会摧毁它。

Android系统会在内存不足的时候强行停止service,节省系统空间来给用户当前焦点所在的activity运行。如果一个service是和当前用户焦点所在的activity绑定的,那么它不大可能会被杀掉,如果一个service是申明运行在前台的(稍后讨论),它将不会被杀掉。除此之外,如果一个service启动并长时间运行,系统随着时间推移会降低其在后台任务列表中的地位,最终将成为一个高可能性被杀掉的service。因此,如果你的service启动了,你必须妥善处理好相关的系统重启工作。如果系统杀掉了你的service,它将会在资源可用的情况下马上重启(虽然这个也同事取决于你在onStartCommand()方法中的返回值,这个以后讨论)。要获取更多关于系统可能摧毁一个service的相关信息,请查看进程和线程的文档。

在接下来的章节里,你将会看到怎么创建各种不同类别的service并且学会怎么在别的应用组件中使用它。

在manifest中声明service-Declaring a service in the manifest

跟activity(和其他组件)一样,你必须把所有的service定义到你的应用程序的manifest文件里面。

要声明你的service,你需要添加<service>元素作为<application>的子元素。例如:

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

这里有一些其他的属性比如启动service的权限请求和该service运行的进程都可以包含在<service>元素下面。android:name属性是唯一必须的属性,它指定service的类名。一旦你发布你的应用了,就不应该修改这个name属性了,因为一旦你这么做了,可能会破坏一些用显式Intent来依赖于该service的功能(阅读相关博客文章,《不能改变的事》)。

查看<service>元素参考来得到更多关于在manifest中声明service的信息。

和activity一样,一个service也能定义意图过滤器(intent filters)来允许其他组件隐式的intent来调用该service。通过声明意图过滤器,安装在用户设备上的其他应用程序的组件能够启动你的service,只要你定义的意图过滤器和另外应用程序启动startService()所用到的Intent相匹配。

如果你计划只在本地用你的service(其他引用不用它),那么你就不必(也不应该)提供意图过滤器。没有任何意图过滤器的话,你必须通过显式给出service的类名的方法来启动该service。更多启动service的信息将在下面讨论。

另外,你可以保证你的service是该应用私有的,只需要你包含android:exported属性并且将其值设为"false"的即可。这个就算你提供意图过滤器也能够有效。

要得到更多关于创建你的service的意图过滤器的信息,可以查看意图和意图过滤器(Intents and Intent Filters)文档。

创建一个启动了的service-Creating a Started Service

一个已启动的service是其他组件通过调用startService()启动的,其结果是系统会调用该service的onStartCommand()方法。

当一个service启动了,他有一个独立于启动它的组件的生命周期,并且该service能在后台无限期的运行,就算那个启动它的组件被摧毁了也不会停止。因此,一个service应该在任务完成后调用方法stopSelf()来自行停止,或者其他组件调用stopService()方法来停止它。

一个应用组件比如activity能通过调用方法startService()来启动service,并且传递一个指定了该service和一些其他数据的intent供该service使用。该service在onStartCommand()方法中接受这个intent。

例如,假设一个activity需要保存一些数据到线上的数据库。这个activity能启动一个配套service并且通过startService()来传递包含需要传输并保存的数据的intent。该service在onStartCommand()方法中接收到该intent,连接到网络并且进行数据传输。当传输过程完毕时,这个service停止自己并自行销毁。

注意:一个service默认是在应用程序的主线程中运行的。因此,如果你的service在用户通过activity进行交互的时候进行密集型的或者阻塞型的操作,会影响到activity的性能。为了避免影响应用程序的性能,你应该在该service启动新的线程来完成工作。


一般来说你可以继承两个类中的一个来创建一个service:Service

这个事所有service的基类。当你继承该类的时候,一件很重要的工作是你需要创建一个新的线程来执行service的任务,因为这个service在应用程序的主线程中运行,可能降低正在运行的activity的性能。

IntentService

这个类是Service的子类,他使用一个工作线程来处理所有的启动请求,一次一个。如果你不要求你的service同事处理多个请求的话,这个是最好的选择。你所需要做的知识实现onHandleIntent()方法,它每次接受启动请求里面的intent,因此你能够在后台工作。

以下各节描述如何可以实现你的service,使用以上两个类中的任何一个。


扩展IntentService类-Extending the IntentService class

因为大多数启动的service不需要同事处理多个请求(这会很容易的导致危险的多线程情形),这个时候扩展IntentService来实现你的service是最好的选择。

IntentService做如下工作:

  • 创建一个默认的工作线程来执行从onStartCommand()里面传来的intent请求,这个线程是和主线程分开来的。
  • 创建一个工作队列来每次传递一个Intent到onHandleIntent()方法,所以你永远不用担心多线程问题。
  • 在所有的请求都被处理之后停止service,所以你永远不用手动调用stopSelf()方法。
  • 提供默认的onBind()方法的实现,也就是返回null。
  • 提供默认的onStartCommand()方法的实现,也就是把intent先传递到工作队列中去,然后再被转到onHandleIntent()方法中。

所有的这些特性都表明了你只需要实现onHandleIntent()方法来处理那些客户端提供的工作即可。(尽管如此,你也需要提供一个该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()必须返回默认实现(这就是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允许绑定的时候才需要实现).

在下面的章节里,你将会看到如何通过扩展基础的Service类来实现相同类型的service,这个需要更多的代码,但是能更好的同时处理多个请求。

扩展Service类-Extending the Service class

你可以在前面的章节中看到,通过用IntentService能够让你实现一个Service变得非常简单。但是,如果你需要你的service能够执行多线程任务(而不是通过一个工作队列来处理请求),那么你需要扩展基础的Service类来处理这每个intent请求。

做个比较,下面的代码是通过实现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(); 
  }
}

你可以看到,相比IntentServcie而言,这需要更多的工作。

尽管如此,因为你独自处理每次对onStartCommand()方法的调用,你能够同时处理多个请求。这个代码没有这么做,但是如果你需要的话,你就能够为每个请求创建一个新线程并且马上执行(而不是等待前面的请求完成)。

注意 onStartCommand()方法必须返回一个数字。这个数字是一个描述了当系统杀掉该service后怎样继续执行(前面已经讨论过,IntentService的默认实现中会帮助你完成这个事情)。onStartCommand()方法中必须返回一下几个常量中的一个:

START_NOT_STICKY

如果系统在onStartCommand()返回之后杀掉该service,不重新创建这个service,除非这里有还没有传递完的正在被挂起的intent。这是最安全的选项,以避免在没有需要必须运行或者你的应用能够很简单的重启任何没有完成的任务的时候还在继续运行该service的情况。

START_STICKY

如果系统在onStartCommand()返回之后杀掉该service,重新创建这个service并且调用onStartCommand()方法,但是不会重新传送最后的Intent请求。相反,系统用一个值为null的intent来调用onStartCommand()方法,除非这里有请求启动service的intent被挂起,在这种情况下会传送这些intent。这对于媒体播放器(或者类似的service)之类的不用执行命令但要无限期的运行并等待工作的service是很合适的。

START_REDELIVER_INTENT

如果系统在onStartCommand()返回之后杀掉该service,重新创建这个service并用最后传送给该service的intent来调用onStartCommand()方法。任何挂起的intent会马上被传送过来。这个对于一些积极执行工作并且需要马上恢复的service比如下载文件的service是很合适的选择。

要获得更多返回值的细节,请查看这些常量对应的文档。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值