Services

Service是一个应用组件但不提UI,用来在后台执行长任务。其它app的组件可以启动一个service,但是这个service一直在后台运行,不论用户转换到其它的app。其它组件可以绑定到一个service并交互,甚至进行内部进程通信。常见的使用service的例子有:处理网络交接,播放音乐,执行文件I/O,和ContentProvider交互。

一个service基本上有两种方式:

Started:

当app组件调用startService()函数时,就启动了一个service。一旦被启动,这个service可以在后台无限期运行,即便启动它的组件已经被销毁。通常情况下,一个启动的service执行一个单一的操作而且不返回结果。比如,service可以用来下载或上载文件到网络中,当这个操作完成后,这个service就自动停止。

Bound

当app组件调用bindService()函数时,就绑定了一个service。一个绑定的service提供一个客户端/服务器接口,以便组件能够与service交互:发送请求,获取结果,甚至通过内部进程通信在不同的进程执行。一个被绑定的service只有在其它组件绑定它才运行(即没有组件绑定时,这个service就自动销毁)。多个组件可以同时绑定一个service,当这些组件都不再绑定这个service时,这个service就被销毁。

在这部分将分别讨论这两种service,当然一个service可以同时工作在两种方式下,即被启动(无限期运行)并允许被绑定。你需要做的仅仅是在service中实现两个函数:

onStartCommand():允许组件启动这个service

onBind():运行组件绑定这个service。

无论你的app是被启动或者绑定或者两者都是,其它的组件都可以通过intent使用这个service(和使用其它activity一样)。但是你可以在service的manifest文件中声明这个service是私有的,那么其它的app就不能使用这个service。这在Declaring the service in the manifest中提到。

注意:service在app的主进程中运行,service不会自己创建独立的进程或者在其它的进程中运行(除非你特别指定)。所以如果你的service是去做一些繁重的cpu工作或者阻塞操作(比如mp3播放或者网络),你应该在你的service中创建一个新的进程来做这些工作。这种做的话,就会减少应用无法响应的风险,而且保证你app的主线程可以专门用于用户交互。

The Basics(基础)

可以通过继承service这个类来创建自己的service(或者其它存在的service子类)。在类里面,你必须实现一些常用的回调函数来处理service的生命周期,和一些函数使得其它组件可以绑定。

onStartCommand()

当其它组件(比如activity)通过调用函数startService()请求启动一个service时,这个函数就会被调用。一旦这个函数被调用,这个service就被启动并可以在后台中无限期运行。在实现这个函数时,你必须调用stopSelf()或者stopService来停止这个service(如果你当当想让你的service被绑定,那么你可以不用实现这个函数)。

onBind()

当其它组件通过调用bindService()想绑定一个service时,这个函数被调用。在这个函数的实现中你必须提供一个IBinder用于客户端用来和service通信的接口。无论你的service是被启动或者绑定,你都必须实现这个函数。当不想你的service被绑定时,你可以在这个函数中返回null。

onCreate()

当service第一次被创建时,这个函数被调用,用来执行一次设置操作(在onStartCommand()或者onBind()被调用之前),当然如果service已经在运行,那么这个函数不会被调用。

onDestroy()

当service不会再被使用时,这个函数被调用,在这个函数中,你必须清楚所有使用的资源,比如进程,注册的listeners,receivers等。这是service最后一个被调用的函数。

如果一个service是通过startService()被启动的话(即onStartCommand()被调用),那么这个service将会一直运行直到自己调用stopSelf()停止自己或者其它组件通过调用stopService()停止这个service。

如果一个组件通过调用函数bindService()来创建一个service时,那么这个service只会在有其它组件绑定它时才运行,一旦没有其它组件绑定它,系统将会销毁这个service。

android 系统只有在现在内存很少而当前获得用户焦点的activity需要内存时才会强制中断service。如果这个service刚好绑定到当前获得用户焦点的activity中,那么这个service被中断的概率很小,如果这个service声明运行在前端,那么这个service基本不会被强制中断。不然当一个service被启动并长时间运行,那么随着时间流逝,系统将会降低这个service在后台task中的位置,这个service也越来越有可能被销毁。当你的service被启动后,你比粗仔细的设计这个service来处理这个service被系统重新启动。如果系统销毁你的service,那么这个service将会在资源可用的情况下被重新启动(虽然这个也取决与onStartCommand()返回的值)。关于系统销毁一个service详见Process and Threading文档。

下面将会展示怎么创建各种service并在其它app组件中如何使用这些service。

Declaring a service in the manifest (在manifest中声明一个service)

