欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Services-Android 6.0开发者文档

程序员文章站 2022-07-15 12:06:29
...

原文地址:Service

一、前言

service没有界面,通常用于在后台进行耗时操作。其他应用组件可以启动一个service,此service在启动后可以长时间运行在后台,即使用户切换到了其他应用。另外,应用组件可以绑定service并与它通信,甚至进行进程间通信。service的使用场景有:网络操作、播放音乐、文件IO、content provider操作等。

service有两种形式:

  • started。
    如果应用组件通过startService()启动一个service,那么这个service就是started形式。service通过started启动后,会一直存在于后台,即使启动它的应用组件被销毁。通常,started形式的service会执行单一操作,并不会向调用方返回结果。例如,某个service从网络下载或者上传文件,当此操作完成,它会自己stop自己
  • bound。
    如果应用组件通过bindService()启动一个service,那么这个service就是bound形式。bound形式的service提供client-service接口,允许调用方与service通信,发送请求,获取结果,甚至做跨进程通信。bound形式的service与绑定它的组件生命周期一样长(绑定它的组件销毁,那么此service也会销毁)。service可以被多个组件绑定,这时,当所有的组件都解绑时,service才会被销毁

虽然本文档是将两种形式的service分开描述的,但是你的service可以同时支持两种形式。你的service支持的形式仅仅取决于你实现的回调方法:实现onStartCommand()允许其他组件start,实现onBind()允许其他组件绑定。

不管你的service是否已经被started或者bound,其他应用组件仍然可以使用你的service,即使是其他应用。但是,你可以将service声明为私有(在manifest中设置),从而阻止其他应用的访问。

注意:service运行在宿主进程的主线程——它不会创建自己的线程,也不是在分离出来的进程中运行(除非你指定)。这意味着,你的service需要在子线程进行CPU高消耗的操作或者阻塞操作,从而减少ANR并优化用户体验。


二、基本内容

通过继承Servie类或者它的子类可以创建一个service。在你的service实现中,你需要重写几个回调方法,以处理service的生命周期并提供启动你的service的机制。重要的几个回调方法如下:

  • onStartCommand()。
    当其他组件通过startService()启动service时,会调用此方法。此方法执行后,service就进入start状态且无限期的运行在后台。如果你实现了此方法,那么当功能完成后停止service就成了你的责任(通过stopSelf()或stopService())。如果你仅仅提供绑定的方式,那么你不需要实现此方法。
  • onBind()。
    当其他组件通过bindService()启动service时,会调用此方法。在此方法中,你必须返回一个IBinder,其表示绑定方与你的service通信的接口。你必须实现此方法,但是如果你不希望别的组件绑定此service,可以返回null。
  • onCreate()。
    当service初次创建时调用此方法。你可以在这个方法中进行一些初始化工作(它先于onStartCommand和onBind被调用)。如果service已经运行了,那么此方法不会被调用。
  • onDestroy()。
    当service被销毁时调用此方法。你可以在这个方法中清理占用的资源,比如线程、已注册的listener、receiver等。这个方法是service能收到的最后的回调。

如果其他组件通过startService()启动你的service,那么这个service会在后台一直运行,直到它停止自己-stopSelf(),或者其他组件停止它-stopService()。

如果其他组件通过bindService()启动你的service,那么这个service只有在被绑定期间运行,只要所有的绑定方都解绑,那么service就会被销毁。

Android系统可能会在内存过低时为了回收内存而停止service。如果此service是被有用户焦点的activity绑定的,那么它被销毁的可能性会小一点。如果此service被声明为“前台服务”(run in the foreground),那么它将几乎不会被销毁。其他情况的service,如果在后台停留了很长时间,那么系统会降低它在后台task的优先级,从而很可能被系统销毁。如果你的service已经启动了,那么你需要用某种方式处理由系统发起的重启。如果系统销毁了你的service,那么它会在资源可用的时候尽快重启你的service。

下面的部分将会叙述怎样创建各种类型的service和怎样使用它。

2.1 在manifest中声明service

像其他组件一样,你需要在manifest文件中声明service。

声明方法是在<application>下添加<service>标签,如下所示:

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

<service>标签下你可以添加一些属性,以控制service的一些行为,如要求启动service的权限、指定service运行的进程等。android:name是必须的,它指定了service的类名。在发布你的应用后,你不应该再改变此属性,以防依赖于此属性的显式intent被影响。

不为service声明intent filter可以保证你的service的安全,你可以使用显式intent启动或绑定你的service。如果你决定允许通过非显式intent启动你的service,可以为service添加intent filter,这样就可以不用在intent中添加组件名称了,但是你必须在intent中通过setPackage()指定包名,以提供足够详细的信息来找到目标service。

另外,如果你希望你的service只能在本应用使用,可以将android:exported属性设置为false。


三、创建started形式的service

