Android service的使用

Services

一个Service是一个应用组件,它能够在后台执行耗时长的操做,而不提供用户接口。另外一个应用组件能够启动一个service,而后它将在后台持续运行,即便用户切换到了另外一个应用。此外,一个组件能够bind到一个service来与之交互,甚至执行进程间通讯(IPC)。好比,一个service能够处理网络事务,播放音乐,执行文件I/O,或者与一个content provider交互,均是在后台。html

一个service实质上能够有两种形式:java

  • Startedandroid

    当一个应用组件(好比一个activity)经过调用startService()来启动一个service时,则service"started"的。一旦被启动,一个service能够在后台无限期地运行,即便启动它的组件已经被销毁了。一般一个被启动的service执行一个单独的操做,而且不给调用者返回一个结果。好比,它能够经过网络下载或上传一个文件。当操做完成时,那个service应该自动中止。数据库

  • Bound安全

    当一个应用组件经过调用bindService()来bind一个service时,则service是 "bound"的。一个bound service提供了一个容许组件与service交互的客户端-服务器接口,发送请求,获取结果,甚至经过进程间通讯(IPC)来夸进程执行这些。一个bound service只有在另外一个应用组件被bound到它时才运行。多个组件能够当即bind到service,但当它们都unbind时,那个service将会销毁。服务器

尽管这份文档分开讨论了这两种类型的service,但你的service能够同时以这两种方式运行——它能够被启动(来无限期的运行)并容许binding。这只是你是否实现了一对回调方法的问题:实现onStartCommand()以容许组件启动它,实现onBind()以容许binding。网络

不管你的应用是被启动的,bound的,或者二者都有,任何应用组件均可以以与任何组件使用一个activity相同的方式来使用这个service(甚至是从另一个应用程序中)——经过一个Intent来启动它。然而,你能够在manifest文件中声明service为私有的,从而阻止来自于其余应用的访问。这将会在关于Declaring the service in the manifest的部分进行更多的讨论。多线程

注意: 一个service是在它的宿主进程的主线程中运行的——service建立它本身的线程,也在分开的进程中运行的(然而除非你那样指定)。这意味着,若是你的service要执行任何CPU密集的操做或者阻塞操做(好比MP3回放或网络),你应该在service中建立一个新的线程来执行那项工做。经过使用一个分开的线程,你将减小发生ANR错误的风险,而应用的主线程依然能够专门用于与你的activities进行用户交互。并发

基本概念

为了建立一个service,你必须建立一个Service(或它的一个已经存在的子类)的子类。在你的实现中,你须要覆写一些回调方法来处理service生命周期的某些重要的方面,并为组件提供一种机制来bind到service,若是合适的话。你应该覆写的最重要的回调方法为:app

  • onStartCommand()

    当另外一个组件,好比一个activity,经过调用startService()请求启动service时,系统会调用这个方法。这个方法一旦执行,则service被启动并能够在后台无限期地运行。若是你实现了这个方法,则你有责任在它的工做完成时,经过调用stopSelf()stopService()来中止它。(若是你只想提供binding,则你不须要实现这个方法。)

  • onBind()

    当另外一个组件经过调用bindService()想要bind到service时(好比执行RPC),系统会调用这个方法。在你的这个方法的实现中,你必须经过返回一个IBinder来提供一个客户端同于与service通讯的接口。你必须老是实现这个方法,但若是你不想提供binding,则你应该返回null。

  • onCreate()

    当service第一次启动时,系统会调用这个方法,来执行一次性的设置过程(在它调用onStartCommand()onBind()以前)。若是service已经在运行了,则这个方法不会被调用。

  • onDestroy()

    当service再也不被用到并在被销毁时,系统会调用这个方法。你的service应该实现这个方法来清理全部的资源,好比线程,注册的listeners,receivers,等等。这是service接收到的最后一个调用。

若是一个组件经过调用startService()启动了service(这将致使一个对于onStartCommand()的调用),则service将持续运行直到它经过stopSelf()来中止或者另外一个组件经过调用stopService()来中止它。

若是一个组件调用了bindService()来建立service(onStartCommand()不会被调用),则service只有在组件bound到它的过程当中才运行。一旦全部的客户端都unbound了service,则系统会销毁它。

只有当memory不足时Android系统才会强制中止一个service,它必须恢复那个具备用户焦点的activity的系统资源。若是service被bound到一个具备用户焦点的activity,则它就不太可能被杀掉,而若是service被声明为 前台运行(稍后讨论),则它将几乎从不被杀掉。然而,若是service被启动并长时间运行,则系统将降随着时间推移低它在后台任务列表的位置,service将变得极可能被杀掉——若是你的service被启动了,你必须设计它来优雅地处理系统重启它的状况。若是系统杀掉了你的service,它将在资源变得再次可用时当即重启它(尽管这也依赖于你从onStartCommand()返回的值,稍后讨论)。更多关于系统在何时可能会杀掉一个service的信息,请参考Processes和Threading 文档。

