Services

services是一种可以长时间在后台运行并且不与用户交互的一种组件,其他的应用组件可以开启一个service,并且这个service会一直在后台运行即使用户切换到其他的应用。另外一个组件可以bind to service来和它交互甚至进行(IPC)进程间交互。例如:一个service在后台处理网络,播放音乐,执行I/O,或者和一个contentprovider进行交互。

service有两种启动方式

  • Started:当应用组件以startService()形式启动service的时候,service被start。一旦启动,即使启动service的组件已经销毁,这个service还是运行在后台。通常,一个started的service执行一个单一操作并且不会给调用者返回结果。比如:service下载或者上传一个file,当这个操作完成时,这个service应该 stop itself
  • Bound
    当service是以bindService()形式启动的时候。一个bound service提供一个client-server的接口允许组件来和service进行交互,发送请求,得到结果,甚至跨进程间通信(IPC)。一个bound service只有当另一应用组件bound to it才会运行。可以有多个组件在同一时间bind to service。当所有的组件unbind,这个service也会被销毁。

你可以同时使用onStartCommond()和onBind()。
不管你的Application是started,bound或者是两者都有,任何应用组件可以使用这个service(即使是另外一个应用的组件),就像通过intent启动activity那样来使用service。你也可将service在manifest文件中申明为private类型,这样其他应用就无法访问该service。

注意:一个service是运行在宿主进程的主线程中-service不会自己创建thread也不会运行在单独的进程中(除非你明确的去设置)。这就意味着,如果你的service去执行一些cpu密集型的工作或者阻塞操作(比如MP3后台播放或者是网络操作),你应该自己在一个service中新建一个thread来进行那些操作。通过使用独立的线程,你就会减少ANR的错误。

你应该使用service还是thread?
service是一个运行在后台的一个组件并且用户不和应用进行交互。因此只有当你需要service才去创建。当你需要在主线程外执行任务,但是用户和你的应用交互,你应该去创建一个thread而不是service。比如你只希望你的activity运行的时候播放音乐,你可以在onCreate()中创建thread,在onStart()中去启动,并且在onStop()中去stop线程。当然考虑使用AsyncTask或者HandlerThread(),而不是传统的thread。

基本使用

为了创建service,你必须创建service的子类。你需要重写一些方法来对应service生命中的一些关键节点并且提供一些机制让组件bind to这个service。一些非常重要的回调方法你应该去重写:

  • onStartCommand()
    系统会回到这个函数,当组件(如activity)通过startService请求service 启动。一旦这个方法被执行,这个service被启动并且运行在后台。如果你重写了这个函数,你有责任在service任务完成时调用stopSelf()或者stopService()来停止这个service
  • onCreate()
    当service第一次被创建系统会回调这个方法来执行一些初始化的程序(在service调用onStartCommand()或者onBind()之前),如果这个service已经运行,这个方法不会被调用。
  • onDestroy()
    当service不再被使用并且正在被销毁系统会调用这个方法。你的service应该重写这个方法来清理像threads,注册的监听器,receivers等等。这是service接收的最后一个回调。

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

