IntentService

1、IntentService是什么?

回顾Android实现线程异步的方法,有AsyncTask、HandlerThread、线程池、IntentService。其中IntentService其实是继承与Service的类,内部是由HandlerThread实现(HandlerThread本系列由专门的小节做介绍,不清楚的童鞋可以去看看)。常用于处理异步请求,处理完子线程的耗时操作后,会自动执行stopService()。

2、IntentService的使用

我们用IntentService来实现在后台读取文件已经下载文件,直接上demo:

// XuruiIntentService.java
public class XuruiIntentService extends IntentService {

    private static final String TAG = "XuruiIntentService";

    public XuruiIntentService() {
        //IntentService 工作线程的名字
        super("XuruiIntentService");
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        if (intent != null) {
            String taskName = intent.getStringExtra("taskName");
            switch (taskName) {
                case "readFile": //1
                    //任务一:执行读取大文件内容的耗时操作
                    try {
                        readFile();
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    break;
                case "DownLoad": //2
                    //任务二:执行从网络下载文件的耗时操作
                    break;
                default:
                    break;
            }
        }
    }
}
// Activity中使用XuruiIntentService类
    public void onClick(View v) {
        Intent intent = new Intent(this, XuruiIntentService.class);
        switch (v.getId()) {
            case R.id.readFileBtn: //3
                intent.putExtra("taskName", "readFile");
                startService(intent);
                break;
            case R.id.downLoadBtn: //4
                intent.putExtra("taskName", "DownLoad");
                startService(intent);
                break;
            default:
                break;
        }
    }

上面的demo还是比较好理解的,在注释3按下读取文件的按钮后,就会通过startService()启动服务,跑到注释1去执行耗时的文件读取操作。没错,IntentService的启动和Service的启动是一样的。

还有一点需要注意,如果按下readFileBtn后马上按downLoadBtn,那么代码执行的顺序是: onCreate、onStartCommand、onHandleIntent: readFile、onStartCommand、onHandleIntent: DownLoad、onDestroy

但如果按下readFileBtn后稍等一会再按downLoadBtn,那么代码执行的顺序是: onCreate、onStartCommand、onHandleIntent: task1、readFile、 onCreate、onStartCommand、onHandleIntent: task2、DownLoad

以上两种情况证明了,IntentService在处理完操作后,会自动退出,因此按下readFileBtn后稍等一会再按downLoadBtn,会重新拉起一个新的服务,从onCreate()开始执行。

:不能用bindService()来绑定IntentService,因为onBind()默认返回null。

3、源码分析

因为内部使用HandlerThread实现,源码也才100+行,因此直接copy完整的源码,不做任何删减(下面源码是android9.0.0的源码)。

public abstract class IntentService extends Service {
     private volatile Looper mServiceLooper;
     private volatile ServiceHandler mServiceHandler;
     private String mName;
     private boolean mRedelivery;
   
     private final class ServiceHandler extends Handler {
         public ServiceHandler(Looper looper) {
             super(looper);
         }
   
         @Override
         public void handleMessage(Message msg) {
             onHandleIntent((Intent)msg.obj); //1
             stopSelf(msg.arg1); //2
         }
     }
   
     /**
      * Creates an IntentService.  Invoked by your subclass's constructor.
      *
      * @param name Used to name the worker thread, important only for debugging.
      */
     public IntentService(String name) {
         super();
         mName = name;
     }
   
     /**
      * Sets intent redelivery preferences.  Usually called from the constructor
      * with your preferred semantics.
      *
      * <p>If enabled is true,
      * {@link #onStartCommand(Intent, int, int)} will return
      * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
      * {@link #onHandleIntent(Intent)} returns, the process will be restarted
      * and the intent redelivered.  If multiple Intents have been sent, only
        * the most recent one is guaranteed to be redelivered.
        *
        * <p>If enabled is false (the default),
        * {@link #onStartCommand(Intent, int, int)} will return
        * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
        * dies along with it.
        */
       public void setIntentRedelivery(boolean enabled) {
           mRedelivery = enabled;
       }
   
