看完这一篇,再也不怕面试官问到IntentService的原理
IntentService是什么
在内部封装了 Handler、消息队列的一个Service子类,适合在后台执行一系列串行依次执行的耗时异步任务,方便了我们的日常coding(普通的Service则是需要另外创建子线程和控制任务执行顺序)

IntentService的缺点
IntentService,一次只可处理一个任务请求,不可并行,接受到的所有任务请求都会在同一个工作线程执行
IntentService is subject to all the background execution limits imposed with Android 8.0 (API level 26).
翻译:IntentService受到Android 8.0(API级别26)施加的所有[后台执行限制]的约束。
IntentService的未来
!This class was deprecated in API level 30.
IntentService is subject to all the background execution limits imposed with Android 8.0 (API level 26).
Consider using WorkManager or JobIntentService, which uses jobs instead of services when running on Android 8.0 or higher.
官方在最新说明中,提到 IntentService 类将会在API Level 30,也即Android 11中,被废弃掉。作为一个从API Level 3就加入的异步工具,如今官方建议使用JetPack组件中的WorkManager或者JobIntentService类代替它。
IntentService怎么用
IntentService的使用,一般都需要子类继承IntentService,然后重写onHandleIntent()内部逻辑
因为IntentService本质上还是一个Service,所以需要先在注册清单中注册上Service以及需要外部手动开启。
<service
android:name = ".MyIntentService">
AndroidStudio可以通过File-new-Service(IntentService),创建IntentService,IDE会帮我们自动在注册清单注册这个IntentService,为我们的IntentService子类提供了模板实现方法,我们可以在上面省事地修改。
下面为了方便演示,我使用官方提供的IntentService代码模板进行修改和操作:(代码有点长有点渣,请见谅)
//MyIntentService.java
public class MyIntentService extends IntentService {
//用以区分 Intent 的Action名
private static final String ACTION_FOO = "action.FOO";
private static final String ACTION_BAZ = "action.BAZ";
//给Intent传递参数取参的常量值
private static final String EXTRA_PARAM1 = "extra.PARAM1";
private static final String EXTRA_PARAM2 = "extra.PARAM2";
/**
* IntentService构造方法:传入的参数name是作为内部的工作线程名的组成部分
*/
public MyIntentService() {
super("MyIntentService");
Log.i("MyIntentService" , "===created===");
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
if(intent != null){
Log.i("MyIntentService","Action "+intent.getAction()+" startId: "+startId);
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("MyIntentService","===onDestroyed===");
}
/**
* 提供给外界调用,启动任务Foo的方法
* 如果IntentService已经在运行,任务将会进入任务(消息)队列等待排队
* @param context 调用者Context
* @param param1 任务参数1
* @param param2 任务参数2
*/
public static void startActionFoo(Context context, String param1, String param2) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_FOO);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
context.startService(intent);
}
/**
* 提供给外界调用,启动任务Baz的方法
* 如果IntentService已经在运行,任务将会进入任务(消息)队列等待排队
* @param context 调用者Context
* @param param1 任务参数1
* @param param2 任务参数2
*/
public static void startActionBaz(Context context, String param1, String param2) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_BAZ);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
context.startService(intent);
}
/**
* IntentService被启动后,会回调此方法
* onHandleIntent内部根据收到的不同Intent执行不同的操作
* @param intent 任务意图
*/
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_FOO.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionFoo(param1, param2);
} else if (ACTION_BAZ.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionBaz(param1, param2);
}
Log.i("MyIntentService","Action "+action+" completed");
}
}
/**
* 会在后台的工作线程上执行(耗时)任务Foo
* @param param1 任务参数1
* @param param2 任务参数2
*/
private void handleActionFoo(String param1, String param2) {
Log.i("MyIntentService","handleActionFoo : "+ Thread.currentThread().getName() +
" " + param1 + " "+ param2 );
}
/**
* 会在后台的工作线程上执行(耗时)任务Baz
* @param param1 任务参数1
* @param param2 任务参数2
*/
private void handleActionBaz(String param1, String param2) {
Log.i("MyIntentService","handleActionBaz : "+ Thread.currentThread().getName() +
" " + param1 + " "+ param2 );
}
}
外部如何启用IntentService
可以通过调用context.startService(new Intent(context, MyIntentService.class))
或者调用MyIntentService的静态方法startActionFoo/startActionBaz启动,如果你仔细看其实这两种方式本质上都是相同的代码逻辑。
你可能会问:我平时最常用的bindService()哪去了?这个问题,请接着往下看。
DEMO运行结果
在Activity里,连续调用开启了Intentservice,特别地前两次是相同的Intent和参数
MyIntentService.startActionFoo(this,"DMingO's" ,"blog");
MyIntentService.startActionFoo(this,"DMingO's" ,"blog");
MyIntentService.startActionBaz(this,"DMingO's" ,"Github");