跟activity一样,你必须在manifest文件中声明service。在<application>中添加<service>元素,如下面代码:

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>
更多关于如何在manifest中声明service详见<service>

还可以在<service>中添加更多属性,比如启动这个service所需要的权限,这个service在那个进程中运行。android:name指明了这个service的名字,是必须有的。一旦你发布你的app,你不能修改这个名字,如果你修改了,那么将影响那些通过显式intent来启动或绑定这个service的代码块运行错误(详见Things that cannot change)。

为了保证你的app是安全的,仅仅使用显式的intent来启动或绑定一个service,不要使用隐式的intent。如果你确实允许启动一个模糊的service,那么在<service>中声明对应的intent-filter,并在intent中使用service的类名,但你必须调用函数setPackage()来设置对应service的package,以便更准确的找到目标service。

你可以通过设置android:exported属性为false来确保你的service适用于你的app。这样就可以阻止其它app来启动你的service,即便它们使用显式的intent。

Creating a Started Service (创建一个被启动的service)

Started service是其它组件通过调用startService()函数来启动这个service,其结果是调用了这个service的onStartCommand()函数。

当一个service被启动后,它有自己独立的生命周期,并和启动它的组件的生命周期无关,即便启动它的组件被销毁,这个service还是可以无限期运行,由于这个service可以无限期运行,所以当这个service完成任务后,应调用stopSelf()来停止自己或者其它组件通过调用stopService()函数来停止这个Service。

其它组件(比如activity)尅通过调用startService()函数来启动一个Service,将一个包含指定要启动的Service的名字和用于Service的相关数据的intent传入到这个函数中,在Service的onStartCommand()函数中会接收到这个intent。

比如,有一个activity,想要将一些数据保存到网上的数据库中,这个activity就可以启动一个Service,通过intent将要发送的数据传到这个Service中。这个Service在onStartCommand()这个函数接收到这个intent,然后就连接到网络中在对应的数据库中进行操作。当完成相应的操作后,这个Service就自动停止并被销毁。

注意:默认情况下,Service是运行在app的主线程(即声明这个Service的app所在的进程中)。所以当你的Service执行复杂的工作或者阻塞操作时将会影响你app的性能。为了不影响app的性能,你应该在你的Service中创建一个线程来执行相应的操作。

你可以使用通过继承下面两个Service来创建一个started service:

Service

这是所有Service的基类,当你继承这个类时,记得在类里面创建一个新的线程来执行Service所有的工作,因为这个Service使用你app的主线程,如果不创建新的线程,有可能会影响你的app的性能。

IntentService

Service的子类,在这个类中使用一个工作线程来处理所有的启动要求,在一个时间段只能执行一个,这个类适合你的Service不需要同时处理多个请求。在这个类中,你仅仅需要实现onHandleIntent()这个函数,这个函数将会接收intent并在后台处理相关工作。

下面将展示如何使用这两个类来实现想要的Service。

Extending the IntentService class (继承intentService类)

因为大多数started Service不需要同时处理多个请求,通常使用IntentService来实现你的Service。

IntentService做了下列工作:

1. 创建一个独立于你app主线程的工作线程,用来执行所有传入onStartCommand()函数的intent。

2. 创建一个工作队列,每次将一个intent传到onHandleIntent()函数中,所以你不用担心多线程问题。

3. 当处理完所有的请求时,自动停止Service,所以你不需要调用stopSelf()函数。

4. 实现了onBind()函数,默认返回null。

5. 实现了onStartCommand(),将请求的intent发送到工作队列中,这些Intent将会被发送到onHandleIntent()函数中。

结合上面内容,你只需要在这个类的onHandleIntent()函数中完成客户端的请求的工作(当然你必须实现下的构造函数)

下面是实现一个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()等,记得在这个函数调用超类的对应函数。

比如在onStartComand()函数中必须返回默认的实现:

@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,你会发现相关代码比IntentService多得多,但是当你需要同时处理多个请求时,你只能这样做了。

Extending the Service class (继承Service 类)

可以看到使用IntentService来实现自己的Service非常简单,但是当你的Service要执行多线程(而不是通过一个工作队列来处理请求),那么你只能继承Service这个类了。

为了比较,下面的例子通过继承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();
  }
}

可以看到比IntentService复杂得多。

由于你可以处理each call to onStartCommand() yourself,所以你可以同时执行多个请求。对于每个请求,你可以创建一个新的线程。

注意在onStartCommand()函数中必须返回一个整数,这个整数用于当系统销毁这个Service后如何再启动这个这个Service(相当于这个Service的ID),onStartCommand()函数返回的值必须是下面几个之一:

START_NOT_STICKY

