IntentService原理详解

IntentService继承于Service,它的特点是什么呢?就跟龙叔小程序的特点一样——“用完即走”。

用过IntentService的人都知道,使用非常简单,根本不用自己去建立线程啊,维护线程通讯啊,甚至连最后资源的释放也不用我们自己处理,我们只需专心于业务实现即可,也就是编写onHandleIntent(Intent intent)方法中的代码,具体使用这里就不多说了,其实我们在之前的文章:定时任务之Alarm,已经使用了IntentService,今天我们主要来分析一下IntentService原理。

分析原理,当然是要看源码的,我们一步步来看:

   @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);
    }

启动一个IntentService,当然先进onCreate方法啦,从上面源码我们可以看到,第一步我们创建了一个HandlerThread(至于HandlerThread相关的说明,可查看:Handler相关看这篇就够了),然后启动线程,获取Looper,然后将HandlerThread中的Looper与ServiceHandler进行绑定,好啦,我们看看ServiceHandler的源码:

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);
        }
    }

ServiceHandler是一个内部类,继承于Handler,在handleMessage中我们看到了熟悉的方法:onHandleIntent(Intent intent),哦,原来我们使用IntentService中最重要的覆盖的onHandleIntent方法是在这里调用的,这里我们说明几点:
1)因为ServiceHandler是与HandlerThread中的Looper绑定的,所以onHandleIntent方法是在子线程中执行的。
2)我们注意到执行完onHandleIntent后,调用了stopSelf(msg.arg1),这就是为什么IntentService能做到“用完即走”的原因了,因为执行完任务后它会停掉自己的服务。
3)注意:这里自己停掉服务用的是stopSelf(int startId)方法,而不是stopSelf() ,为什么呢?那么我们就得先说一下这两个API之间的区别了,首先我们看stopSelf() 的源码:

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

发现原来stopSelf()调用的是stopSelf(int startId),只不过startId为-1而已。
说到这个startId,它是什么呢?其实它就是service的一个生命周期:onStartCommand(@Nullable Intent intent, int flags, int startId)中最后的一个参数。
我们都知道,当我们多次调用startService来启动同一个service时,只有第一次会执行onCreate,然后会多次调用onStartCommand,如果你去打印log的话,你会发现尽管onCreate只执行一次,但是每次的startId却是不同的,且都大于0。
而stopSelf(int startId)中的startId与onStartCommand中的startId是一一对应的关系,所以,当我们调用stopSelf(int startId)时,系统会检测是否还有其它的startId存在,有的话就不销毁当前service,没有的话则销毁。
而如果我们调用的是stopSelf(),那么无论是否还存在其它的startId,都会立即销毁当前service。
这就是stopSelf()和stopSelf(int startId)两个方法的区别!

我们回到之前的的问题,为什么IntentService中自停服务用的是stopSelf(int startId)而不是stopSelf()呢?
从上面比较两个方法的区别我们不能得出:这是为了提高IntentService的利用率,也就是说,如果在onHandleIntent方法执行完毕前,又调用了startService启动了同一个IntentService,那么我们就没必要销毁当前service了,直接继续用当前service对象执行任务即可,这样有利于减少了对象的销毁及创建。

另外:这里插一点,因为IntentService中用的是一个HandlerThread,也就是单一的线程,所以,用IntentService来执行任务只能是串行依次进行。

下面,我们继续分析源码,我们刚刚说到了onStartCommand,那我们也来看看它的源码:

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

onStartCommand第一步调用的是onStart,从onStart方法我们可以看出,其实就是用上面的handler发送了消息,从主线程切换到了子线程执行任务,这个没什么好说的。
然后我们看下面一行代码,返回值: mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
查看了一下mRedelivery默认值是false,当然提供了相应的方法可进行设置,下面我们先简单说明下onStartCommand几种返回值及区别:

1)START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
2)START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务
3)START_REDELIVER_INTENT:重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。
4)START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。

从上面的介绍我们知道了,onStartCommand默认返回了START_NOT_STICKY,所以,这里注意了,如果你使用IntentService执行的任务非常重要的话,建议通过设置setIntentRedelivery将mRedelivery设置为true,这样一来onStartCommand的返回就变成了START_REDELIVER_INTENT,有利于异常情况下服务的重启及恢复

最后,我来看看销毁的方法:

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

前面我们在Handler相关看这篇就够了这篇文章中介绍HandlerThread就说了,使用HandlerThread必须注意用完释放Looper,这不?IntentService在onDestroy生命周期中,帮我们进行了Looper的释放,所以我们得以“用完即走”,什么都不用处理,超级方便。

好啦,大概也就这些了,别看IntentService的源码也就一百多行,其实细究起来还是能学到不少东西的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容