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. ref和out的使用及区别

    1.  ref的使用:使用ref进行参数的传递时,该参数在创建时,必须设置其初始值,且ref侧重于修改: 2. out的使用: 采用out参数传递时,该参数在创建时,可以不设置初始值,但是在方法中必须 ...

  2. oracle 环境变量配置 字符集编码配置

    字符编码的环境变量配置: http://jingyan.baidu.com/article/e73e26c0c20f1a24adb6a73e.html (1).数据库服务器字符集select * fr ...

  3. AcWing 走廊泼水节 题解

    这道题大致题意就是让一棵树任意两点有连边(也就是完全图),但是补完后最小生成树是一开始的那棵树,问最小加的边权之和是多少. 了解题意后,我们可以想到用Kruskal(废话),当每两个集合合并的时候,除 ...

  4. Python 实现短信轰炸机

    原理其实很简单,就是利用selenium包打开各种网站的注册页,输入轰炸的号码,实现轰炸.其实也算是利用了注册漏洞.申明:仅娱乐使用,禁止️用于非法用途!若用于非法用途,后果及法律责任博主一律不承担 ...

  5. 学习 Spring Boot 知识看这一篇就够了

    从2016年因为工作原因开始研究 Spring Boot ,先后写了很多关于 Spring Boot 的文章,发表在技术社区.我的博客和我的公号内.粗略的统计了一下总共的文章加起来大概有六十多篇了,其 ...

  6. 如何配置webpack让浏览器自动补全前缀

    一.postcss-loader有什么用? PostCSS 本身是一个功能比较单一的工具.它提供了一种方式用 JavaScript 代码来处理 CSS.它负责把 CSS 代码解析成抽象语法树结构(Ab ...

  7. web前端工程化/构建自动化

    前端工程化 前端工程化的概念在近些年来逐渐成为主流构建大型web应用不可或缺的一部分,在此我通过以下这三方面总结一下自己的理解. 为什么需要前端工程化. 前端工程化的演化. 怎么实现前端工程化. 为什 ...

  8. 字符串的排列(剑指offer-27)

    题目描述 输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba. 输入描述: 输 ...

  9. LNMP架构应用案例----WordPress

    一.环境部署 mysql        172.16.1.10                                  数据库 web01      10.0.0.11      172.1 ...

  10. Charles抓包工具操作指南

    一.Charles说明 Charles是HTTP代理/ HTTP监视器/反向代理,使开发人员或测试人员可以查看其计算机与Internet之间的所有HTTP和SSL/ HTTPS通信. 这包括请求,响应 ...