Android官方文档之Services

Posted 他叫小黑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android官方文档之Services相关的知识,希望对你有一定的参考价值。

官方原文链接

本文将介绍Services的定义、创建、启动、绑定、前台Service等相关内容。


Service是android中一个类,它是Android四大组件之一,使用Service可以在后台执行长时间的操作,Service并不与用户产生UI交互。其他的应用组件可以启动Service,即便用户切换了其他应用,启动的Service仍可在后台运行。一个组件可以与Service绑定并与之交互,甚至是跨进程通信(IPC)。例如,一个Service可以在后台执行网络请求、播放音乐、执行文件读写操作或者与 content provider交互等。


Services两种启动形式

  1. Started:其他应用组件(如 Activity)通过调用 startService() 启动一个Service。一旦启动,Service将一直运行在后台(run in the background indefinitely)即便启动Service的组件已被destroy。通常,一个被start的Service会在后台执行单独的操作,也并不给启动它的组件返回结果。例如,它可能通过网络下载或上传文件。 操作完成后,服务会自行停止运行。
  2. Bound:其他组件调用bindService()方法绑定一个Service。通过绑定方式启动的Service是一个client-server结构,该Service可以与绑定它的组件进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。一个bound service仅在有组件与其绑定时才会运行(A bound service runs only as long as another application component is bound to it),多个组件可与一个service绑定,service不再与任何组件绑定时,该service会被destroy。

service也可以同时在上述两种方式下运行。这涉及到Service中两个回调方法的执行:onStartCommand()(通过start方式启动一个service时回调的方法)、onBind()(通过bind方式启动一个service回调的方法)。

无论通过那种方式启动service(start、bind、start&bind),任何组件(甚至其他应用的组件)都可以使用service。并通过Intent传递参数。当然,您也可以将Service在manifest文件中配置成私有的,不允许其他应用访问。

注意:Service运行在主线程中(A service runs in the main thread of its hosting process),Service并不是一个新的线程,也不是新的进程。也就是说,若您需要在Service中执行较为耗时的操作(如播放音乐、执行网络请求等),需要在Service中创建一个新的线程。这可以防止ANR的发生,同时主线程可以执行正常的UI操作。

Service基础知识

为了创建Service,需要继承Service类。并重写它的回调方法,这些回调方法反应了Service的生命周期,并提供了绑定Service的机制。最重要的Service的生命周期回调方法如下所示:

  • onStartCommand():当其他组件调用startService()方法请求启动Service时,该方法被回调。一旦Service启动,它会在后台独立运行。当Service执行完以后,需调用stopSelf() 或 stopService()方法停止Service。(若您只希望bind Service,则无需调用这些方法)
  • onBind():当其他组件调用bindService()方法请求绑定Service时,该方法被回调。该方法返回一个IBinder接口,该接口是Service与绑定的组件进行交互的桥梁。若Service未绑定其他组件,该方法应返回null。
  • onCreate():当Service第一次创建时,回调该方法。该方法只被回调一次,并在onStartCommand() 或 onBind()方法被回调之前执行。若Service处于运行状态,该方法不会回调。
  • onDestroy():当Service被销毁时回调,在该方法中应清除一些占用的资源,如停止线程、接触绑定注册的监听器或broadcast receiver等。该方法是Service中的最后一个回调。

如果某个组件通过调用startService()启动了Service(系统会回调onStartCommand()方法),那么直到在Service中手动调用stopSelf()方法、或在其他组件中手动调用stopService()方法,该Service才会停止。

如果某个组件通过调用bindService()绑定了Service(系统不会回调onStartCommand()方法),只要该组件与Service处于绑定状态,Service就会一直运行,当Service不再与组件绑定时,该Service将被destroy。

仅当内存过低且必须回收系统资源以供具有用户焦点的 Activity 使用时,Android 系统才会强制停止服务。如果将服务绑定到具有用户焦点的 Activity,则它不太可能会终止;如果将服务声明为在前台运行(稍后讨论),则它几乎永远不会终止。 否则,当创建了一个长时间在后台运行的Service后,系统会降低该Service在后台任务栈中的级别——这意味着它容易被kill(lower its position in the list of background tasks over time and the service will become highly susceptible to killing),所以在开发Service时,需要使Service变得容易被restart,因为一旦Service被kill,再restart它需要其资源可用时才行(restarts it as soon as resources become available again ),当然这也取决于onStartCommand()方法返回的值,这将在后续介绍。


