Android Services

592 阅读18分钟

这篇文章主要是对google官方文档的整理,加入了一些自己的理解与分析。

什么是service

service是一个app组件,它可以在后台执行需要长时间运行的操作,并且不提供任何用户界面。其他的app组件可以开启一个service,这个service会一直在后台运行即使用户已经切换到了另一个app。另外,app组件可以通过绑定service的方式来与这个service交互,甚至进行进程间通信。比如,service可以完成网络请求,播放音乐,读写文件,或者与内容提供者交互,所有这些任务都可以在后台进行。

下面介绍三种不同的service:

  • 前台service. 前台service用来执行能够被用户感知的操作。比如,一个音乐app会使用前台service来播放音乐。前台service必须在通知栏显示一个通知。前台service会一直运行即使用户并没有与app交互

  • 后台service 后台service则用来执行不能被用户直接感知的操作。比如,app可以使用service来压缩自己使用的空间,这里一般使用的就是后台service。

  • 被绑定的service 当app组件调用bindService()方法来绑定一个service时,这个service变成了一个被绑定的service。一个被绑定的service可以提供一个客户端-服务端的接口,通过这个接口app组件可以与被绑定的service交互,比如发送请求,接收结果,甚至进行IPC。一个被绑定的service的生命周期与它绑定的app组件保持一致。多个app组件可以绑定到同一个service,但是当所有的app组件解绑之后,这个service就会被销毁。

虽然这篇文章是把开启service和绑定service分开来讲的,但是一个service是可以同时开启和绑定的,换句话说就是service在被开启(来无限运行)的同时,也允许绑定。

不考虑一个service是被开启,被绑定,还是两者皆有,任何app组件都可以用使用activity的方式去使用service(即使service来自另一个app),这个方式就是使用Intent。然而,我们也可以在manifest文件中把servcie声明为private来阻止其他的app的访问。

service的基本使用

要使用service,我们需要创建一个Service的子类或者使用已经存在的子类。在我们的实现类中,需要重写一些回调方法来处理service的一些关键的生命周期并且提供一个机制使app组件可以在恰当的时候绑定到这个service。下面是一些重要的回调方法:

  • onStartCommand() 当app组件调用startService()来开启service时,这个service的onStartCommand()方法会被调用。当这个方法执行的时候,service被开启并且会一直在后台运行。如果你通过startService()开启了service,那么你就应该在service的工作完成之后调用stopSelf()或者stopService()。如果你只想绑定一个service,就没必要实现这个方法了。

  • onBind() 当app组件调用bindService()来绑定一个service时,这个servcie的onBind()方法就会被调用。当你实现这个方法时,你需要返回一个IBinder对象来提供一个可以让客户端和服务端进行通信的接口。如果不需要绑定的话,这个方法返回null就好了。

  • onCreate() 当service对象被初始化的时候,这个方法会被调用来执行一些一次性的设置流程。如果service已经在运行了,这个方法将不会被调用。

  • onDestroy() 当service被销毁的时候这个方法会被回调。你应该在这个方法里面清理掉那些消耗资源的东西比如线程,监听器等等。这是servcie接收到的最后一个回调。

如果app组件通过startService()开启service,这个service会一直运行直到它调用stopSelf()结束自己或者别的app组件调用stopService()来结束它。

如果app组件调用bindService()来开启service,那么这个service的生命周期与绑定在它身上的app组件保持一致。当所有的客户端与service解绑之后,系统会销毁这个service。

只有当资源不足的时候,android系统才会结束service来保证当前用户正在使用的activity的资源。如果service被绑定到当前用户正在使用的activity,那么它不太可能会被杀掉。如果service被声明成前台service,它就几乎不可能被杀掉了。如果servcie是被开启的并且正处于长期运行的状态,随着时间流逝,这个service在系统中的重要性会逐渐降低,变得容易被系统杀掉。 所以如果你开启了一个service,那么你最好完善你的代码以应对它被系统杀掉又被重启的情况。如果系统杀掉了你的servcie,只要资源足够系统便会重启service,重启时的细节则取决于onStartCommand()的返回值。

