Service的使用

Author Avatar
罗炜光 10月 04, 2016
  • 在其它设备中阅读本文章

概述

Service是一种没有界面且能长时间运行于后台的应用组件.其它应用的组件可以启动一个服务运行于后台,即使用户切换到另一个应用也会继续运行.另外,一个组件可以绑定到一个service来进行交互,即使这个交互是进程间通讯也没问题.例如,一个service可能处理网络事物,播放音乐,执行文件I/O,或与一个内容提供者交互,所有这些都在后台进行.

Service与Thread的区别

进程是系统最小资源分配单位,而线程是则是最小的执行单位,线程需要的资源通过它所在的进程获取

Thread:Thread 是程序执行的最小单元,可以用 Thread 来执行一些异步的操作。

Service:Service 是android的一种机制,当它运行的时候如果是本地Service,那么对应的 Service 是运行在主进程的主线程上的。如果是远程Service,那么对应的 Service 则是运行在独立进程的主线程上。

Thread 的运行是独立的,也就是说当一个 Activity 被 finish 之后,如果没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,不再持有该 Thread 的引用,也就是不能再控制该Thread。另一方面,没有办法在不同的 Activity 中对同一 Thread 进行控制。

例如:如果 一个Thread 需要每隔一段时间连接服务器校验数据,该Thread需要在后台一直运行。这时候如果创建该Thread的Activity被结束了而该Thread没有停止,那么将没有办法再控制该Thread,除非kill掉该程序的进程。这时候如果创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一个Service,而系统也只会创建一个对应 Service 的实例)。
因此可以把 Service 想象成一种消息服务,可以在任何有 Context 的地方调用 Context.startService、Context.stopService、Context.bindService、Context.unbindService来控制它,也可以在 Service 里注册 BroadcastReceiver,通过发送 broadcast 来达到控制的目的,这些都是 Thread 做不到的。

重要的回调方法

public void onCreate()

系统在service第一次创建时执行此方法,来执行只运行一次的初始化工作(在调用它方法如onStartCommand()或onBind()之前).如果service已经运行,这个方法不会被调用.

public int onStartCommand(Intent intent, int flags, int startId )

系统在其它组件比如activity通过调用startService()请求service启动时调用这个方法.一旦这个方法执行,service就启动并且在后台长期运行.如果你实现了它,你需要负责在service完成任务时停止它,通过调用stopSelf()或stopService().(如果你只想提供绑定,你不需实现此方法).

onStartCommand的返回值

onStartCommand()必须返回一个整数.这个整数描述了在系统杀死它的事件中系统如何继续这个服务

  • START_NOT_STICKY

如果系统在onStartCommand()方法返回之后杀死这个服务,那么直到接受到新的Intent对象,这个服务才会被重新创建。这是最安全的选项,用来避免在不需要的时候运行你的服务。

  • START_STICKY

如果系统在onStartCommand()返回后杀死了这个服务,系统就会重新创建这个服务并且调用onStartCommand()方法,但是它不会重新传递最后的Intent对象,系统会用一个null的Intent对象来调用onStartCommand()方法,在这个情况下,除非有一些被发送的Intent对象在等待启动服务。这适用于不执行命令的媒体播放器(或类似的服务),它只是无限期的运行着并等待工作的到来。

  • START_REDELIVER_INTENT

如果系统在onStartCommand()方法返回后,系统就会重新创建了这个服务,并且用发送给这个服务的最后的Intent对象调用了onStartCommand()方法。任意等待中的Intent对象会依次被发送。这适用于那些应该立即恢复正在执行的工作的服务,如下载文件。

  • START_STICKY_COMPATIBILITY

START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

onStartCommand的参数flags

  • START_FLAG_REDELIVERY

如果你实现onStartCommand()来安排异步工作或者在另一个线程中工作, 那么你可能需要使用START_FLAG_REDELIVERY来让系统重新发送一个intent。这样如果你的服务在处理它的时候被Kill掉, Intent不会丢失.

  • START_FLAG_RETRY

表示服务之前被设为START_STICKY,则会被传入这个标记。
启动service的时候,onCreate方法只有第一次会调用,onStartCommand和onStart每次都被调用。onStartCommand会告诉系统如何重启服务,如判断是否异常终止后重新启动,在何种情况下异常终止.

public IBinder onBind(Intent intent)

当组件调用bindService()想要绑定到service时(比如想要执行进程间通讯)系统调用此方法.在你的实现中,你必须提供一个返回一个IBinder来以使客户端能够使用它与service通讯,你必须总是实现这个方法,但是如果你不允许绑定,那么你应返回null.

public boolean onUnbind(Intent intent)

绑定的所有客户端断开连接的时候回调

public void onDestroy()

系统在service不再被使用并要销毁时调用此方法.你的service应在此方法中释放资源,比如线程,已注册的侦听器,接收器等等.这是service收到的最后一个调用.

public void onRebind(Intent intent)

onUnbind返回true且Service重新绑定时回调

startService

  • public ComponentName startService(Intent service)

开始服务

  一个应用组件,比如一个activity可以通过调用startService()启动service同时传递一个指定service和service所用的数据的Intent,service在方法onStartCommand()中接收这个Intent.

  • public boolean stopService(Intent name)

停止服务

一个启动的service,在被其它组件调用startService()来启动时,会导致service的onStartCommand()方法被调用.

bindService

  • public boolean bindService(Intent service, ServiceConnection conn,int flags)

绑定服务