在manifest文件中注册Service

如同 Activity(以及其他组件)一样,您必须在应用的清单文件中声明所有服务。
要声明服务,请添加 < service> 元素作为 < application> 元素的子元素。例如:

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

您还可将其他属性包括在 元素中,以定义一些特性,如启动服务及其运行所在进程所需的权限。android:name 属性是唯一必需的属性,用于指定服务的类名。应用一旦发布,即不应更改此类名,如若不然,可能会存在因依赖显式 Intent 启动或绑定服务而破坏代码的风险。

注意:为了保证应用的安全,请使用显式Intent启动或绑定一个Service,请不要在< service>标签中配置intent-filter。

若不确定该启动哪个Service,那么可以在中配置intent-filter,并在Intent中排除该Service(supply intent filters for your services and exclude the component name from the Intent),但必须调用Intent的setPackage()方法,来为启动的service消除歧义(provides sufficient disambiguation for the target service)。

注:setPackage()方法传入一个String参数,代表一个包名。该方法表示该Intent对象只能在传入的这个包名下寻找符合条件的组件,若传入null,则表示可以在任意包下寻找。

start方式启动Service

启动服务由另一个组件通过调用 startService() 启动,这会导致调用服务的 onStartCommand() 方法。服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。 因此,服务应通过调用 stopSelf() 结束工作来自行停止运行,或者由另一个组件通过调用 stopService() 来停止它。
startService()方法中传入一个Intent参数,用于显式指定目标Service的名字,并携带data以供Service使用,该Intent参数将回传至onStartCommand()方法中。
比如说,Activity需要向在线数据库中上传数据,那么可以调用startService()启动一个Service,并将数据传入Intent的data中,接着,onStartCommand()方法会接收这个Intent并开启一个线程将数据上传至网络,当数据上传完成后,该Service将停止并被destroy。

注意:默认情况下,服务与服务声明所在的应用运行于同一进程,而且运行于该应用的主线程中。 因此,如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。 为了避免影响应用性能,您应在服务内启动新线程。


一般使用如下两种方式创建一个start Service:

  • 继承Service类:这是适用于所有服务的基类。扩展此类时,必须在Service中开启线程来执行耗时操作,因为默认情况下,服务将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。
  • 继承IntentService类:IntentService继承于Service,如果你不要求服务同时处理多个请求,那么使用IntentService将是最好选择。你只需要重写onHandleIntent()方法,该方法接收一个回传的Intent参数,你可以在方法内进行耗时操作,因为它默认开启了一个子线程,操作执行完成后也无需手动调用stopSelf()方法,onHandleIntent()会自动调用该方法。

继承IntentService类

在大多数情况下,start Service并不会同时处理多个请求(don’t need to handle multiple requests simultaneously),因为处理多线程较为危险(a dangerous multi-threading scenario),所以继承IntentService类带创建Service是个不错选择。

IntentService 执行以下操作:

  • 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
  • 创建工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
  • 在处理完所有启动请求后停止服务,因此您永远不必调用 stopSelf()。
  • 提供 onBind() 的默认实现(返回 null)。
  • 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。

综上所述,您只需实现 onHandleIntent() 来完成客户端提供的工作即可。(不过,你还需要为服务提供构造函数。)实例如下:

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) {
              }
          }
      }
  }
}

如果您还希望在IntentService的继承类中重写其他生命周期方法,如onCreate()、onStartCommand() 或 onDestroy(),那么请先调用各自的父类方法以保证子线程能够正常启动。

比如,要实现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中执行多线程而不是处理一个请求队列(perform multi-threading instead of processing start requests through a work queue),那么需要继承Service类,分别处理每个Intent。