在manifest中声明service

像activity和其他的app组件一样,你必须在项目的manifest文件中声明所有的service。

那么如何声明一个service呢,在下添加一个就可以了。下面是一个🌰:

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

标签中有很多属性可以自定义,比如service的权限,service运行在那个进程等等。其中只有「android:name」必须的,这个属性定义了service的名字。

创建被开启的Service类

被开启的service指的是被其他app组件通过startService()创建的service,开启service时会调用service的onStartCommand()方法。

当service被开启时,它的生命周期与开启它的app组件是没有关系的。这个service可以在后台无限运行,即使开启它的app组件已经被销毁了。同样的,当service的工作完成之后,它应该调用stopSelf()来关闭自己,或者其他的app组件调用stopService()来关掉它。

app组件比如activity可以通过startService()开启服务,并且传递一个Intent对象,这个Intent对象指定了要开启的service并且包含了从app组件传递到service的数据。service在onStartCommand()方法的参数中接收到这个Intent对象。

举个🌰,一个activity需要将一些数据上传到远程数据库中。我们可以在activity中开启一个service,通过intent将数据传递给service。service接收到数据之后,请求网络,将数据存入数据库中。当任务完成之后,service调用stopSelf停止。

一般情况下,我们可以继承下面两个类来创建一个被开启的service:

  • Service 这个类是所有service的基类。当你继承这个类时,如果service需要完成耗时操作,你需要开启一个子线程来完成这些操作。默认情况下service运行在app的主进程的主线程中,所以它会影响app中正在运行的activity的性能。

  • IntentService IntentService是Service的子类,它自带一个工作线程来处理客户端发出的请求,这些请求会按顺序一个一个处理。如果你的service不需要同时处理多个请求,那么IntentService是你最好的选择。IntentService的onHandleIntent()方法会接收所有请求的intent,然后你可以在工作线程中异步的处理这些请求。

继承IntentService

因为大多数的开启的线程不需要同时处理多个请求,所以IntentService是不错的选择。

IntentService有如下特性:

  • 它创建了一个默认的工作线程来执行所有onStartCommand()方法接收到的intent对象。(所以你不必自己创建子线程)
  • 它创建了一个工作队列来一个一个的将intent对象传递给你的onHandleIntent()方法。(所以你不需要担心多线程问题)
  • 所有的客户端请求处理完毕之后,会自动终止service。(所以不需要你来调用stopSelf())
  • 帮你实现了onBind()方法。(返回null)
  • 提供了onStartCommand()的默认实现。(将所有的请求放入一个工作队列,然后一个一个的传递给onHandleIntent())

所以你要做的就是实现onHandleIntent()方法,在其中完成你的需求。然后,你也需要给IntentService提供一个构造方法。

public class HelloIntentService extends IntentService {

  /**
   * 	   构造方法中必须调用父类的构造方法
   * 	   参数是创建的工作线程的名字
   */
  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.
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          // Restore interrupt status.
          Thread.currentThread().interrupt();
      }
  }
}

如果你需要重写其他的回调方法,比如onCreate(),onStartCommand()或者onDestroy(),记住一定要调用父类的实现来保证IntentService可以正常处理工作线程的生命周期。

举个🌰,onStartCommand()方法必须返回父类的默认实现,这样接收到的intent才能正确传递给onHandleIntent()方法。

除了onHandleIntent()方法外,唯一不需要调用父类实现的方法是onBind()。如果你的service同时要求绑定,你才需要实现这个方法。

继承Service

使用IntentService可以让你轻轻松松的创建一个被开启的service。然而,如果你要求你的service多线程处理请求(而不是通过一个工作队列依次处理请求),你可以直接继承Service来处理每个请求。

作为对比,下面的例子将展示Service的子类是如何完成上一个例子中IntentService子类完成的工作的。

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // 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.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // Restore interrupt status.
              Thread.currentThread().interrupt();
          }
          // 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 doesn't 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
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @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 = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.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();
  }
}

可以看出,直接使用Service的工作量要大得多。

