IntentService 源码分析|周末学习

「本文已参与 周末学习计划,点击查看详情 」

这是我参与更文挑战的第6天,活动详情查看: 更文挑战

本文基于 Android 9.0.0 的源代码

framework/base/core/java/andorid/app/IntentService.java

IntentService简介

IntentServiceService的子类,比普通的Service增加了额外的功能。先看Service本身存在两个问题:

  • Service不会专门启动一条单独的进程,Service与它所在应用位于同一个进程中
  • Service 也不是一个线程,它和线程没有任何关系,所以它不能直接处理耗时操作。

Service主要用来在后台进行任务处理,例如后台播放音乐、下载文件、上传文件等等。由于Service是运行在主线程中的,也有一定的时间限制,如果在主线程中对一个任务的处理时间超过了限制,进程就会出现“应用不响应”,即ANR, Application Not Responding。为了避免这样情况,一般都会在Service里用新的thread处理一些可能需要更多处理时间的任务。

其实Android早就替我们设计了一种更方便的Service + Thread模式,就是本文要讲的IntentService,通过它可以很方便地实现在Service中使用Thread进行耗时任务的处理。

基础用法

扩展 IntentService 类

以下是 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.
      try {
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          // Restore interrupt status.
          Thread.currentThread().interrupt();
      }
  }
}
复制代码

您只需要一个构造函数和一个 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 基类时实现同类服务。该基类包含更多代码,但如需同时处理多个启动请求,则更适合使用该基类。

扩展服务类

正如上一部分中所述,使用 IntentService 显著简化了启动服务的实现。但是,若要求服务执行多线程(而不是通过工作队列处理启动请求),则可扩展 Service 类来处理每个 Intent。

为了便于比较,以下提供了 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.
          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() 方法必须返回整型数。整型数是一个值,用于描述系统应该如何在服务终止的情况下继续运行服务(如上所述,IntentService 的默认实现将为您处理这种情况,不过您可以对其进行修改)。从onStartCommand() 返回的值必须是以下常量之一:

  • START_NOT_STICKY

    如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。

  • START_STICKY

    如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。

  • START_REDELIVER_INTENT

    如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

以上大部分来自官方文档

源码分析

创建工作线程

public void onCreate() {
    // TODO: It would be nice to have an option to hold a partial wakelock
    // during processing, and to have a static startService(Context, Intent)
    // method that would launch the service & hand off a wakelock.

    super.onCreate();
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();

    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}
复制代码

IntentService第一次启动的时候会调用其onCreate来完成一些初始化操作:

首先创建了一个HandlerThread对象,这就是前面一直提到的Worker线程。大家对HandlerThread都很了解,那这个HandlerThread是什么呢?简单来说,它就是内部有一个消息循环队列的线程,我们知道默认的线程内部是没有消息循环队列的,这就导致我们无法直接在其内部使用HandlerAndroid为了方便使用,直接提供了一个含有消息循环队列的HandlerThread。具体见HandlerThread 源码分析

利用已创建的HandlerThread内部的消息循环创建一个 ServiceHandler对象,这样它的消息处理函数handleMessage就会在对应的线程中执行了。

接收和处理请求

我们其他组件通过startService来发送请求的,结合service的生命周期,会执行onStartCommand回调

public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
复制代码
@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}
复制代码

从这段代码看到,onStartCommand会直接调用onStart,在这里对发送过来的请求接收并通过mServiceHandler进行处理。

private final class ServiceHandler extends Handler {
    public ServiceHandler(Looper looper) {
        super(looper);
    }

    @Override
    public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1);
    }
}
复制代码

handleMessage中对接收到的请求用onHandleIntent进行实际处理,而onHandleIntent就是我们在使用过程中必须实现的处理逻辑。

销毁Worker线程

前面提到:当所有请求都被处理完成后,service就会被销毁,这是如何实现的呢?在上面看到handleMessage方法里在处理完当前请求时会调用stopSelf(msg.arg1)来尝试停止当前服务,之所以说“尝试”,是因为它不一定能真正停止服务。还是来看下stopSelf(int)的实现代码:

/**
     * Old version of {@link #stopSelfResult} that doesn't return a result.
     *  
     * @see #stopSelfResult
     */
public final void stopSelf(int startId) {
    if (mActivityManager == null) {
        return;
    }
    try {
        mActivityManager.stopServiceToken(
                new ComponentName(this, mClassName), mToken, startId);
    } catch (RemoteException ex) {
    }
}
复制代码
public final boolean stopSelfResult(int startId) {
    if (mActivityManager == null) {
        return false;
    }
    try {
        return mActivityManager.stopServiceToken(
                new ComponentName(this, mClassName), mToken, startId);
    } catch (RemoteException ex) {
    }
    return false;
}
复制代码

stopSelf(int)的声明里提到它是stopSelfResult(int)的老版本,唯一的区别就是没有返回值。那我们直接看stopSelfResult(int)的声明,其中提到只有在当前的service的最近一次启动是startId发起的才会被停止。我们把这句话放在IntentService的场景里去理解,如果说当前接收到3个请求,在处理第一个请求后打算去停止服务,但是调用stopSelf(int)的时候发现最后一次启动是第三个请求发生的,并不会停止服务;处理完第二个请求后是类似的,只有在处理完第三个请求后,去尝试停止服务,这时发现最近一次启动就是它发起的,可以去停止服务了。

停止服务时,其onDestroy会得到调用:

@Override
public void onDestroy() {
    mServiceLooper.quit();
}
复制代码

具体的用法例子可以参考IntentService 示例与详解

总结

IntentService相比父类Service而言,最大特点是其回调函数onHandleIntent中可以直接进行耗时操作,不必再开线程。其原理是IntentService的成员变量 Handler在初始化时已属于工作线程,之后handleMessage,包括onHandleIntent等函数都运行在工作线程中。

如果对IntentService的了解仅限于此,会有种IntentService很鸡肋的观点,因为在Service中开线程进行耗时操作也不麻烦。我当初也是这个观点,所以很少用IntentService

但是IntentService还有一个特点,就是多次调用onHandleIntent函数(也就是有多个耗时任务要执行),多个耗时任务会按顺序依次执行。原理是其内置的Handler关联了任务队列,Handler通过Looper取任务执行是顺序执行的。

这个特点就能解决多个耗时任务需要顺序依次执行的问题。而如果仅用Service,开多个线程去执行耗时操作,就很难管理。

由于大多数启动服务都不必同时处理多个请求(实际上,这种多线程情况可能很危险),因此使用 IntentService 类实现服务也许是最好的选择。

IntentService特征

  • 会创建独立的worker线程用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent。
  • 创建worker队列,用于将 Intent 逐一传递给 onHandleIntent() 实现,这样您就永远不必担心多线程问题。
  • 所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service
  • ServiceonBind()提供默认实现,返回null
  • ServiceonStartCommand提供默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现。
  • IntentService不会阻塞UI线程,而普通Serveice会导致ANR异常
  • 可以启动 IntentService 多次,而每一个耗时操作会以工作队列的方式在IntentServiceonHandleIntent 回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推`

参考

IntentService的实际应用场景

理解 IntentService 原理

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享