通过startService()启动的service即started形式的service,其对应service的onStartCommand()方法。

当service通过start启动后,它的生命周期就独立于启动它的组件了,它将在后台持续运行,即使启动它的组件被销毁。因此,这种service应该在工作完成后停止自己(通过stopSelf()方法),或者其他应用通过stopService()停止它。

应用组件,如activity,可以通过startService()启动service,并可以在intent中添加service需要的数据。service将会在onStartCommand()方法中收到此intent。

举个例子,某个activity需要将数据保存到云端数据库。那么此activity可以start一个service,并且将数据传给它(在startService()时通过intent添加数据)。service通过onStartCommand()收到intent并取出数据,然后连接网络、操作数据库。当操作结束后,service就stopSelf,被系统销毁。

注意:默认情况下,service运行在声明它的应用的进程的主线程中。所以,如果在service进行复杂或者阻塞操作会影响用户与activity的交互,在这种情况下,你应用新建一个线程进行这些操作。

通常,你可以继承以下两个类来创建service:

  • Service
    它是所有service的基类。如果你继承了这个类,那么你应该在新的线程中执行各种操作,以防阻塞主线程。
  • IntentService
    它是Service的子类,使用worker线程处理传入的请求。如果你不需要同时处理多个请求,可以选择继承它。你需要做的,只是实现它的onHandleIntent()方法,其会收到每个start请求传入的intent,这样你就可以在后台执行工作了。

下面的部分描述了通过以上两个类创建service的方法。

3.1 继承IntentService类

大多数started形式的service不需要同时处理多个请求,那么你可以使用IntentService来实现你的service。

IntentService做了以下事情:

  • 创建默认的worker线程处理应用主线程通过onStartCommand()分发的intent
  • 创建一个work队列,每次向onHandleIntent()传递一个intent,这样你就不需要担心多线程处理了
  • 在所有的请求都被处理后停止service,所以你不需要调用stopSelf()
  • 提供默认的onBind()方法实现,其返回null
  • 提供默认的onStartCommand()实现,其会将传入的intent传递给work队列,后者会将intent依次传递给onHandleIntent()

你需要做的,只是在onHandleIntent()方法中处理调用方传来的intent。当然,你也需要提供一个简单的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) {
              }
          }
      }
  }
}

如果你想要重写其他的回调方法,比如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,最好不实现此方法。

3.2 继承Service类

在前面我们可以看到,通过IntentService可以很简单的实现一个started形式的service。但是,如果你想要你的service可以处理多线程,而不是通过work队列依次处理请求,可以继承Service类处理每个intent。

作为比较,下面使用Service类实现了与使用IntentService具有相同功能的service。下面的代码逻辑为:将每个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()方法必须返回一个整数。这个整数表示,如果系统kill了你的service将会怎样重启你的service(之前也提到,IntentService已经为你做了这些,你不需要做这些)。onStartCommand()的返回值必须为以下值:

  • START_NOT_STICKY
    如果系统在onStartCommand()方法返回后kill了此service,那么系统不会再重启这个service,除非有pending intent尚未分发。这个值可以避免service的不必要启动,如果你的应用能够重启未完成工作那么可以使用这个值
  • START_STICKY
    如果系统在onStartCommand()方法返回后kill了service,那么系统会重新创建service并调用其onStartCommand()方法,但是不会再传递最后的intent,相反,系统会传入一个空的intent(除非是使用pending intent启动service,那么系统还是会传入原intent)。这个值适合多媒体播放器或类似的service,它们不执行某种命令,而是在后台持续运行等待工作
  • START_REDELIVER_INTENT
    如果系统在onStartCommand()方法返回后kill了service,那么系统会重建service并调用其onStartCommand()方法,并传入最后的intent(pending intent也会依次被传入)

3.3 启动service

你可以在某个activity或其他应用组件中启动service,调用startService()并传入指定了目标service的intent即可。系统会调用目标service的onStartCommand()方法并传入你的intent(你不应该直接调用onStartCommand()方法)。

下面的例子说明了怎样使用显式intent启动前面提到的service:

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

startService()方法会立刻返回,之后系统会调用目标service的onStartCommand()方法。如果目标service并没有在运行,那么系统会先调用onCreate(),然后调用onStartCommand()。

如果service不支持绑定方式,那么通过startService()传递intent就是应用组件和service通信的唯一方式了。但是,如果你需要service向应用组件返回结果,那么应用组件可以先创建一个广播的PendingIntent并传递给service,然后service可以用这个广播发送结果。

多次start一个service会调用多次onStartCommand()。stop此service只需调用一次stopSelf()或stopService()即可。

3.4 停止service

started形式的service必须自己管理自己的生命周期。这是因为在onStartCommand()方法返回后,service会持续运行,除非系统需要回收内容,否则系统不会停止或销毁service。所以service必须通过stopSelf()停止自己,或者其他组件通过stopService()停止它。