      @Override
       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 + "]"); //3
           thread.start(); //4
   
           mServiceLooper = thread.getLooper(); //5.1
           mServiceHandler = new ServiceHandler(mServiceLooper); //5.2
       }
   
       @Override
       public void onStart(@Nullable Intent intent, int startId) {
           Message msg = mServiceHandler.obtainMessage();
           msg.arg1 = startId;
           msg.obj = intent;
           mServiceHandler.sendMessage(msg); //6
       }
   
       /**
        * You should not override this method for your IntentService. Instead,
        * override {@link #onHandleIntent}, which the system calls when the IntentService
        * receives a start request.
        * @see android.app.Service#onStartCommand
        */
       @Override
       public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
           onStart(intent, startId);
           return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
       }
   
       @Override
       public void onDestroy() {
           mServiceLooper.quit(); //7
       }
   
       /**
        * Unless you provide binding for your service, you don't need to implement this
        * method, because the default implementation returns null.
        * @see android.app.Service#onBind
        */
       @Override
       @Nullable
       public IBinder onBind(Intent intent) {
           return null;
       }
   
       /**
        * This method is invoked on the worker thread with a request to process.
        * Only one Intent is processed at a time, but the processing happens on a
        * worker thread that runs independently from other application logic.
        * So, if this code takes a long time, it will hold up other requests to
        * the same IntentService, but it will not hold up anything else.
        * When all requests have been handled, the IntentService stops itself,
        * so you should not call {@link #stopSelf}.
        *
        * @param intent The value passed to {@link
        *               android.content.Context#startService(Intent)}.
        *               This may be null if the service is being restarted after
        *               its process has gone away; see
        *               {@link android.app.Service#onStartCommand}
        *               for details.
        */
       @WorkerThread
       protected abstract void onHandleIntent(@Nullable Intent intent);
   }

}

如果已经学习过本系列Service和HandlerThread两小节的同学,看上面的代码就非常容易了。我们以上一小节,按下readFileBtn来读取文件内容为例,看看IntentService源码都做了什么?

    // 按下readFileBtn后执行:
    intent.putExtra("taskName", "readFile");
    startService(intent);

启动服务后,流程如下:

  1. 首先执行onCreate(),里面的注释3-5正是HandlerThread三大经典的使用步骤,对应[注释3-5];
  2. 接着执行onStartCommand() -> onStart(),通过子线程的mServiceHandler发送消息,对应[注释6];
  3. 发送信息就有接收信息,执行子线程handleMessage()中调用我们重写的 onHandlerIntent 执行异步任务,对应[注释1];
  4. 等待所有任务执行完后,调用[注释2] stopSelf(msg.arg1)来销毁服务,可以发现该方法带了参数,意为尝试停止服务之前会判断最近启动服务的次数是否是startId,如果相等才停止服务。

因为onCreate()只会执行一次,但onStartCommand()可以执行多次,所以多次调用startService(Intent) 时,上面2-3步骤可能发送和接收多个消息,通过复写onHandlerIntent(),再根据发送不同的Intent,进行不同的线程操作。且发送多个消息时,对应的多个任务需要按顺序逐个执行。

4、Service 与 IntentService 的区别

  • Service不是单独的线程,如果在onStartCommand()中执行耗时操作可能发生ANR,IntentService会创建一个工作线程来处理多线程任务;
  • Service长期存在后台,结束需要主动调用stopSelft()来结束服务,而IntentService会在任务完成后默认调用stopSelft()直接退出;
  • 两者都可以是使用startService()启动,但IntentService不能onBind();

5、使用场景

IntentService适合多个任务需要按顺序,适用更高优先级的的后台任务,不容易被系统杀死的使用场景,比如离线下载场景,后台下载场景。