系统在onStartCommand()返回后,销毁这个Service,而且没有重新创建这个Service,直到有intent想要启动这个Service。这种是最安全的方式,可以避免不必要的运行你的Service,app也可以简单的重新启动没完成的任务。

START_STICKY

系统在onStartCommand()返回后,销毁这个Service,重新创建这个Service并调用onStartCommand()函数,但并没有派发之前的那个Intent,而是传入一个为null的Intent到这个函数中,若之前有Intent要派发给这个Service,这些之前的Service将会被派发给这个Service。这种适合媒体播放器(或类似activity),长时间运行着等待任务。

START_REDELIVER_INTENT

系统在onStartCommand()返回后,销毁这个Service,调用onStartCommand()函数,将之前最后一个intent传入到这个函数中,而接下来的其它派发给这个Service的intent将按顺序派发给这个Service。这适合于一些执行完一个任务后马上被复原的activity(如下载文件)。

Starting a Service (启动一个Service)

你在一个activity或者其它app组件中调用函数startService()来启动Service,在这个函数中传入相应的intent。系统将会调用Service的onStartCommand()函数,将相应的intent传给这个函数。

例如在activity中可以使用显式的intent来启动一个Service,如下:

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

上面的代码执行时,startService将会马上返回,系统将会调用对应的Service的onStartCommand函数,并将intent传给这个函数。如果这个Service不在系统运行,那么android系统会先调用这个Service的onCreate()函数,然后在调用onStartCommand()函数。

如果你的Service不提供绑定服务,那么只能通过startService的方式使得app的组件和Service通信。但是如果想让Service返回一个结果,那么启动Service的客户端可以创建一个PendingIntent用于广播,并将这个PendingIntent包含在传给startService的intent中。这样Service可以用broadcast来传递结果。

多个请求启动Service将导致这个Service的onStartCommand()函数被多次调用,但是只需要在一个请求中停止这个Service就可以停止这个Service。

Stopping a service (停止一个service)

一个已经启动的这个service必须管理自己的生命周期,即系统不会停止或销毁这个service,除了系统必须回收内存。一个service在onStartCommand()函数被调用后将会一直运行,所以service必须通过自己调用stopSelf()来停止自己,或其它app组件通过调用stopService()来停止这个service。

一旦这两个函数的其中一个被调用,系统将会立即销毁这个service。

但是当你的service处理多个请求时,你不能在你的service处理其它请求时而停止你的service,例如你在第一个请求完成后停止这个service,但有可能在处理第一个请求的时候有其它组件请求这个service,那么在第一个请求完成后停止service将会影响到其它请求。为了避免这个问题,你可以使用stopSelf(int)函数来保证你是在处理完最后一个请求时停止这个service的,你通过将onStartCommand()返回的请求ID传给stopSelf(int)函数来指明在ID对应的请求中停止service,当系统在你调用stopSelf()函数之前接收到一个新的请求时,此时的请求ID就会给你之前传给stopSelf()的ID不同,那么这个service将不会被停止,这样可以保证不影响后面其它请求。

注意:一旦你的service完成相应的工作,你的app必须停止这个service,否则会消耗系统资源和耗电。如果有必要,其它组件可以调用stopService停止这个service。就是你的service允许被绑定,你也必须在当你的service的onStartCommand()函数被调用时,接下来停止这个service。

关于更多service的生命周期,请参照Managing the Lifecycle of a Service。

Creating a Bound Service (创建一个绑定Service)

一个bound Service允许其它app组件通过调用bindService()函数来绑定它创建一个长期连接(一般情况下不允许其它组件通过调用startService启动它)。

当你想和来自其它activity或组件的Service进行交互时,你只能创建一个bound Service,或者当你想通过内部进程通信将你的app的功能告诉其它app时,你也可以创建一个Service。

如何创建一个bound Service,你可以通过实现onBind()回调函数并返回一个IBinder接口,通过这个接口与Service通信。其它app组件可以通过调用bindService()火气这个接口,并通过这个接口调用Service的函数。当没有其它app组件绑定到Service时,系统将会销毁这个Service(你不必停止一个bound Service,但是对于started Service,你必须主动停止它)。

为了床架一个bound Service,你嗽跹必须定义一个接口,这个接口指明了客户端和Service之间如何通信,这个接口必须实现IBinder接口而且必须在onBind()返回。一旦客户端收到这个接口,将会通过这个接口与Service交互。

多个客户端可以同时绑定到一个Service,但一个客户端完成与Service交互时,将会调用unbindService()函数来解除绑定。一旦没有client绑定到一个bound Service,系统将会销毁这个Service。

