This is the sixth day of my participation in Gwen Challenge

This article is based onAndroid 9.0.0The source code

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

Introduction of IntentService

IntentService is a subclass of Service that adds additional functionality over regular Service. There are two problems with the Service itself:

  • ServiceInstead of specifically starting a single process,ServiceIn the same process as the application in which it resides
  • ServiceIt is not a thread, and it has nothing to do with threads, so it cannot handle time-consuming operations directly.

Service is used to perform background tasks, such as playing music, downloading files, and uploading files in the background. Since a Service runs on the main thread, it also has a time limit. If the processing time of a task in the main thread exceeds the time limit, the process experiences an “Application Not Responding”, or ANR, Application Not Responding. To avoid this, it is common to use new threads in services to handle tasks that may require more processing time.

Actually Android early for us to design a kind of more convenient Service + Thread model, is this article to tell IntentService, through which can be easily implemented using Thread for time-consuming tasks in the Service of processing.

Basic usage

Extend the IntentService class

Here is an example IntentService implementation:

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(a) {
      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(); }}}Copy the code

All you need is a constructor and an implementation of onHandleIntent().

If you decide to override other callback methods as well (such as onCreate(), onStartCommand(), or onDestroy()), be sure to call the superclass implementation so that IntentService can properly handle the worker thread lifecycle.

For example, onStartCommand() must return the default implementation (that is, how to pass the Intent to 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);
}
Copy the code

The only way you don’t need to call a superclass from it, other than onHandleIntent(), is onBind() (which you only need to implement if the service allows binding).

In the next section, you’ll learn how to implement similar services when extending the Service base class. This base class contains more code, but is more suitable if you need to process multiple start requests simultaneously.

Extended Service class

As described in the previous section, using IntentService significantly simplifies the implementation of starting the service. However, if the Service is required to perform multiple threads (rather than handle the launch request through a work queue), the Service class can be extended to handle each Intent.

For comparison purposes, the following code example is provided for the implementation of the Service class, which does exactly the same thing as the above example using IntentService. That is, for each start request, it uses a worker thread to execute the job, and only one request at a time.

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 jobstopSelf(msg.arg1); }}@Override
  public void onCreate(a) {
    // 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(a) {
    Toast.makeText(this."service done", Toast.LENGTH_SHORT).show(); }}Copy the code

As you can see, this requires more work than using IntentService.

However, because you handle each call to onStartCommand() yourself, you can execute multiple requests at the same time. This example doesn’t do that, but if you want to, you can create a new thread for each request and then run those threads immediately (rather than wait for the last request to complete).

Note that the onStartCommand() method must return an integer. An integer is a value that describes how the system should continue to run the service if it terminates (as mentioned above, the default implementation of IntentService will handle this for you, though you can modify it). The value returned from onStartCommand() must be one of the following constants:

  • START_NOT_STICKY

    If the system terminates the service after onStartCommand() returns, the system will not rebuild the service unless there is a pending Intent to deliver. This is the safest option to avoid running the service when it is not necessary and when the application can easily restart all outstanding jobs.

  • START_STICKY

    If the system terminates the service after onStartCommand() returns, the service is rebuilt and onStartCommand() is called, but the last Intent is not redelivered. Instead, onStartCommand() is called with an empty Intent unless there is a pending Intent to start the service (in which case those intents will be delivered). This applies to media players (or similar services) that do not execute commands but run indefinitely and wait for jobs.

  • START_REDELIVER_INTENT

    If the system terminates the service after onStartCommand() returns, the service is rebuilt and onStartCommand() is called with the last Intent passed to the service. Any pending intents are delivered in sequence. This applies to services that actively perform jobs that should be restored immediately, such as downloading files.

Most of this comes from official documents

Source code analysis

Creating a worker thread

public void onCreate(a) {
    // 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);
}
Copy the code

When IntentService is first started, onCreate is called to perform some initialization operations:

We first create a HandlerThread object, which is the Worker thread mentioned earlier. So you know a lot about handlers and threads, but what is this HandlerThread? Simply put, it is a thread with an internal message loop queue. We know that the default thread does not have an internal message loop queue, which prevents us from using a Handler directly inside it. Android provides a HandlerThread with a message loop queue for ease of use. See HandlerThread source code analysis for details

Create a ServiceHandler object using the message loop inside the created HandlerThread so that its message handler function, handleMessage, is executed in the corresponding thread.

Receive and process requests

Our other components send requests through startService and, combined with the life cycle of the service, execute the onStartCommand callback

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

As you can see from this code, onStartCommand calls onStart directly, where the incoming request is received and processed by the 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); }}Copy the code

In the handleMessage, the incoming request is actually handled with onHandleIntent, which is the processing logic we must implement in the process of using it.

Destroying Worker threads

The service is destroyed when all requests have been processed. As you can see from the handleMessage method above, stopSelf(msg.arg1) is called to try to stop the current service after processing the current request. “Try” because it may not actually stop the service. 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) {
    }
}
Copy the code
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;
}
Copy the code

The stopSelf(int) declaration mentions that this is the older version of stopSelfResult(int), the only difference being that there is no return value. StopSelfResult (int) will only be stopped if the last start of the current service was made by startId. IntentService = IntentService = IntentService = IntentService = IntentService = IntentService = IntentService = IntentService = IntentService = IntentService = IntentService After processing the second request, it is similar, except that after processing the third request, it tries to stop the service, and then it finds that the last startup was initiated by it and can stop the service.

When a service is stopped, onDestroy is called:

@Override
public void onDestroy(a) {
    mServiceLooper.quit();
}
Copy the code

For specific usage examples, see the IntentService example and explanation

conclusion

IntentService (IntentService) performs time-consuming operations directly in its onHandleIntent (IntentService) callback. IntentService’s member variable Handler is initialized to belong to the worker thread, and handleMessage, including onHandleIntent functions, are then run in the worker thread.

If that’s all you know about IntentServices, you might think that IntentServices are useless because it’s easy to open threads for time-consuming operations in a Service. That’s why I rarely use IntentService.

IntentService, however, also calls onHandleIntent functions multiple times, executing them in sequence. The principle is that the built-in Handler is associated with the task queue, and the Handler executes the tasks sequentially through the Looper.

This feature solves the problem of multiple time-consuming tasks that need to be executed sequentially. Using a Service alone, opening multiple threads to perform time-consuming operations can be difficult to manage.

Since most startup services don’t have to handle multiple requests at the same time (in fact, multithreading can be dangerous), implementing the service using the IntentService class is probably the best choice.

Characteristics of IntentService

  • Separate worker threads are created to execute passes to outside the main thread of the applicationonStartCommand()All intEnts of.
  • Create worker queues for passing intEnts one by one toonHandleIntent()Implementation, so you never have to worry about multithreading.
  • When all requests are processed,IntentServiceAutomatically stops without callingstopSelf()Methods to stopService
  • forServicetheonBind()Provides a default implementation that returns NULL
  • forServicetheonStartCommandProvides a default implementation that can be IntentTo the work queue andonHandleIntent()The implementation.
  • IntentServiceDoes not block the UI thread, while normalServeiceThe ANR is abnormal
  • You can start IntentServiceMultiple times, and each time-consuming operation will be in the form of a work queueIntentServiceonHandleIntentThe callback method is executed, and only one worker thread is executed at a time, the first one executed, the second one executed, and so on

reference

Actual application scenarios of IntentService

Understand the IntentService principle