应用组件(客户端)可以调用bindService()绑定到一个service.Android系统之后调用service的onBind()方法,它返回一个用来与service交互的IBinder.

绑定是异步的.它不会立即返回IBinder给客户端.要接收IBinder,客户端必须创建一个ServiceConnection的实例并传给bindService().ServiceConnection包含一个回调方法,系统调用这个方法来传递要返回的IBinder.

  • public void unbindService(ServiceConnection conn)

解绑服务

ServiceConnection

  • public void onServiceConnected(ComponentName name, IBinder service)

系统调用这个来传送在service的onBind()中返回的IBinder.

  • public void onServiceDisconnected(ComponentName name)

Android系统在同service的连接意外丢失时调用这个.比如当service崩溃了或被强杀了.当客户端解除绑定时,这个方法不会被调用.

bindService的Flags

  • BIND_AUTO_CREATE

表示当收到绑定请求时,如果服务尚未创建,则即刻创建,在系统内存不足,需要先销毁优先级组件来释放内存,且只有驻留该服务的进程成为被销毁对象时,服务才可被销毁;

  • BIND_DEBUG_UNBIND

通常用于调试场景中判断绑定的服务是否正确,但其会引起内存泄漏,因此非调试目的不建议使用;

  • BIND_NOT_FOREGROUND

表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行

终止服务

被启动类型的服务必须管理它自己的生命周期。也就是说除非系统要回收系统内存,否则系统不会终止或销毁这个服务,在onStartCommand()方法返回后,这个服务会继续运行。因此而这种类型的服务必须通过调用stopSelf()方法或另一个组件通过调用stopService()方法才能终止。
一旦用stopSelf()方法或stopService()方法请求终止服务,那么系统一有可能就会销毁这个服务。

但是,如果你的服务同时处理多个对onStartCommand()方法的请求,那么在你完成请求启动过程时,不应该终止这个服务,因为你的服务可能正在接受一个新的启动请求(在第一个请求结束时终止服务有可能会终止第二个请求)。要避免这个问题,你能够使用stopSelf(int)方法来确保你请求终止的服务始终是基于最近启动的请求。也就是说,调用stopSelf(int)方法时,你要把那个要终止的服务ID传递给这个方法(这个ID是发送给onStartCommand()方法的)。这样如果服务在你调用stopSelf(int)方法之前收到了一个新的启动请求,那么这个ID就会因不匹配而不被终止。

警告:重要的是你的应用程序要在工作结束时终止它们的服务,从而避免浪费系统资源和电池电量。如果需要,其他的组件能够调用stopService()方法终止服务。即使对于能够绑定的服务,如果这个服务接收了对onStartCommand()方法的调用,你也必须自己来终止这个服务。

Service组件表示在不影响用户的情况下执行耗时的操作或者提供其他应用使用的功能

5.0开始,必须使用显示Intent启动Service组件

当程序使用startService()和stopService()启动、关闭Service时,Service与访问者之间基本上不存在太多关联,因此Service和访问者之间也无法进行通讯,交换数据

实现ServiceConnection.

你的实现必须重写两个回调方法:

onServiceConnected()

系统调用这个来传送在service的onBind()中返回的IBinder.

OnServiceDisconnected()

Android系统在同service的连接意外丢失时调用这个.比如当service崩溃了或被强杀了.当客户端解除绑定时,这个方法不会被调用.

在你的service中,创建一个Binder实例,提供以下三种功能之一:

Binder包含一些可供客户端调用的公开方法.

返回当前的Service实例,它具有一些客户端可以调用的公开方法.

或者,返回另一个类的实例,这个类具有客户端可调用的公开方法并托管于service.

IntentService

这是一个Service类的子类,它使用工作线程来依次处理所有的启动请求,如果你不想要服务同时处理多个请求,那么这是最好的选择。需要你做的所有工作就是实现onHandleIntent()方法,它接受每个启动请求的Intent对象,以便完成后台工作。

因为大多被启动类型的服务不需要同时处理多个请求(这实际是一个危险的多线程场景),因此使用IntentService类来实现自己的服务可能是最好的。
IntentService类执行以下操作:

  1. 创建一个独立与应用程序主线程的默认工作线程,执行所有的给onStartCommand()方法Intent的处理;

  2. 创建一个工作队列,以便每次只给你的onHandleIntent()方法实现传递一个Intent,因此你不必担心多线程的问题;

  3. 所有的启动请求都被处理之后终止这个服务,因此你不需要自己去调用stopSelf()方法;

  4. 提供返回null的onBind()方法的默认实现;

  5. 提供一个给工作队列发送Intent对象的onStartCommand()方法的默认实现和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) {
              }
          }
      }
  }
}

以上就是你做的全部:一个构造器和一个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

前台服务

Service如果要防止尽可能不被系统杀掉,需要设置为在前台运行。

启动为前台服务

  • 创建Notification对象
  • 调用startForeground()方法

终止前台服务

  • 调用stopForeground()方法

检查是否运行

1.如何检查Android后台服务线程(Service类)是否正在运行

Android系统自己提供了一个函数ActivityManager.getRunningServices,可以列出当前正在运行的后台服务线程

private boolean isServiceRunning() {  
    ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);  
    for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {  
        if ("com.example.MyService".equals(service.service.getClassName())) {  
            return true;  
        }  
    }  
    return false;  
}

参考资料

FireOfStar的专栏
Android开发进阶从小工到专家
疯狂Android讲义