有很多方式可以实现一个bound Service,而且实现方式都比stated Service复杂得多。将在一个独立部分讨论Bound Service。

Sending Notifications to the user (发送通知给用户)

Service一旦运行,可以通过Toast Notification或者Status Bar Notification通知用户相应的事件(比如完成文件下载)。

Toast是出现在屏幕上的一天信息,但只显示一会儿,然后就消失。而Status Bar在status bar中生成一个图标(包含相关信息),用户可以选择一个图标来执行相关动作(比如打开一个activity)。

通常status bar是用来通知后台运行的任务完成的最好方式(比如完成文件下载),用户现在可以对这文件进行操作。当用户从status bar的下拉列表中选择一个图标是,肯那个会启动一个activity(比如浏览下载好的文件)。

详见Toast Notifications 和 Status Bar Notifications。

Running a Service in the Foreground (在前端运行一个Service)

一个foreground Service是用户能感知的的活动(比如播放音乐),系统将不会因为内存不足而销毁这个Service。一个foreground Service必须在status bar有个通知,表示这个通知不能没丢失,除非这个Service被停止或者从前端移除。

比如,一个音乐播放器通过一个Service来播放音乐,这个Service应该被放到前端,因为用户明显直到这个活动。在status bar对应的通知可能是显示当前播放的音乐名字,通过这个通知(音乐名字)可以启动一个activity来跟音乐播放器交互。

通过调用startForeground()可以是你的Service在前端运行。在这个函数中传入两个参数:一个用于标识通知的整数,和一个在status bar对应的Notification对象:

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

注意分配给Notification的ID不能为0。

可以通过调用stopForeground()函数来将Service从前端移开。在这个函数传入一个boolean的参数,指示是否也将在status bar中的Notification移除,但这个函数并不会销毁Service。如果被停止的Service正好在foreground运行,那么对应的Notification也会被移除。

更多信息详见Creating Status Bar Notification

Managing the Lifecycle of a Service (管理Service的生命周期)

Service的生命周期比activity简单多了。但是你还是必须直到Service怎么被创建和销毁因为Service可以在用户不知道的情况下在后台运行。

Service根据started或bound可以有两种不同的生命周期过程:

1. started Service:当其它组件调用startService()时这个Service被创建,之后这个Service独立运行并调用stopSelf()将自己停止。当前其它组件也可以通过调用stopService()停止这个Service,一旦被停止,系统可以销毁这个Service。

2. bound Service:当其它组件调用bindService()时这个Service被绑定,之后client通过一个IBinder接口与这个Service通信。client通过调用unBindService()解除绑定。多个client可以同时绑定到一个Service,当没有client绑定时,系统将会销毁这个Service。

这两种路径并不是完全独立的,对于当你绑定到一个已经通过startService()被启动的Service时,例如,一个后台音乐Service通过被startService启动来播放音乐,接下来有可能用户想在这个播放器进行一些控制或者获取当前播放的音乐的信息,这样一个activity就可以绑定到这个Service来。在这种情况下,stopService()和stopSelf()并不会停止这个Service,而是等到没有client没有绑定到这个Service时,这两个函数才能发挥作用。

Implementing the lifecycle callbacks (实现生命周期对应的回调函数)

跟activity一样,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的回调函数,在Service的回调函数中你不需要调用超类的相应回调函数。


通过实现这些回调函数,你可以监控两种Service的生命周期过程:

1. entire lifetime(整个生命周期):在onCreate()和onDestroy()被调用之间。在onCreate()中进行设置,在onDestroy()中释放所有使用的资源。比如,一个音乐播放Service可以在onCreate()中创建进程来播放音乐,在onDestroy()停止这个进程。

函数onCreate()和onDestroy()是所有的Service都会被调用的函数,不论这个Service是通过startService或者bindService被创建。

2. active lifetime(活跃过程):从onStartCommand()或onBind()函数被调用开始。两个函数分别用来接收由startService和bindService传来的intent。如果Service被started,那么active lifetime将和entire lifetime同时结束;如果Service是被bound,那么active lifetime将在onUnbind()函数返回后结束。

注意:虽然一个started Service可以通过调用stopSelf或者stopService被停止,但是没有相应stop的回调函数(比如onStop())。所以除非这个Service是被绑定到一个client,不然系统将会在这个Service被停止后销毁这个Service,即onDestroy()是唯一的回调函数。

上图展示了Service的回调函数,虽然图上将有started Service和bound service的回调函数分开,但是请记住对任何service,不论这个service是怎么被started,都潜在的允许其它client绑定它。所以一个通过onStartCommand()的service,里面的onBind()函数有可能会被调用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值