然而,因为你在onStartCommand()方法中自己处理intent,你可以同时处理多个请求。如果你有这个需求,你可以给每一个请求创建一个子线程,这样每个请求就可以立即运行而不需要等待前一个请求处理完毕。

需要注意的是,onStartCommand()方法需要一个int类型的返回值。这个int数决定了系统杀掉这个service之后如何重启service。IntentService默认帮你选择了重启策略,但是你也可以修改这个策略。onStartCommand()方法的返回值必须是以下几个常量之一:

首先我们明确一个事实,android在内存不足的时候会杀掉service,等到某一个时刻内存充足了,则可能会去重启service。下面这几个常量都遵循上面的规则,我们主要讨论它们的不同点:

  • START_NOT_STICKY 如果内存充足了,但是此时没有待定的请求,则不会重启service,否则按上面默认的处理。

  • START_STICKY 如果内存充足了,但是此刻没有待定的请求,也会重启service,重启时会向onStartCommand()中传递一个值为null的intent,否则按上面默认的处理。

  • START_REDELIVER_INTENT 如果内存充足了,也会重启service,重启时会向onStartCommand()中传递最后一个intent,如果有待定的请求,会跟在前面说的那个intent后面。

开启一个service

在app组件中调用startService()或者startForegroundService()方法,参数为intent对象,就可以开启一个service。android系统调用service的onStartCommand()方法,并且把intent传过去,intent指明了要开启的那个service。

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

如果service不提供绑定,那么通过startService()方法传递的intent是app组件与service通信的唯一途径。然而,如果你想让service返回一个结果,客户端开启service的时候可以传递一个PendingIntent,这个PendingIntent可以用于广播。然后service就可以通过广播返回结果了。

有多个请求同时开启service会导致onStartCommand()方法被多次调用。然而,终止service的时候只需要调用一次stopSelf()或者stopService()。

终止一个service

一个开启的service必须管理自己的生命周期。也就是说,系统并不会主动终止或者销毁service除非内存不足。service必须通过stopSelf()或者其他的app组件调用stopService()来结束。

一旦上面说的两个方法调用了,系统会立即销毁service。

如果你的service并行处理多个请求,你不应该在处理完某一个请求后终止service,因为此时service很可能已经接收了一个新的请求。为了解决这个问题,你可以使用stopSelf(int)方法来确保你用来终止service的请求是最新的请求。也就是说,当你调用stopSelf(int)时,你传入启动service的请求的id会去匹配终止service的请求的id。然后,如果service在调用stopSelf()之前接收到了一个新的请求,id匹配不上,stopSelf(int)方法就会失效。

创建被绑定的Service类

app组件可以通过调用bindService()方法来绑定一个service并且创建一个长连接。一般情况下被绑定的service不允许通过startService()来开启。

被绑定的service在以下情况中使用:app中有组件需要与service交互;通过IPC将app中的功能暴露给其他的app使用。

创建一个被绑定的service的时候,你需要实现onBind()方法并返回一个IBinder对象来定义与service交互的接口。其他的app组件就可以调用bindService()方法来获取这个接口并且调用service中的方法。service存在的意义就是为了服务绑定在它身上的app组件,所以当没有组件绑定在service身上时,service就会自动被销毁掉。

创建一个被绑定的service的时候,你必须定义一个指明客户端与服务端如何通信的接口。这个位于客户端与服务端之间的接口必须是一个IBinder的实现类,并且通过onBind()方法返回。当客户端接收到这个IBinder对象之后,就可以通过这个接口与service进行交互。

多个客户端可以同时绑定到一个service上。当一个客户端与service交互完成之后,就需要调用unBindService()方法来解绑。当没有任何客户端绑定到service上的时候,service就会被销毁。

有多个方式可以来实现一个被绑定的service,并且这些实现比实现被开启的service复杂。接下来,我会单独写文章介绍,这里先按下不表。

给用户发送通知

当一个service正在运行的时候,他可以通过toast或者状态栏通知来告知用户一些事件。

toast时一个在当前窗口弹出一小段时间然后消失的通知。状态栏通知则会在状态栏显示一个图标和一个消息,用户可以点击这个通知来出发一个操作。

