Android 并发二三事之 IntentService Handler 机制再次来袭

Android 并发第六篇。

本篇介绍 IntentService 。

IntentService 本身是一个 Service 。
当我们执行耗时操作时,例如下载一个文件,一般我们都会把这个操作放在 Service 中。
当然我们不能直接在 Service 中做这些耗时事情,因为四个组件都运行在主线程中,不能直接做耗时的操作。
这时我们就需要在 Service 中开启线程去做了,当耗时操作结束后,关闭这个 Service 。

那么这时 IntentService 就应运而生了,自定义类继承 IntentService ,重写onHandleIntent() 方法。onHandleIntent() 方法
便执行在子线程中, 这样调用者就不再需要在 Service 中写线程了,免去了很多麻烦。
IntentService 还有一个优点便是,当执行过任务后,会自动退出。
其实 IntentService 的原理就是利用了 HandlerThread。在内部构造了一个可以执行在子线程中的消息队列。

在上一篇我们说,HandlerThread 需要与Handler 一起使用,在 IntentService 中 也存在 Handler: ServiceHandler 。
这也验证我们的说法。

一 IntentService 如何使用:

1 、自定义 LoadService 继承 IntentService,重写onHandleIntent() 方法:

public class LoadService extends IntentService {

    private static String TAG = "Demo";

    public LoadService() {
        super("LoadService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        loadFile();
        //打印当前线程Id
        Log.d(TAG, "onHandleIntent "+Thread.currentThread().getId());
    }

    /**
     * 模拟下载文件
     */
    private void loadFile() {
        try {
            //模拟耗时
            Thread.sleep(3000);
        }catch (Exception e){

        }
    }
}

2、调用:

调用方式与启动 Service 是一样的:

    private void startLoadService() {

        Intent intent = new Intent(this, LoadService.class);
        startService(intent);

    }

3 结果:

D/Demo: onHandleIntent 28411

可以看到 onHandleIntent() 方法是执行在子线程中的。

4 注意:

但是值得注意的是: IntentService 是完成所有的任务后才会退出。
也就是说当上一个任务还在执行,这时再次调用 startLoadService() 方法,
相当于再次提交了一个任务,那么 IntentService 会在所有的任务都执行完后再退出,
这些任务也都会执行在同一个线程中,但如果上一个任务已经执行完了,再次调用 startLoadService() 方法
会发现本次任务和上次任务并不是执行在同一个线程中。

这次我们在Activity 中的onCreate 中便调用 startLoadService() 方法,点击按钮也会调用 startLoadService() 方法。
当页面初始化后,快速点击按钮,这是第一次在 onCreate 中的调用的 startLoadService() 方法还没有执行完(因为3秒的耗时操作)。
然后再相隔一会等到已经前两次任务都执行完后,再次点击按钮,一共执行三次 startLoadService() 方法,查看结果:

结果:

11-18 17:04:15.454 4100-2468/com.loader.demo D/Demo: onHandleIntent 28416
11-18 17:04:18.456 4100-2468/com.loader.demo D/Demo: onHandleIntent 28416
11-18 17:04:24.302 4100-2573/com.loader.demo D/Demo: onHandleIntent 28417

我们能够清楚的发现,前两次任务是执行在同一个线程中的,第二次任务执行后,IntentService 才退出。
这时第三次任务开始执行,便已经和前两次不在同一个线程中了。

接下来看源码:

二 原理

1、IntentService 源码:

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);
            stopSelf(msg.arg1);
        }
    }

    public IntentService(String name) {
        super();
        mName = name;
    }

    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 + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @WorkerThread
    protected abstract void onHandleIntent(Intent intent);
}

2、IntentService 的代码还是很少的,但是思想很巧妙。

在 onCreate() 时会初始化 HandlerThread 以及 Handler : ServiceHandler。
在 onStart() 中将 Intent和startId 封装到 Message 中,并且发送出去。
由于 ServiceHandler 初始化时,利用的是 HandlerThread 提供的 Looper ,所以处理消息的方法 handlerMessage() 会执行在工作线程中,
也就是子线程中。 (其中原理可以看前一片文章)
这时调用者可以重写 onHandleIntent() 方法,做自己想做的事情。当 onHandleIntent() 方法执行后调用 stopSelf() 退出。
并在onDestroy() 中退出消息循环。

3、关于 startId:

我们之前说,IntentService 会把所有的任务执行完才会退出,并且这些任务都是执行依次执行在同一个线程中,那么这是为什么呢?
我们有看到 IntentService 的源码中是在调用 onHandleIntent() 方法后紧接着就调用了 stopSelf() 方法,为什么没有关闭呢?
因为stopSelf() 方法需要传入 startId, 而这个startId 是服务最后启动时的 startId 时才会关闭。
由于接连提交几个任务,每次的 startId 是不一样。我们在提交Message 到消息队列中,会把当前 startId 也封装过去。那么上一次任务完成后
stopSelf() 中传入的 startid 还是上一个任务的 startId,并不是最后的 startId。只有当最后一个任务执行完毕后,其传入stopSelf() 方法的
startId 才是最后启动 service 的最后 startId,那么 service 才会被关闭。

另外当 startId 为-1 时也会关闭 service。具体原因可见源码:

    public final void stopSelf() {
        stopSelf(-1);
    }

    /**
     * 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) {
        }
    }

调用无参 stopSelf() 相当于传入了 startId 为 -1。

三、OK IntentService 其实并没有很复杂,只要搞懂 HandlerThread 是怎么回事,回头再看就和简单了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值