在下面的几个小节中,你将看到如何建立每种类型的service,及如何在其余的应用组件中使用它。

在manifest中声明一个service

像activities(及其余的组件)同样,你必须在你的应用程序的manifest文件中声明全部的services。

要声明你的service,添加一个<service>元素做为 <application>元素的子元素。好比:

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

参见<service>元素参考来获取更多关于在manifest中声明你的service的信息。

你能够在<service>元素中包含一些其余的属性来定义诸如启动service所需的权限,service应该运行其中的process等属性。android:name是属性仅有的必须的属性——它描述了service的类名。一旦你发布了你的应用,你就不该该再改动这个名字了,由于若是你改了的话,你可能会破坏那些依赖于明确的intent来启动或bind service的代码(阅读博客文章,Things That Cannot Change)。

为了确保你的app是安全的,请在启动或binding你的Service老是使用一个明确的intent,而不是为service声明intent filters。关于具体要启动哪一个service,若是容许一些模糊性很重要,你能够为你的service提供intent filters,并从Intent中排除组件的名字,但你必须随后经过setPackage()来为intent设置包名,这将为目标service消除歧义。

此外,你能够经过包含android:exported属性并将它设为“false”来确保只有你的app能够访问你的service。这有效的阻止了其它apps启动你的service。

建立一个Started Service

一个started service是另外一个组件经过调用startService()来启动,会致使一个对于service的onStartCommand()方法的调用的service。

当一个service被started了,它将具备一个独立于启动它的组件的生命周期,而且能够在后台无限期的运行,即便启动它的组件被销毁了。所以,service应当在它的工做结束时经过调用stopSelf()自动中止,或者另外一个组件能够经过调用stopService()来中止它。

一个应用组件好比一个activity能够经过调用startService()来启动service,传递一个Intent来指定service并包含一些service将会使用的数据。service在onStartCommand()方法中接收这个Intent

好比,假设一个activity须要在一个线上数据库保存一些数据。则那个activity能够启动一个伙伴service,并经过给startService()传递一个intent来将要保存的数据传递过去。service在onStartCommand()中接收intent,链接到Internet并执行数据库事务。当事务完成时,service自动中止并被销毁。

注意: 默认状况下,一个service所运行的进程与声明那个service的应用的进程是相同的,而且它仍是在那个应用的主线程中运行。所以,若是你的service在用户与相同的应用中的一个activity进行交互时,执行计算密集的或阻塞的操做,则service将下降activity的性能。为了不影响应用的性能,你应该在service的内部新起一个线程。

传统上,你能够扩展两个类来建立一个started的service:

  • Service

    这是全部services的基类。当你扩展这个类时,很重要的一点是,你要建立一个新的线程来执行service的全部工做,由于默认状况下service使用了你的应用的主线程,这将下降你的应用所运行的全部的activity。

  • IntentService

    这是一个Service的子类,它使用了一个工做者线程来处理全部的start请求,每次一个intent。若是你不须要你的service并行的处理多个请求的话,这是最好的选择。你所须要作的就是实现onHandleIntent(),每次的start请求它都接收intent,以使你能够执行后台工做。

下面的小节将描述你能够如何使用一个或多个这样的类来实现你的service。

扩展IntentService类

因为大多数started services不须要并行地处理多个请求(这实际上多是一个危险的多线程场景),使用IntentService类来实现你的service多是最好的。

IntentService 作了以下的这些:

  • 在你的应用的主线程以外建立一个默认的工做者线程执行传递给onStartCommand()的全部的intents。

  • 建立一个工做队列,来每次给你的onHandleIntent()实现传递一个intent,以使你从不须要担忧多线程。

  • 在全部的start 请求都被处理了以后,终止service,所以你从不须要调用stopSelf()

  • 提供了一个默认的返回null的onBind()实现。

  • 提供了一个默认的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容许binding时才须要实现。)

在下一小节,你将看到当扩展基本的Service类时,相同种类的service如何实现,其中须要更多的代码,但若是你须要并发地处理start请求则可能更合适一点。

扩展Service类

如你在前一小节所见,使用IntentService使得你的一个started service实现变得很简单。若是,然而,你的service要执行多线程(而不是经过一个工做队列来处理start请求),则你能够扩展Service类来处理每一个intent。

