Android service的使用

假装没事ソ 提交于 2019-12-09 18:11:53

Services

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

一个service实质上可以有两种形式:

  • Started

    当一个应用组件(比如一个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,如果合适的话。你应该覆写的最重要的回调方法为:

  • 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()回调方法的信息。


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!