为了便于比较,以下提供了 Service 类实现的代码示例,该类执行的工作与上述使用 IntentService 的示例完全相同。在Service中执行操作时,处理每个请求都需要开启一个线程,并且同一时刻一个线程只能处理一个请求( for each start request, it uses a worker thread to perform the job and processes only one request at a time)。

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

因为是由您自己处理对 onStartCommand() 的每个调用,因此可以同时执行多个请求。此示例并未这样做,但如果您希望如此,则可为每个请求创建一个新线程,然后立即运行这些线程(而不是等待上一个请求完成)。

请注意,onStartCommand() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService 的默认实现将为您处理这种情况,不过您可以对其进行修改)。从 onStartCommand() 返回的值必须是以下常量之一:

  • START_NOT_STICKY:若执行完onStartCommand()方法后,系统就kill了service,不要再重新创建service,除非系统回传了一个pending intent。这避免了在不必要的时候运行service,您的应用也可以restart任何未完成的操作。
  • START_STICKY:若系统在onStartCommand()执行并返回后kill了service,那么service会被recreate并回调onStartCommand()。但是不要重新传递最后一个Intent(do not redeliver the last intent)。相反,系统回调onStartCommand()时回传一个空的Intent,除非有 pending intents传递,否则Intent将为null。该模式适合做一些类似播放音乐的操作。
  • START_REDELIVER_INTENT:若系统在onStartCommand()执行并返回后kill了service,那么service会被recreate并回调onStartCommand()并将最后一个Intent回传至该方法。任何 pending intents都会被轮流传递。该模式适合做一些类似下载文件的操作。

启动服务

可以结合使用显式 Intent 与 startService(),启动上文中的示例服务 (HelloSevice):

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

startService(intent)方法将立即返回,并回调onStartCommand()(请不要手动调用该方法),若该Service未处于运行状态,系统将首先回调onCreate(),接着再回调onStartCommand()。若您希望Service可以返回结果,那么需要通过调用getBroadcast 返回的PendingIntent启动Service(将PendingIntent包装为Intent),service可使用broadcast 传递结果。
多个启动Service的请求可能导致onStartCommand()多次调用,但只需调用stopSelf() 、 stopService()这两个方法之一,就可停止该服务。


停止服务

一个启动的Service必须管理自己的生命周期。系统不会主动stop或destroy一个运行的Service,除非系统内存紧张,否则,执行完onStartCommand()方法后,Service依然运行。停止Service必须手动调用stopSelf()(在Service中)或调用stopService()(在启动组件中)。
一旦调用了上述两种方法之一,系统会尽快destroy该Service(as soon as possible)。

如果服务同时处理多个 onStartCommand() 请求,则您不应在处理完一个启动请求之后停止服务,因为您可能已经收到了新的启动请求(在第一个请求结束时停止服务会终止第二个请求)。为了避免这一问题,您可以使用 stopSelf(int) 确保服务停止请求始终基于最近的启动请求。也就说,在调用 stopSelf(int) 时,传递与停止请求的 ID 对应的启动请求的 ID(传递给 onStartCommand() 的 startId) 。然后,如果在您能够调用 stopSelf(int) 之前服务收到了新的启动请求, ID 就不匹配,服务也就不会停止。

注意:为了避免浪费系统资源和消耗电池电量,应用必须在工作完成之后停止其服务。 如有必要,其他组件可以通过调用 stopService() 来停止服务。即使为服务启用了绑定,一旦服务收到对 onStartCommand() 的调用,您始终仍须亲自停止服务。


创建绑定Service

通过其他组件调用bindService()方法可以绑定一个Service以保持长连接(long-standing connection),这时一般不允许其他组件调用startService()启动Service。

当其他组件需要与Service交互或者需要跨进程通信时,可以创建一个bound Service。

为创建一个bound Service,必须重写onBind()回调,该方法返回一个IBinder接口。该接口时组件与Service通信的桥梁。组件调用bindService()与Service绑定,该组件可获取IBinder接口,一旦获取该接口,就可以调用Service中的方法。一旦没有组件与Service绑定,系统将destroy它,您不必手动停止它。

为创建一个bound Service,必须定义一个接口 ,该接口指定组件与Service如何通信。定义的接口在组件与Service之间,且必须实现IBinder接口。这正是onBind()的返回值。一旦组件接收了IBinder,组件与Service便可以开始通信。

