看完这一篇,再也不怕面试官问到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 ...
随机推荐
- Jquery的一些方法
$.trim(str);说明:去掉字符串首尾空格.
- GitHub 热点速览 Vol.26:手把手带你做数据库
作者:HelloGitHub-小鱼干 摘要:手把手带你学知识,应该是学习新知识最友好的姿势了.toyDB 虽然作为一个"玩具"项目不能应用在实际开发中,但通过它你可以了解到如何制作 ...
- 浅谈MySQL数据库基本操作
数据库配置 通过配置文件统一配置的目的:统一管理 服务端(mysqld) .客户端(client) 配置了 mysqld(服务端) 的编码为utf8,那么再创建的数据库,默认编码都采用utf8 配置流 ...
- SQL中的ON DUPLICATE KEY UPDATE使用详解
一:主键索引,唯一索引和普通索引的关系主键索引 主键索引是唯一索引的特殊类型. 数据库表通常有一列或列组合,其值用来唯一标识表中的每一行.该列称为表的主键. 在数据库关系图中为表定义一个主键将自动创建 ...
- Taro 3 正式版发布:开放式跨端跨框架解决方案
作者:凹凸曼 - yuche 从 Taro 第一个版本发布到现在,Taro 已经接受了来自于开源社区两年多的考验.今天我们很高兴地在党的生日发布 Taro 3(Taro Next)正式版,希望 Tar ...
- 小书MybatisPlus第1篇-整合SpringBoot快速开始增删改查
Mybatis Plus官方文档已经很完善了,为什么还要写一个这样的文档? 官方文档注重知识结构的整理,没有注重学习者的学习顺序 官方文档中的案例注重API描述,比较适合学会mybatis plus之 ...
- js实现json格式化,以及json校验工具的简单实现
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,采用完全独立于语言的文本格式,但是也使用了类似于C语言家族的习惯(包括C, C++, C#, Java, ...
- CBV源码与APIView源码解析
一.CBV源码解析 在我们写cbv的时候在url中和fbv的区别就是是否调用了as_view()方法,所以关键入手点就是这个方法 @classonlymethod # 这是类的绑定方法,这个cls是我 ...
- python面试题六: 剑指offer
面试题3 二维数组中的查找 LeetCode题目:二维数组中,每行从左到右递增,每列从上到下递增,给出一个数,判断它是否在数组中思路:从左下角或者右上角开始比较 def find_integer(ma ...
- tensorflow.python.framework.errors_impl.InvalidArgumentError: You must feed a value for placeholder tensor 'x_1' with dtype float and shape [?,227,227,3]
记一次超级蠢超级折磨我的bug. 报错内容: tensorflow.python.framework.errors_impl.InvalidArgumentError: You must feed a ...