为了便于对比,下面的示例代码是一个Service类的实现,其执行的工做与上面使用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.
          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()方法必须返回一个整数。那个整数是一个值,其描述了在系统要杀掉一个service的事件中系统应该如何继续那个service(如上面讨论的,IntentService的默认实现为你处理了这些,尽管你也能够修改它)。onStartCommand()的返回值必须是下列常量中的一个:

  • START_NOT_STICKY

    若是在onStartCommand()返回以后系统杀死了service,不要从新建立那个service,除非有挂起的intents传送过来。这是最安全的选项来避免在你的service在不须要或你的应用能够简单地重启任何未完成的工做时运行你的service。

  • START_STICKY

    若是在onStartCommand()返回以后系统杀死了service,从新建立service并调用onStartCommand(),但不要从新传送上一个intent。相反,系统以一个null intent来调用onStartCommand(),除非有挂起的intent来启动service,在这种状况下,那些intents会被传送。这对媒体播放器(或相似的services)比较合适,它们不执行命令,但会无限期的运行并等待一个任务。

  • START_REDELIVER_INTENT

    若是在onStartCommand()返回以后系统杀死了service,从新建立service,并以传送给service的最近的intent调用onStartCommand()。其余的挂起的intents则依序传送。对于那些应该被当即恢复地活跃地执行一个任务的service比较合适,好比下载一个文件。

关于这些返回值的更详细信息,请参考每一个常量连接的参考文档。

Starting一个Service

你能够在一个activity或其它的应用组件中,经过传递一个Intent(指定了要启动的service)给startService()来启动一个service。Android系统调用service的onStartCommand()方法并给它传递那个Intent。(你毫不应该直接调用onStartCommand())

好比,一个activity能够经过startService()使用一个显式的intent来启动上一小节中的示例service(HelloSevice):

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

startService()会当即返回,而且Android系统会调用service的onStartCommand()方法。若是service尚未在运行,则系统将首先调用onCreate(),而后调用onStartCommand()

若是service不提供binding,则经过startService()传送的intent将是在应用组件和service之间通讯的惟一模式。然而,若是你想要service发回一个结果,则启动service的客户端能够给一个broadcast建立一个PendingIntent(经过getBroadcast()),并在启动service的Intent中把它传递给service。而后service可使用broadcast来传送一个结果。

多个启动service的请求会致使对于service的onStartCommand()的多个对应的调用。然而,要中止service(经过stopSelf()stopService())则只须要一个请求。

中止一个service

一个started service必须管理它本身的生命周期。即,系统不会中止或销毁service,除非它必需要恢复系统内存,而且service在onStartCommand()返回后也将继续运行。所以,service必须经过调用stopSelf()来中止它本身或另外一个组件能够经过调用stopService()来中止它。

一旦经过 stopSelf()stopService()被请求中止了,则系统会尽快销毁那个service。

然而若是你的service并发地处理多个到onStartCommand()的请求,则你不该该在完成处理一个start请求时中止service,由于你可能已经接收了一个新的请求(在第一个请求的结尾处中止将终止第二个)。为了不这个问题,你可使用stopSelf(int)来确保你的中止service的请求老是基于最近的start请求。即,当你调用stopSelf(int)时,你传递你的中止请求所对应的start请求的ID(startId传送给了onStartCommand())。而后若是service在你可以调用stopSelf(int)接收了一个新的start请求,则ID将不匹配,而service将不会中止。

注意:很重要的一点是,你的应用要在工做完成后中止它的service,以免浪费系统资源及消耗电源。若是须要的话,其它的组件能够经过调用stopService()来中止service。即便你容许binding那个service,若是曾经接收了一个对于onStartCommand()的调用,你也必须老是亲自中止service

 更多关于一个service的生命周期的信息,请参考下面的关于管理一个service的生命周期的部分。

建立一个Bound Service

一个bound service是一个容许应用程序组件经过调用bindService()来bind到它以建立一个长期的链接(一般来讲不容许组件经过调用startService()start)。

当你想要在你的应用的activities或其它组件中与service交互,或要经过进程间通讯(IPC)将你的应用的功能暴露给其余应用时,你应该建立一个bound service。

要建立一个bound service,你必须实现onBind()回调方法来返回一个IBinder,然后者则定义了与service通讯的接口。随后,其它的应用组件能够调用bindService()来获取接口并开始调用service上的方法。service只有在服务于bound到它的应用组件时才是存活的,所以当没有组件bound到service时,则系统会销毁它(你须要中止一个bound,像经过onStartCommand()启动的service中那样的方式)。

要建立一个bound service,你必需要作的第一件事情是定义接口,其描述了一个客户端能够如何与service通讯。在service和一个客户端之间的接口必须是一个IBinder的实现,而且是你的service必须在onBind()回调方法中返回的东西。一旦客户端接收了IBinder,它能够开始经过那个接口来与service交互。