多个组件可同时与Service绑定,当组件与Service交互结束后,可调用unbindService()方法解绑。bound Service比start Service要复杂,故我将在后续单独翻译。


向用户发送通知

运行中的Service可以通过Toast Notifications 或 Status Bar Notifications 向用户发送通知。Toast是一个可以短时间弹出的提醒框。Status Bar是顶部状态栏中出现的带有图标的信息,用户可以通过下拉状态栏获得具体信息并执行某些操作(如启动Activity)。

通常,Status Bar用于通知某些操作已经完成,如下载文件完成。当用户下拉状态栏后,点击该通知,可获取详细内容,如查看该下载的文件。


运行前台Service

前台Service用于动态通知消息,如天气预报。该Service不易被kill。前台Service必须提供status bar,只有前台Service被destroy后,status bar才能消失。
举例来说,一个播放音乐的Service必须是前台Service,只有这样用户才能确知其运行状态。为前台Service提供的status bar可以显示当前音乐的播放状态,并可以启动播放音乐的Activity。


调用startForeground()可以启动前台Service。该方法接收两个参数,参数一是一个int型变量,用户指定该通知的唯一性标识,而参数二是一个Notification用于配置status bar,示例如下:

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

注意:提供给 startForeground() 的整型 ID 不得为 0。

调用stopForeground()来移除(remove)前台Service。该方法需传入一个boolean型变量,表示是否也一并清除status bar上的notification(indicating whether to remove the status bar notification as well)。该方法并不会停止Service。但是,如果停止正在前台运行的Service,那么notification 也一并被清除。


管理Service生命周期

从Service的启动到销毁,有两种路径:

  • A started service:需手动停止。该服务在其他组件调用 startService() 时创建,然后无限期运行,且必须通过调用 stopSelf() 来自行停止运行。此外,其他组件也可以通过调用 stopService() 来停止服务。服务停止后,系统会将其销毁。
  • A bound service:可自动停止。该服务在另一个组件(客户端)调用 bindService() 时创建。然后,客户端通过 IBinder 接口与服务进行通信。客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务。

这两条路径并不是毫不相干的:当调用startService()start一个Service后,您仍可以bind该Service。例如,可以通过使用 Intent(标识要播放的音乐)调用 startService() 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 bindService() 绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 stopService() 或 stopSelf() 不会真正停止服务。
Service生命周期


实现Service的生命周期回调

与 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 生命周期回调方法不同,您不需要调用这些回调方法的超类实现。


在两条生命周期路径中,都包含了两个嵌套的生命周期:

  • 完整生命周期( entire lifetime
    ):从onCreate()被调用到onDestroy()返回。与Activity类似,一般在onCreate()中做一些初始化工作,而在onDestroy()做一些资源释放工作。如,若Service在后台播放一个音乐,就需要在onCreate()方法中开启一个线程启动音乐,并在onDestroy()中结束线程。无论是startService()
    还是 bindService()启动Service,onCreate() 和 onDestroy()均会被回调。
  • 活动生命周期(active lifetime):从onStartCommand() 或
    onBind()回调开始。由相应的startService() 或 bindService()调用。 若是Start
    Service,那么Service的活动生命周期结束就意味着其完整生命周期结束 (the active lifetime ends the
    same time that the entire lifetime
    ends),即便onStartCommand()返回后,Service仍处于活动状态;若是bound
    Service,那么当onUnbind()返回时,Service的活动生命周期结束。

注意:针对Start Service,由于Service中没有类似onStop()的回调,所以在调用stopSelf() 或 stopService()后,只有onDestroy()被回调标志着Service已停止。

以上是关于Android官方文档之Services的主要内容,如果未能解决你的问题,请参考以下文章

Android官方文档之App Resources(中)

Android官方文档之App Resources(下)

Android官方文档之App Resources(上)

Android官方开发文档Training系列课程中文版:OpenGL绘图之图形绘制

Android官方开发文档Training系列课程中文版:OpenGL绘图之环境配置

Android官方开发文档Training系列课程中文版:通知用户之构建通知