理解 IntentService 原理

本人只是 Android小菜一个,写技术文档只是为了总结自己在最近学习到的知识,从来不敢为人师,如果里面有些不正确的地方请大家尽情指出,谢谢!

1.概述

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

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

本文将首先给大家演示下它的基本使用方式,再讲解下IntentService的内部原理。

2. IntentService 的使用

在知道如何使用前,先看看IntentService到底是什么东西,它的声明如下:

/**
 * IntentService is a base class for {@link Service}s that handle asynchronous
 * requests (expressed as {@link Intent}s) on demand.  Clients send requests
 * through {@link android.content.Context#startService(Intent)} calls; the
 * service is started as needed, handles each Intent in turn using a worker
 * thread, and stops itself when it runs out of work.
 *
 * <p>This "work queue processor" pattern is commonly used to offload tasks
 * from an application's main thread.  The IntentService class exists to
 * simplify this pattern and take care of the mechanics.  To use it, extend
 * IntentService and implement {@link #onHandleIntent(Intent)}.  IntentService
 * will receive the Intents, launch a worker thread, and stop the service as
 * appropriate.
 *
 * <p>All requests are handled on a single worker thread -- they may take as
 * long as necessary (and will not block the application's main loop), but
 * only one request will be processed at a time.
 */
public abstract class IntentService extends Service { ... }
复制代码

相信大家都能很容易看懂这段声明的意思,小菜在这里简单为大家总结下,这么一大段文字主要是说明了两个问题:

  1. IntentService是什么:用来进行处理异步请求的服务,其内部有一个工作线程,所有发送给服务的请求都会在这个工作线程中按序执行,在处理完所有请求后服务会自动停止。
  2. IntentService如何使用:拓展IntentService并在其拓展类或者叫子类中实现onHandleIntent(Intent)接口,在这个接口中进行实际的请求处理,这些请求通过Context.startService(Intent)来进行发送。

Android SDK真的可以作为所有SDK的典范,它会清楚地告诉你“是什么”和“怎么用”的问题,针对相对复杂的情况,还会直接在声明里给出范例。

既然我们已经知道要如何使用IntentService了,就让我们用一个小例子来演示一下:

public class TestIntentService extends IntentService {
    private static final String TAG = "TestIntentService";
    
    private static final String DEFAULT_NAME = "default_name";
    
    // 为了区分不同的请求和方便调用端使用,直接定义了不同的 ACTION.
    public static final String DOWNLOAD_ACTION = "com.test.intent.action.DOWNLOAD";
    public static final String UPLOAD_ACTION = "com.test.intent.action.UOLOAD";

    // 要在 AndroidManifest.xml 里声明 servcie,必须提供一个无参构造函数.
    public TestIntentService() {
        // IntentService 的构造函数需要提供一个工作线程的名字信息.
        super(DEFAULT_NAME);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, "onDestroy");
    }

    @Override
    public void onHandleIntent(Intent intent) {
        String action = intent.getAction();
        // 根据不同的请求类型进行不同的处理,这里只是休眠一段时间,并没有进行实际的处理。
        if (DOWNLOAD_ACTION.equals(action)) {
            try {
                Log.i(TAG, "onHandleIntent, start to download");
                Thread.sleep(30 * 1000);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        } else if (UPLOAD_ACTION.equals(action)) {
            try {
                Log.i(TAG, "onHandleIntent, start to upload");
                Thread.sleep(40 * 1000);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
        }
    }
}
复制代码

在这段代码里,请求处理函数onHandleIntent(Intent)会根据接收到的请求进行不同的处理,如果收到的是“下载”请求就休眠30秒模拟下载过程,如果收到的是“上传”请求就休眠40秒模拟上传过程。在写好了service逻辑后一定不要忘记在AndroidManifest.xml对其进行注册,否则是无法使用的,注册代码如下:

<service android:name=".TestIntentService" />
复制代码

注意: 这里只是简单地对其进行注册,并没有设置其他相关属性,例如intent-filter,因为这些和本文所讲内容并无直接关系。


请求的接收和处理代码都已完成,接下来就是发送请求的代码逻辑,如下:

// 发送“下载”请求
Intent downloadIntent = new Intent(this, TestIntentService.class);
downloadIntent.setAction(TestIntentService.DOWNLOAD_ACTION);
startService(downloadIntent);

// 发送“上传”请求
Intent upIntent = new Intent(this, TestIntentService.class);
upIntent.setAction(TestIntentService.UPLOAD_ACTION);
startService(upIntent);
复制代码

现在看当发送这两个“下载”和“上传”请求后,IntentService是如何响应的:

02-27 12:58:23.100 24190 24190 I TestIntentService: onCreate
02-27 12:58:23.102 24190 24240 I TestIntentService: onHandleIntent, start to download
02-27 12:58:53.107 24190 24240 I TestIntentService: onHandleIntent, start to upload
02-27 12:59:33.115 24190 24190 I TestIntentService: onDestroy
复制代码

可以看到:在发送第一个“下载”请求的时候,service首先被创建,然后开始处理这个“下载请求”,仅接着第二个“上传”请求也被接收并在处理完第一个请求后开始处理,在处理完所有请求后service被自动销毁。

3. IntentService 的原理

前面已经讲了如何通过IntentService实现在工作线程中处理较耗时任务,那么IntentService内部又是如何实现的呢?本节我们通过分析它的源码来一探究竟。

3.1 创建工作线程

既然IntentService的功能是在工作线程中处理任务,首先来看看这个工作线程是如何创建出来的。

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来完成一些初始化操作:

  1. 首先创建了一个HandlerThread对象,这就是前面一直提到的“工作线程”。大家对HandlerThread都很了解,那这个HandlerThread是什么呢?简单来说,它就是内部有一个消息循环队列的线程,我们知道默认的线程内部是没有消息循环队列的,这就导致我们无法直接在其内部使用HandlerAndroid为了方便使用,直接提供了一个含有消息循环队列的HandlerThread
  2. 利用已创建的HandlerThread内部的消息循环创建一个 ServiceHandler对象,这样它的消息处理函数handleMessage就会在对应的线程中执行了。

3.2 接收和处理请求

既然工作线程已经创建完成,这时就要考虑如何接收客户端发送过来的请求了,已经了解到客户端是通过startService来发送请求的,结合service的生命周期,紧接着会执行onStartCommand回调:

/**
 * 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 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就是我们在使用过程中必须实现的处理逻辑。

3.3 销毁工作线程

前面提到:当所有请求都被处理完成后,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) {
    }
}

/**
 * Stop the service if the most recent time it was started was 
 * <var>startId</var>.  This is the same as calling {@link 
 * android.content.Context#stopService} for this particular service but allows you to 
 * safely avoid stopping if there is a start request from a client that you 
 * haven't yet seen in {@link #onStart}. 
 */
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()的时候发现最后一次启动是第三个请求发生的,并不会停止服务;处理完第二个请求后是类似的,只有在处理完第三个请求后,去尝试停止服务,这时发现最近一次启动就是它发起的,可以去停止服务了。停止服务时,其onDestroy会得到调用:

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

在这里会停止工作线程的消息循环,等待线程退出。

4. 总结

IntentService能够接受用户发送的请求在工作线程中顺序处理,处理完成后自动退出,但是由于从 Android O开始对后台服务增加了更严格的控制,导致当前进程在后台时其含有的后台服务也无法长期存活,IntentService的使用也有了一定的限制,推荐使用更好的JobIntentService,感兴趣的同学可以自己去研究。

猜你喜欢

转载自juejin.im/post/5c75f3e851882540a702ea8f
今日推荐