如果一个组件调用bindService(0来创建service(onStartCommond()不会被调用),这个service只会在组件bound to 这个service的时候运行。一档这个service被所有的clients unbound,系统会销毁这service。

android系统只会在内存比较低的情况下强制一个service停止并且它必须为那些还有用户focus的actvity回收系统资源。如果service是bound to一个有用户focus的activity的,它很少可能被killed,如果service是被声明run in the foreground,它几乎不可能被killed。另外,如果service被started并且是长时间运行,系统会随着时间的推移降低其在后台任务中的地位并且service会有很大的可能被kill。如果你的service 是started的,并且来运行时间比较长的任务,你应该优雅的应对系统的restart service。如果系统杀死了你的service,当系统有足够资源时service会被restart(当时,需要依据onStartCommand()的返回值)。

在manifest中声明一个service

添加一个service标签作为Application的子标签。

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

其他的一些属性比如:启动这个service需要的权限,这个service应该运行在哪一个process。android:name是唯一一个必须的属性-它具体说明了service的名称。一旦你发不了自己的应用,就不应该更改这些名称。

另外,你可以通过android:exported来保证你的service只能被你自己的应用访问。即使是通过具体的intent。

##通过start创建一个service

一个started service是组件通过调用startService()来启动,最终会调用service的onStartCommand()方法。
当一个service是被started,他的生命周期独立于启动它的组件并且service运行在后台,即使启动它的组件已经被销毁。所以,如果后台任务完成,service应该自己stop自己通过调用stopSelf(),或者其他的组件可以通过调用stopService()来暂停它。
应用组件可以通过传递一个intent(标明想要启动的service并且携带service需要的数据)给startService()。这个service在onstartCommand函数中接收到该intent。
一般有如下两个类可以继承来创建service
**service**
所有service的基类。记住它默认是在主线程中运行,所以需要自己新建一个线程来处理任务。
**IntentService**
这是service的子类,它包含一个工作线程来处理所有的请求,如果你不需要同时处理多任务这个是最好的选着。你只需要做的是重写onHandleIntent(),在这里接收所有的intent。
IntentService为我们做了如下的一些事情:

 - 创建了一个默认的工作线程来处理所有传递给onStartCommand()的intents
 - 创建了一个工作队列每次传递一个intent给onHandleIntent()。所以你永远不必担心多线程。
 - 当所有的请求被处理完成,service会被停止,你永远不必调用stopSelf()
 - 提供了默认的onBind()实现(返回null)
 - 提供了默认的onStartCommand()实现来将intent发送到工作队列最终交给onHandleIntent()来处理
我们需要做的是:一个构造函数并且实现onHandleIntent()函数。如果你决定重写其他的回到函数,比如onCreate(),onStartCommand()或者onDestroy(),一定要调用父类 实现,来保证可以正确的处理工作线程的生命周期
除了onHandleIntent(),另外唯一一个你不需要调用父类实现的是onBind()(如果你的service需要bind()你需要重新实现)

```java
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. 
      try { 
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          // Restore interrupt status. 
          Thread.currentThread().interrupt();
      } 
  } 
} 




<div class="se-preview-section-delimiter"></div>

继承service

正如你所看到的,使用IntentService让你start一个service非常的容易。但是如果你需要你的service执行多线程任务(而不是通过工作队列来处理请求)。你可以继承Service来处理每一个intent。

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. 
          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 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()方法必须返回一个整数值,这个整数值描述了如果系统杀死了service系统该如何继续service的执行。返回的值必须是如下几个常数:

START_NOT_STICKY
如果系统在onStartCommand()之后杀死了service,不会去创建service,除非还有需要处理的intent。这个是安全的选择来避免不需要的时候运行service。
START_STICKY
如果系统在onStartCommand()返回之后杀死了service,重新创建service并且调用onStartCommand(),但是不会重新传递最后的intent。系统调用onStartCommand()的时候回传递一个空intent,除非还有未完成的intent,这种情况这些未完成的intent会被传递进去。这种适合媒体播放器(或者类似的services)不会执行命令,但是会无限期的运行并且等待任务。
START_REDELIVER_INTENT
如果在onStartCommand()返回后系统杀死service,会重新创建service并且会调用onStartCommand()并且传递最后处理的intent。任何等待处理的intent都会依次被传递。这种适合需要立即回复的service比如下载文件。


###继承service
正如你所看到的,使用IntentService让你start一个service非常的容易。但是如果你需要你的service执行多线程任务(而不是通过工作队列来处理请求)。你可以继承Service来处理每一个intent。

```java
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. 
          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 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()方法必须返回一个整数值,这个整数值描述了如果系统杀死了service系统该如何继续service的执行。返回的值必须是如下几个常数:

START_NOT_STICKY
如果系统在onStartCommand()之后杀死了service,不会去创建service,除非还有需要处理的intent。这个是安全的选择来避免不需要的时候运行service。
START_STICKY
如果系统在onStartCommand()返回之后杀死了service,重新创建service并且调用onStartCommand(),但是不会重新传递最后的intent。系统调用onStartCommand()的时候回传递一个空intent,除非还有未完成的intent,这种情况这些未完成的intent会被传递进去。这种适合媒体播放器(或者类似的services)不会执行命令,但是会无限期的运行并且等待任务。
START_REDELIVER_INTENT
如果在onStartCommand()返回后系统杀死service,会重新创建service并且会调用onStartCommand()并且传递最后处理的intent。任何等待处理的intent都会依次被传递。这种适合需要立即回复的service比如下载文件。

启动一个service

你可以通过传递一个intent(明确指定一个service)给startService()来启动一个service。Android系统调用service的onStartCommand()并且将intent传递给它。(你不应该直接调用onStartCommand())
startService方法立即返回android系统会调用service的onStartCommand()方法。如果service还没有运行,系统会首先调用onCreate(),然后调用onStartCommand()。
如果service没有提供binding,通过startService()传递intent是应用组件和service通信的唯一方式。但是如果你需要service返回结果,启动service的客户端可以创建一个PendingIntent并且传递给service。service就可以使用广播来传递结果。
多次请求启动service会导致多次调用service的onStartCommand().但是只需要调用一次stopSelf()或者stopService()来暂停service。

暂停一个service

通过start启动的service必须管理自己的生命周期。系统不会stop或者destroy service除非系统需要回收内存并且service会继续运行在onStartCommand()返回后。所以,service必须通过stopSelf()来停止自己或者其他的组件调用stopService()。

创建一个bound service

应用组件可以通过调用bindService()将service绑定到自己。
如果你想和应用中其他的应用组件通信或者想将本应用的一些功能(通过IPC)暴露给其他的应用。你应该创建一个bound类型的service。
为了创建一个bound service,你必须实现onBind()回调函数来返回一个Binder(它定义了和service通信的接口)。其他的应用组件可以通过调用bindService()来获得接口然后调用service的函数。这个service仅仅服务于绑定到service上的组件,所以如果没有组件绑定到service上,系统会销毁service()。

为了创建一个bound service。你需要做的第一件事是定义客户端和service通信的接口。这个接口必须继承IBinder接口并且是onBinder()必须返回的。一旦客户端接收到IBinder,客户端就会通过这个接口来和service进行交互。
多个clients同时立刻绑定到service。当一个客户端完成了和service的交互,他会调用unbindService()解绑。一旦没有客户端绑定到service,系统会销毁这个service。

给用户发送提醒

一旦运行,service可以通过Toast Notification或者Status Bar Notifications来提醒用户事件。
相对于吐司(屏幕上显示一段时间后就会消失)状态栏提醒可以在状态栏提供一个图标和消息,用户点击可以执行相应的动作(比如启动activity)
通常,一些后台工作结束了(比如文件下载完成)状态栏通知是最好的一个选择,用户可以对其作出相应的选择。

在前台进程运行一个service

一个前台service是用户比较在意的因此系统内存不足时不会杀死这个service。一个前台service必须在状态栏提供一个提醒,并且放在“onGoing” heading,这就意味着notification不能解除除非service被stop或者从前台移除。
比如,一个使用service播放音乐的音乐播放器应该被设置成前台的,因为用户很在意这些操作。在状态栏上的提醒可以表明当前播放的歌曲并且允许用户启动一个activity

管理service的生命周期

service生命周期相对于activity的来说简单些。但是,密切关注service的创建,销毁等更重要,因为service可以在用户没有 意识到的情况下运行在后台。
两种方式:

  • 当其他组件调用startService()时service被创建。然后service运行起来,并且自己必须通过stopSelf()来停止该service。其他的组件可以通过stopService()来暂停service。当service暂停,系统会销毁service。
  • 如果其他组件通过bindService()来启动service。客户端需要通过Binder接口来和service交互。客户端可以通过unbindService()来关闭连接。多个clients可以同时连接到同一个service。当所有的客户端都解绑定service。系统会销毁service。

    这两种方式不是完全分离的。也就是说你可以绑定到通过startService()启动的service。比如,一个后台的音乐服务可能通过startService()来启动,后来用户可能想要控制播放器的一些操作或者获得更多关于当前歌曲的信息,一个activity可以通过调用bindService()来绑定到service()。这种情况下stopService()或者stopSelf()不会实际的去暂停service直到所有的与之绑定的客户端将绑定解除。
    这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值