从运行结果可以看出:
- IntentService会按照先后顺序给Action编号递增的startId,从1开始。
- 每启动一次IntentService,
onStartCommand(),onHandleIntent()就会被回调一次,但IntentService构造方法只会被调用一次 - IntentService主要的操作逻辑都在
onHandleIntent()中 - 在主线程启动的IntentService,而onHandleIntent的操作是在指定了线程名的工作线程上执行的
- IntentService在所有的任务完成后会自动执行销毁回调onDestroyed,而不用我们手动停止
IntentService的使用场景,很适合需要在后台执行一系列串行执行的耗时任务,不会影响到UI线程,且任务全部完成后会自动销毁。
下面开始探究IntentService这种可以依次执行任务,任务完毕即销毁的背后原理,
IntentService原理探究
IntentService的源码行数其实不多,结合源码分析,先从构造函数入手:
private String mName;
public IntentService(String name) {
//传递给父类--Service类
super();
mName = name;
}
IntentService本质上还是一个Service的子类,通过super()调用父类构造器,给工作线程名变量赋值后,接着会开始Service的生命周期,IntentService重写了生命周期的第一步 onCreate()
接着看看IntentService的onCreate中有什么名堂:
@Override
public void onCreate() {
super.onCreate();
//创建了一个本地 HandlerThread 的变量,结合mName进行命名,目的是为了获取它的Looper和消息队列
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//获取到HandlerThread的Looper,利用这个Looper创建
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
再来好好看下这个IntentService内部的Handler子类——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);
}
}
//全部任务都调用stopSelf过后才会回调onDestroy(),退出工作线程的Looper循环
public void onDestroy() {
mServiceLooper.quit();
}
可以看到:当ServiceHandler收到了来自HandlerThread的Looper传递过来的Message时,首先会将消息的obj属性强制转换为Inetnt类型,调用抽象方法onHandleIntent。
由于ServiceHandler的Looper是来自HandlerThread这个工作线程的,Looper与Handler的消息处理是直接挂钩的,所以handleMessage(msg) ——> onHandleIntent(intent)均是在工作线程上完成的。
msg.arg1 的值其实是这个任务Message的startId,在onStartCommand方法中可以发现它对开启IntentService的任务都用startId标记了顺序,在构建Message对象时就被赋值给了它的arg1属性了。
onHandleIntent()执行完毕,stopSelf()会根据指定 startId 来停止当前的任务。而 Service 如果被启动多次,自然会有多个 startId ,只有当所有任务都被停止之后,才会调用 onDestory() 进行销毁。这就是为什么start了IntentService多次后,任务全部执行完成之后,IntentService才会自动销毁的原因。
接下来继续分析,重点来了,这个Message对象msg究竟是什么地方被构建的。
从onStartCommand方法入手:
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
onStartCommand首先将外部想要执行的Intent和startId传递给了onStart(intent, startId)调用,先跟进去看看 onStart 方法有什么名堂 :
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
看来onStart方法就是实际上IntentService机制的关键之处了,它根据每个 Intent 创建Message对象,完成了Message对象的属性赋值,还利用了mServiceHandler发送消息。同时也解释了为什么我们看到每启动一次IntentService,onHandleIntent就会被回调执行一次。也由此可见,Handler在Android中真的是太太太重要了。
肯定有好奇的同学会问,IntentService也是Service,能不能用 bind的方式启动它呢?emmm可以是可以,但是最好不要这么做。
IntentService在设计时,应该也想到bind方式启动与IntentService任务完成自动销毁的特点不太符合。这点可以从源码可见一斑,用bind方式启动,onBind会直接返回 null :
/**
* 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;
}
源码分析总结
我们通过逐渐深入抽丝剥茧的方式分析了IntentService的源码,最后可以简单总结这个内部封装了Handler和消息队列的IntentService的原理:
IntentService的机制核心是Handler和消息队列,每次我们调用 startService(new Intent),其实就是给 IntentService 添加一个任务。在IntentService的内部,第一次启动时首先会 构建IntentService对象,开始初始化工作:通过HandlerThread获取到一个工作线程的Looper,用来构建它的核心Handler。然后每当有一个任务被添加进来,内部就会创建一个附带着Intent的Message对象,使用IntentService 内部的Handler发送Message。Looper从消息队列中循环地取出Message传递给这个Handler,Handler就会在工作线程上依次处理这些消息任务的Intent。
看完这一篇,再也不怕面试官问到IntentService的原理的更多相关文章
- 图解Java线程的生命周期,看完再也不怕面试官问了
文章首发自个人微信公众号: 小哈学Java https://www.exception.site/java-concurrency/java-concurrency-thread-life-cycle ...
- 深度剖析Java的volatile实现原理,再也不怕面试官问了
上篇文章我们讲了synchronized的用法和实现原理,我们总爱说synchronized是重量级锁,volatile是轻量级锁.为什么volatile是轻量级锁,体现在哪些方面?以及volatil ...
- Springboot启动扩展点超详细总结,再也不怕面试官问了
1.背景 Spring的核心思想就是容器,当容器refresh的时候,外部看上去风平浪静,其实内部则是一片惊涛骇浪,汪洋一片.Springboot更是封装了Spring,遵循约定大于配置,加上自动装配 ...
- 手写webpack核心原理,再也不怕面试官问我webpack原理
手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...
- 如何完美回答面试官问的Mybatis初始化原理!!!
前言 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程. MyBatis的初始化做了什么 MyBatis基于XML配置文件 ...
- 面试官问:JS的this指向
前言 面试官出很多考题,基本都会变着方式来考察this指向,看候选人对JS基础知识是否扎实.读者可以先拉到底部看总结,再谷歌(或各技术平台)搜索几篇类似文章,看笔者写的文章和别人有什么不同(欢迎在评论 ...
- 美团面试官问我一个字符的String.length()是多少,我说是1,面试官说你回去好好学一下吧
本文首发于微信公众号:程序员乔戈里 public class testT { public static void main(String [] args){ String A = "hi你 ...
- 面试官问线程安全的List,看完再也不怕了!
最近在Java技术栈知识星球里面有球友问到了线程安全的 List: 扫码查看答案或加入知识星球 栈长在之前的文章<出场率比较高的一道多线程安全面试题>里面讲过 ArrayList 的不安全 ...
- 深度剖析HashMap的数据存储实现原理(看完必懂篇)
深度剖析HashMap的数据存储实现原理(看完必懂篇) 具体的原理分析可以参考一下两篇文章,有透彻的分析! 参考资料: 1. https://www.jianshu.com/p/17177c12f84 ...
随机推荐
- ibit-mybatis 2.x 介绍
原文链接:ibit-mybatis 2.x 介绍 概述 ibit-mybatis 是一个 Mybatis 的增强工具,在 Mybatis 的基础上增加了新的特性与功能,志在简化开发流程.提高开发效率. ...
- 2020年,web前端还好找工作吗?
好不好找是个相对概念,如果你要跟几年前相比,那么一定是「相对不好找」.原因所学的知识过时 用 Vue 模仿一个饿了么就能找工作的时代一去不复返. 但是为什么现在一堆大厂喊着招聘难呢? 那是因为候选人技 ...
- 第 11 篇:基于 drf-haystack 的文章搜索接口
作者:HelloGitHub-追梦人物 在 django 博客教程中,我们使用了 django-haystack 和 Elasticsearch 进行文章内容的搜索.django-haystack 默 ...
- 每日一题 - 剑指 Offer 42. 连续子数组的最大和
题目信息 时间: 2019-06-30 题目链接:Leetcode tag: 动态规划 难易程度:简单 题目描述: 输入一个整型数组,数组里有正数也有负数.数组中的一个或连续多个整数组成一个子数组.求 ...
- HTML文档解析和DOM树的构建
浏览器解析HTML文档生成DOM树的过程,以下是一段HTML代码,以此为例来分析解析HTML文档的原理 <!DOCTYPE html> <html lang="en&quo ...
- 总结几个移动端H5软键盘的大坑
1.部分机型软键盘弹起挡住原来的视图 解决方法:可以通过监听移动端软键盘弹起 Element.scrollIntoView() 方法让当前的元素滚动到浏览器窗口的可视区域内.参数如下. true,表示 ...
- 「区间DP」「洛谷P1043」数字游戏
「洛谷P1043」数字游戏 日后再写 代码 /*#!/bin/sh dir=$GEDIT_CURRENT_DOCUMENT_DIR name=$GEDIT_CURRENT_DOCUMENT_NAME ...
- 洛谷P3237 [HNOI2014]米特运输(树形dp)
解题报告 题干 米特是D星球上一种非常神秘的物质,蕴含着巨大的能量.在以米特为主要能源的D星上,这种米特能源的运输和储存一直是一个大问题. D星上有N个城市,我们将其顺序编号为1到N,1号城市为首都. ...
- 利用SignalR实施响应股票数据波动
1.新建ASP.NET Web应用程序, 选择Empty模板. 2.创建Stock.cs类 public class Stock { /// <summary> /// 价格 /// & ...
- 源码剖析@ApiImplicitParam对@RequestParam的required属性的侵入性
问题起源 使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便.最近却遇到了一个有点困惑的 ...