谈谈Android源码——IntentService

IntentService 介绍

IntentService 继承自 Service,所以它是一个 Service,并且它是一个抽象类,所以使用它必须要使用它的子类。

按照国际惯例,先来看看 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.
 *
 * <p class="note"><b>Note:</b> IntentService is subject to all the
 * <a href="/preview/features/background.html">background execution limits</a>
 * imposed with Android 8.0 (API level 26). In most cases, you are better off
 * using {@link android.support.v4.app.JobIntentService}, which uses jobs
 * instead of services when running on Android 8.0 or higher.
 * </p>
 */

注释很长,总结为以下几点:

  • IntentService 用来处理异步请求,客户端通过 startService 发送请求,请求参数通过 Intent 携带,服务端启动后依次在工作线程中执行请求,执行完成后 stopself 结束自己
  • 使用 IntentService 需要继承 IntentService 并重写 onHandleIntent 方法,IntentService 接收到 Intent 后会加载一个工作线程,在工作线程中执行完任务后停止服务
  • 所有的请求都只在一个工作线程上执行,并且每次只处理一个请求,所有请求执行完成后才会结束服务
  • 在 Android8.0 上,IntentService 受后台限制,大多数情况下最好使用 JobIntentService,而不是 IntentService

通过以上总结,我们就知道了它与 Service 的异同点了。相同点是它们都是服务,不同的是 Service 运行在主线程中,而 IntentService 运行在子线程中;并且 Service 需要手动关闭,而 IntentService 是自动关闭。从本质上说,IntentService 就是一个开启了工作线程的 Service。

IntentService 使用

常见一个类继承 IntentService 并实现 onHandleIntent 方法,还要生成构造函数,这个无参构造函数调用父类的一个参数的构造函数,这是必要的。一般地,我们只需要重写 onHandleIntent 这一个方法就可以了。

public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MIS");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

    }
}

既然 MyIntentService 也是一个服务,那么肯定是需要在 AndroidManifest.xml 中注册的,然后在 Activity 中启动 MyIntentService。从上面的注释总结可知,IntentService 可以处理多个请求,并且所有请求都在同一个工作线程中处理,那么就多发送几个请求并打印出所在的线程验证一下。

public class MyIntentService extends IntentService {
    public MyIntentService() {
        super("MIS");
        Log.e("MIS", "construct - " + this);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.e("TAG", "params = " + intent.getIntExtra("params", -1) + " , mis =  " + this + " , thread = " + Thread.currentThread().getName());
    }
}

public class MainActivity extends AppcompatActivity {
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Intent intent = new Intent(this, MyIntentService.class);
    intent.putExtra("params", 1);
    startService(intent);

    Intent intent2 = new Intent(this, MyIntentService.class);
    intent2.putExtra("params", 2);
    startService(intent2);

    Intent intent3 = new Intent(this, MyIntentService.class);
    intent3.putExtra("params", 3);
    startService(intent3);
  }
}

上面自定义了一个 MyIntentService 类,并传入线程名为 MIS,同时在构造器中打印了当前对象。然后在 onHandleIntent 方法中打印出了 intent 中的参数、当前对象以及当前线程名。

E/IS: construct - com.intentservice.MyIntentService@e01c344
E/TAG: params = 1 , mis =  com.intentservice.MyIntentService@e01c344 , thread = IntentService[MIS]
E/TAG: params = 2 , mis =  com.intentservice.MyIntentService@e01c344 , thread = IntentService[MIS]
E/TAG: params = 3 , mis =  com.intentservice.MyIntentService@e01c344 , thread = IntentService[MIS]

从上面打印日志中可以看出,即使 startService 启动了三次,但是只构造了一个 MyIntentService 对象,构造器中只打印了一次。客户端请求了三次,MyIntentService 也接收到了三个请求,而且三次打印出的 mis 对象都是一直的,表明只创建了一个对象,而且三次 thread 的值也是一致的,这也验证了上面的总结,多次请求的处理线程都是同一个工作线程。