多个客户端能够同时bind到service。当一个客户端完成了与service的交互,它能够调用unbindService()来unbind。一旦没有客户端bound到了service,则系统会销毁那个service。

有多种方法来实现一个bound service,而且那样的实现相对于一个started service要更加的复杂,所以bound service将在另外的一份关于Bound Services的文档中来讨论。

给用户发送一个通知

一旦执行起来的,则一个service可使用Toast通知状态栏通知来给用户通知事件。

一个toast通知是一个消息,它将出如今当前窗口的表面一小段时间而后消失,而一个状态栏通知在状态栏中显示一个消息,同时还有一个图标,用户能够选择它们来执行一个动做(好比启动一个activity)。

一般,一个状态栏通知是当后台工做完成时(好比一个文件下载完了)可用的最好的技术,用户能够在他上面执行一些动做。当用户在expanded view中选中了通知,通知能够启动一个activity(好比查看下载的文件)。

请参考Toast通知状态栏通知开发者指南来获取更多信息。

在前台运行一个Service

A foreground service is a service that's considered to be something the user is actively aware of and thus not a candidate for the system to kill when low on memory. A foreground service must provide a notification for the status bar, which is placed under the "Ongoing" heading, which means that the notification cannot be dismissed unless the service is either stopped or removed from the foreground.

For example, a music player that plays music from a service should be set to run in the foreground, because the user is explicitly aware of its operation. The notification in the status bar might indicate the current song and allow the user to launch an activity to interact with the music player.

To request that your service run in the foreground, call startForeground(). This method takes two parameters: an integer that uniquely identifies the notification and the Notification for the status bar. For example:

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);

Caution: The integer ID you give to startForeground() must not be 0.

To remove the service from the foreground, call stopForeground(). This method takes a boolean, indicating whether to remove the status bar notification as well. This method does not stop the service. However, if you stop the service while it's still running in the foreground, then the notification is also removed.

For more information about notifications, see Creating Status Bar Notifications.

管理一个Service的生命周期

一个service的生命周期比一个activity的要简单的多。然而密切注意你的service如何被建立和销毁则甚至更加剧要,由于一个service能够在用户意识不到的状况下在后台运行

service的生命周期——从它什么时候被建立到什么时候被销毁——可能遵循两个不一样的路径:

  • 一个started service

    当另外一个组件调用了startService()时,service被建立。而后service就无限期的运行,它必须经过调用stopSelf()来中止它本身。另外一个组件也能够经过调用stopService()来中止那个service。当service被中止时,系统将销毁它。

  • 一个bound service

    当另外一个组件(一个客户端)调用了bindService()时service被建立。而后客户端经过一个IBinder接口来与service通讯。客户端能够经过调用unbindService()来关闭链接。多个客户端能够bind到相同的service,当全部的客户端都unbind时,系统销毁service.(Service须要中止它本身。)

这两个路径不彻底是分开的。即,你能够bind到一个已经经过startService()被started的service。好比,一个后台的音乐服务能够经过调用startService()来启动,其Intent来描述要播放的音乐。随后,用户可能想要使用一些关于播放器的控制或获取关于当前歌曲的信息,一个activity能够经过调用bindService()来bind到service。在相似这样的状况下,stopService()stopSelf()不会真的中止service,直到全部的客户端都unbind。

实现生命周期回调

像一个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 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的生命周期回调方法,你须要调用这些回调方法的超类实现。

图 2. Service的生命周期。 左边的图显示了经过startService()来建立的service的生命周期,右边的图显示了经过bindService()建立的service的生命周期。

经过实现这些方法,你能够监视service的生命周期的两个嵌套循环:

Note注意:尽管一个started的service经过一个调用stopSelf()stopService()来中止,但service却没有一个专门的对应的回调(没有onStop()回调)。所以,除非service被bound到一个客户端,系统将在它被中止时销毁它——onDestroy()是仅有的接收到的回调。

图2描绘了一个service的典型的回调方法。尽管图中将经过startService()建立的service和那些经过bindService()建立的service分开了,但要记住,任何service,不管它是如何被启动的,均可能潜在的容许客户端bind到它。所以,一个最初经过onStartCommand()启动的service(经过一个客户端调用startService())依然能够接收一个到onBind()的调用(当一个客户端调用bindService())。

更多关于建立一个提供binding的service的信息,请参考Bound Services 文档,其中在关于Managing the Lifecycle of a Bound Service的小节中包含了更多关于onRebind()回调方法的信息。

相关文章
相关标签/搜索