一般情况下,状态栏通知是后台任务完成之后通知用户的最好方式,并且用户可以主动采取某种操作。

在前台运行service

前台service是一直被用户感知的并且几乎不会被系统杀掉。前台service必须在通知栏展示一个「正在运行」通知。这意味着除非service终止或者被从前台移除,否则这个通知不会消失。

举个🌰,一个音乐播放器播放音乐的service就应该在前台运行,因为用户能非常明显的感知到这个操作。状态栏的通知应该显示当前的歌曲并且应该允许用户打开一个与播放器交互的activity。相似的,一个记录用户轨迹的app也应该使用前台service来定位当前位置。

为了让你的service运行在前台,调用startForeground()。这个方法需要两个参数:一个用来唯一标识的int数和一个用于状态栏的通知对象。

Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent =
        PendingIntent.getActivity(this, 0, notificationIntent, 0);

Notification notification =
          new Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
    .setContentTitle(getText(R.string.notification_title))
    .setContentText(getText(R.string.notification_message))
    .setSmallIcon(R.drawable.icon)
    .setContentIntent(pendingIntent)
    .setTicker(getText(R.string.ticker_text))
    .build();

startForeground(ONGOING_NOTIFICATION_ID, notification);

要将service从前台移除,调用stopForeground()方法。这个方法接收一个boolean参数,代表是否同时移除状态栏的通知。这个方法并不终止service。然而,如果你终止掉一个正在前台运行的service,通知也会被移除。

管理servcie的生命周期

service的生命周期比activity的生命周期简单得多。然而,你需要密切关注你的service如何是被创建和销毁的因为service运行在后台而不被用户感知到。

service的生命周期,从它的创建到销毁可以从下面两条路径来研究:

  • 一个被开启的service 当另一个ap组件调用startService()时被创建。然后它会一直运行直到自己调用stopSelf()方法。另一个组件也可以通过调用stopService()方法来终止service。当一个service被终止之后,系统会销毁它。
  • 一个被绑定的service 当另一个ap组件调用bindService()时被创建。然后客户端会通过IBinder接口与服务端通信。客户端可以通过调用unbindService()方法来关闭长连接。多个客户端可以绑定到同一个service,如果所有的客户端都解绑了,系统会销毁service。service不需要终止自己。

这两条路径并不是完全分离的。你也可以绑定到一个已经开启的service上面。举个🌰,你可以通过startService()来开启一个后台音乐的service并且传递一个intent来标明要播放的歌曲是哪一首。稍后,很可能用户想要对播放器进行一些操作或者获取当前正在播放的歌曲的信息,一个activity就可以通过bindService()绑定到service上面。在这种情况下,stopService()或者是stopSelf()方法无法终止service除非所有的客户端都已经解绑。

##实现声明周期回调方法

像activity一样,你可以实现service的生命周期回调方法来监控service的状态变化并且在合适的时机执行一些操作。下面的代码展示了service的生命周期方法

public class ExampleService extends Service {
    int startMode;       // indicates how to behave if the service is killed
    IBinder binder;      // interface for clients that bind
    boolean allowRebind; // 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
    }
}

avatar

上面的图片中主要展示了开启的service和绑定的service的生命周期。尽管在图片中这两种情况的生命周期时分开展示的,但是有一点要时刻记住,不管这个service是如何创建,它都一定可以被客户端绑定。一个通过startService()初始化的service仍然可以回调onBind()方法当有客户端绑定的时候。

实现下面这个方法,你就可以监控这两种方式嵌套使用的service的生命周期:

  • 一个service完整的生命周期是在onCreate()方法和onDestroy()方法之间。和activity一样,service在onCreate()方法中完成初始化操作,在onDestroy()方法中释放所有资源。比如,一个音乐播放的service可以在service的onCreate()方法中创建播放音乐的子线程,然后在onDestroy()方法中关闭线程。

  • 而service活跃的生命周期则开始于onStartCommand()或者onBind()方法。每个方法都接收到传递过来的intent对象。如果service是被开启的,活跃时间跟实际的结束时间是一致的。如果是绑定的,则活跃时间在onUnbind()方法返回时结束。