IntentService 源码解析

说完了基本使用,该来说说它内部是怎么实现的了。

它继承 Service,所以和 Service 的生命周期方法一直,启动后先执行 onCreate 方法。

@Override
public void onCreate() {
  super.onCreate();
  HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
  thread.start();

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

该方法中创建了一个 HandlerThread 对象(关于 HandlerThread 的分析看另一篇文章HandlerThread),将 MyIntentService 构造函数传进来的名字用来构造工作线程的名字,此处的名字是 IntentService[MIS],然后启动线程。接着从 HandlerThread 对象中获取到 Looper 对象 mServiceLooper,并创建一个 ServiceHandler 对象。

然后就执行到了 onStartCommand 方法,来看看 onStartCommand 做了什么。

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

这个方法实现比较简单,就调用了自身的 onStart 方法,然后返回了一个 int 值。返回的值受 mRedelivery 变量影响,这个变量由 setIntentRedelivery 方法设置,默认为 false。如果为 true,则返回 START_REDELIVERY,表示粘性,当服务被杀死会再次重新启动;否则返回 START_NOT_STICKY,表示不重新启动。

接着看看 onStart 方法中做了什么

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

在 onCreate 方法中创建了 mServiceHandler 对象,然后从 mServiceHandler 对象中获取到一个 msg 对象,并封装了 startId 和 intent 到 Message 对象中,紧接着通过 mServiceHandler 将 msg 发送出去。

mServiceHandler 对象发送出去的消息会被 ServiceHandler 接收到,ServiceHandler 是 IntentService 的内部类,继承自 Handler,并实现了 handleMessage 方法。handleMessage 方法中调用了 抽象方法 onHandleIntent 方法以及 stopSelf 方法停止服务。

那么为什么 onHandleIntent 方法就执行在子线程中呢?

在 onCreate 方法中创建了 HandlerThread 对象,它是一个线程,内部封装了 Looper,这个 Looper 是属于 HandlerThread 线程,而在创建 ServiceHandler 时传入了 HandlerThread 线程的 Looper,所以 ServiceHandler 也是属于 HandlerThread 的 Handler,那么 Handler 的 handleMessage 方法的线程自然也是 HandlerThread 线程,所以 onHandleIntent 中可以做耗时操作。

又因为在执行完 onHandleIntent 方法之后主动调用了 stopSelf 方法关闭服务,所以在使用的时候并不需要我们手动来 stopService。

接下来又有一个问题了,我们都知道启动 Service 有两种方式,一种是 startService,另一种是 bindService,那么 IntentService 支持 bindService 这种方式吗?我们从源码中找找答案吧。

众所周知,bindService 的生命周期和 startService 的生命周期不同,startService 的生命周期是 onCreate -> onStartCommand -> onDestroy,而 bindService 的生命周期是 onCreate -> onBind -> onUnbind -> onDestroy。

这两种方式都会执行 onCreate 方法,所以 IntentService 中的 onCreate 中的代码都能得到执行,也就是说两种方式都会创建并启动 HandlerThread 对象,而且也能创建 HandlerThread 线程的 Handler。

然而 onHandleIntent 方法中处理的请求是需要使用 Handler 发送消息的,但是发送消息是在 onStart 方法中发送的,但是 bindService 这种方式不会执行 onStart 方法,所以 bindService 这种方式是不行的。

到这里,IntentService 的源码就分析完了,源码很简单,下面就来总结一下。

总结

IntentService 是一个自带线程的 Service,并且不需要手动的去关闭。内部实现是通过 HandlerThread 来创建一个子线程的 Looper,并根据这个 Looper 创建一个子线程的 Handler,将代码切换到子线程中执行。并且 IntentService 只能用于 startService 这种方式启动。

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

推荐阅读更多精彩内容