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()

接着看看IntentServiceonCreate中有什么名堂:

    @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

由于ServiceHandlerLooper是来自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发送消息。同时也解释了为什么我们看到每启动一次IntentServiceonHandleIntent就会被回调执行一次。也由此可见,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的原理的更多相关文章

  1. 图解Java线程的生命周期,看完再也不怕面试官问了

    文章首发自个人微信公众号: 小哈学Java https://www.exception.site/java-concurrency/java-concurrency-thread-life-cycle ...

  2. 深度剖析Java的volatile实现原理,再也不怕面试官问了

    上篇文章我们讲了synchronized的用法和实现原理,我们总爱说synchronized是重量级锁,volatile是轻量级锁.为什么volatile是轻量级锁,体现在哪些方面?以及volatil ...

  3. Springboot启动扩展点超详细总结,再也不怕面试官问了

    1.背景 Spring的核心思想就是容器,当容器refresh的时候,外部看上去风平浪静,其实内部则是一片惊涛骇浪,汪洋一片.Springboot更是封装了Spring,遵循约定大于配置,加上自动装配 ...

  4. 手写webpack核心原理,再也不怕面试官问我webpack原理

    手写webpack核心原理 目录 手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6 ...

  5. 如何完美回答面试官问的Mybatis初始化原理!!!

    前言 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章将通过以下几点详细介绍MyBatis的初始化过程. MyBatis的初始化做了什么 MyBatis基于XML配置文件 ...

  6. 面试官问:JS的this指向

    前言 面试官出很多考题,基本都会变着方式来考察this指向,看候选人对JS基础知识是否扎实.读者可以先拉到底部看总结,再谷歌(或各技术平台)搜索几篇类似文章,看笔者写的文章和别人有什么不同(欢迎在评论 ...

  7. 美团面试官问我一个字符的String.length()是多少,我说是1,面试官说你回去好好学一下吧

    本文首发于微信公众号:程序员乔戈里 public class testT { public static void main(String [] args){ String A = "hi你 ...

  8. 面试官问线程安全的List,看完再也不怕了!

    最近在Java技术栈知识星球里面有球友问到了线程安全的 List: 扫码查看答案或加入知识星球 栈长在之前的文章<出场率比较高的一道多线程安全面试题>里面讲过 ArrayList 的不安全 ...

  9. 深度剖析HashMap的数据存储实现原理(看完必懂篇)

    深度剖析HashMap的数据存储实现原理(看完必懂篇) 具体的原理分析可以参考一下两篇文章,有透彻的分析! 参考资料: 1. https://www.jianshu.com/p/17177c12f84 ...

随机推荐

  1. 【实践】如何利用tensorflow的object_detection api开源框架训练基于自己数据集的模型(Windows10系统)

    如何利用tensorflow的object_detection api开源框架训练基于自己数据集的模型(Windows10系统) 一.环境配置 1. Python3.7.x(注:我用的是3.7.3.安 ...

  2. 小白写了一堆if-else,大神实在看不下去了,竟然用策略模式直接摆平了

    这里涉及到一个关键词:策略模式,那么到底什么是策略模式呢?本文就来好好给大家讲讲策略模式,大家可以带着如下几个问题来阅读本文:   1. 如何通过策略模式优化业务逻辑代码(可以根据自己从事的工作思考) ...

  3. 发家致富的鬼bug。让人心动

    这个bug是我目前见过最离谱的bug…… 颠覆了我二十多年的世界观,天上掉馅饼这种好事第一次砸我身上(雾),至今我都没想明白其中的原理. 情况是这样的,去年我出门去外地有些事情要处理.由于要呆很长时间 ...

  4. css制作简单loading动画效果【css3 loading加载动画】

    曾经以为,loading的制作需要一些比较高深的web动画技术,后来发现大多数loading都可以用“障眼法”做出来.比如一个旋转的圆圈,并不都是将gif图放进去,有些就是画个静止图像,然后让它旋转就 ...

  5. Mysql查询语句执行过程

    Mysql查询语句执行过程   Mysql分为server层和存储引擎两部分,或许可以再加一层连接层   连接层(器) Mysql使用的是典型的C/S架构.连接器通过典型的TCP握手完成连接. 需要注 ...

  6. Flask-Limit使用详细说明

    Flask-Limit详细说明 在flask项目中我们需要对全部或者一部分接口进行限制,又不想造轮子,那怎么办呢? 所以这就是flask-limit出现的原因,不过对于相对复杂的需求,还是自己造轮子吧 ...

  7. Java面向对象详解-下

    一. static static:静态的,可以用来修饰属性.方法.代码块(或初始化块).内部类 static修饰属性(类变量): 由类创建的所有的对象,都共用这一个属性 当其中一个对象对此属性进行修改 ...

  8. HTML5提高

    HTML5提高 前言 我个人觉得,当你学会了一些最基本的标签其实是够用的,但是在很多网页中可以发现很多新的标签.这个时候不知道它是干嘛的实际上心里是非常没底的,所以在这里我打算写一篇HTML5提高的文 ...

  9. 机器学习实战基础(十七):sklearn中的数据预处理和特征工程(十)特征选择 之 Embedded嵌入法

    Embedded嵌入法 嵌入法是一种让算法自己决定使用哪些特征的方法,即特征选择和算法训练同时进行.在使用嵌入法时,我们先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大 ...

  10. POJ 1046 Color Me Less 最详细的解题报告

    题目来源:POJ 1046 Color Me Less 题目大意:每一个颜色由R.G.B三部分组成,D=Math.sqrt(Math.pow((left.red - right.red), 2)+ M ...