调用stopSelf()或stopService()后,系统会尽快销毁此service。

如果service在onStartCommand()中同时处理多个请求,那么你不应该在某个请求被处理完成后停止这个服务,因为可能会有新的请求进来。要避免这种情况,你可以使用stopSelf(int)方法,它会保证你的stop请求总是基于最近的start请求。当你调用stopSelf(int)时,你需要传入start请求的id(通过onStartCommand()的startId获得),以指定你的stop请求对应了哪个start请求。这样的话,当service收到新的start请求,之前的stopSelf(int)中的ID就会与新的start请求不匹配,service就不会被停止了。

注意:你的应用应该在service完成工作后停止它,以防浪费系统资源、消耗电量。如果必要的话,其他应用组件可以通过stopService()停止此service。即使你使用的是bound形式的service,你也得在收到onStartCommand()调用后停止你的service


四、创建bound形式的service

bound形式的service允许应用组件通过bindService()绑定它,以创建一个持续的连接。通常bound形式的service不允许用于组件通过startService()启动它。

如果你希望service能与其他应用组件互动或者通过进程间通信分享功能,可以使用bound形式的service。

要创建bound形式的service,你必须实现onBind()方法,并返回一个定义了通信接口的IBinder。其他应用组件通过bindService()获取接口,并通过其中的方法与service通信。bound形式的service只在有应用组件绑定它时运行,如果没有组件绑定到此service,系统会销毁它(你不必像started形式的service那样自己停止service)。

如果有多个应用组件绑定了service,那么应用组件在结束通信后,需要通过unbindService()解绑,当所有的应用组件都解绑了,系统会销毁此service。

bound形式的service比started形式的service难的多,所以这部分内容另写一篇文档。


五、向用户发送通知

service在运行时,可以通过Toast Notification或者Status Bar Notification通知用户一些信息。

Toast Notification是在桌面上显示一段时间的信息,Status Bar Notification会在通知栏显示图标和信息,用户可以点击此通知来进行某个动作。

通常,Status Bar Notification是后台工作完成后进行通知的最好方式,用户可以看到并与它互动。当用户点击了通知,通知可以进行某些动作。


六、在前台运行service

前台service指用户可以“看到”的service,系统不会在内存紧张的时候kill前台service。前台service必须在状态栏显示一个通知,此通知位于顶栏下面。除非service停止或者从前台移除,否则此通知不会被关闭,。

例如,音乐应用会在service中播放音乐,此service一般为前台service,用户可以明确的看到此service。状态栏中的通知表示当前播放歌曲,并且允许用户点击以打开音乐应用。

你可以通过startForeground()让你的service在前台运行。这个方法有两个参数:一个是整数,表示通知的id,另一个是要显示的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);

注意:startForeground()的整数ID不能为0

你可以通过stopForeground()方法将service从前台移除。这个方法需要一个boolean参数,表示是否将状态栏通知一起移除。这个方法不会停止service。当然,如果你直接停止了service,那么它也会从前台移除(包括状态栏通知)。


七、管理service生命周期

service的生命周期类似于activity。但是你需要特别注意service创建和销毁过程,因为service可以在用户不知情的情况下运行在后台。

service的生命周期从创建到销毁有两条路线:

  • started service
  • bound service

这两条路线不是完全分离的,你可以绑定已经通过startService()启动的service。例如,后台音乐service可能被startService()启动(传入要播放的音乐的信息),之后,用户可能想要获取当前音乐的信息,那么就会有一个activity通过bindService()绑定此service。在这个例子中,stopService()或者stopSelf()在解绑以前不能真正的停止此service。

7.1实现生命周期方法

类似于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生命周期的两个环:

  • entire lifetime
    entire lifetime从service的onCreate()调用开始到onDestroy()返回结束。类似于activity,service在onCreate()中初始化设置,并在onDestroy()中释放资源。例如,音乐service可以在onCreate()中新建线程播放音乐,在onDestroy()中停止线程。
    不管service是通过startService()创建的还是通过bindService()创建的,onCreate()与onDestroy()方法都会被调用。
  • active lifetime
    active lifetime从onStartCommand()或onBind()开始,这两个方法会处理传入的intent。started形式的service的active lifetime与entire lifetime一起结束。bound形式service的active lifetime从onUnbind()返回后结束

注意:虽然started形式的service是通过stopSelf()或stopService()停止的,但是没有对应的回调方法,即没有onStop()方法。所以,除非service被某个组件绑定,否则当此service进入stopped状态,系统便会销毁它——onDestroy()是唯一会被调用的方法

下图1表示了service的典型回调方法。虽然图中将started形式的service与bound形式的service分开了,但是注意,不管service是怎样被启动的,都可以进行bind或start。所以,通过onStartCommand()初始化的service仍然可以收到onBind()的调用。

Services-Android 6.0开发者文档

相关标签: service