IntentService 源码理解

news2025/8/7 6:00:29

一、概述

        本篇文章讲解的是分析IntentService源码并使用,安卓API迭代更新的太快,IntentService已经在Android8.0 (API 26)之后就不推荐使用了,在Android API 30正式弃用,官方建议用JobIntentService 或 WorkManager替代,但它的实现源码和思想也值得再回味一下。读本篇文章之前,请移步笔者的另外一篇文章:

HandlerThread源码理解_broadview_java的博客-CSDN博客 , 理解HandlerThread的用法后,对理解IntentService 也会事半功倍。

二、IntentService是什么

2.1 概述

        IntentService 继承了Service,并且它是一个抽象类,它又比普通的Service增加了额外的功能。那Google为什么要引入IntentService呢?

1. 我们知道Service它是一个服务,是Android四大组件之一,它和应用程序处于同一个进程之中

2. Service也不是一个线程,和线程完全没有关系,所以你在Service中直接处理耗时的任务,容易引起ANR。如果要做耗时任务,还必须要手动开启一个线程去处理任务。

3. 从Service的生命周期管理角度来看,startService启动服务,当做完任务后,还需要调用stopService停止服务。

但是 IntentService 就不需要考虑这么多,它既不需要去我们手动去开线程,也不需要我们做完任务后手动停止服务

2.2 特征

1. IntentService 会创建单独的work线程来处理所有的Intent请求,开发者无需处理多线程问题

2. IntentService可用于执行后台耗时任务,当所有请求处理完成后,IntentService会自动停止

3. IntentService是一个服务,四大组件之一,所以它的优先级肯定要比单独创建出来一个线程优先级要高,因此也不容易被系统kill掉,可以保证后台任务能完成。

三、源码分析

//IntentService首先是一个抽象类,类中只有一个abstarct抽象方法:onHandleIntent
//所以我们需要重写onHandleIntent这个方法
public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    @UnsupportedAppUsage
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;
    
    //内部ServiceHandler,用于线程间通信
    //这里是消息处理端,对应于 onStar()消息发送端
    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); 
        }
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     * 通过名称来创建IntentService
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     * with your preferred semantics.
     *
     * <p>If enabled is true,
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
     * {@link #onHandleIntent(Intent)} returns, the process will be restarted
     * and the intent redelivered.  If multiple Intents have been sent, only
     * the most recent one is guaranteed to be redelivered.
     *
     * <p>If enabled is false (the default),
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
     * dies along with it.
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }
    
    
    @Override
    public void onCreate() {
        super.onCreate();
        //内部还是由 HandlerThread  +  Handler 来实现的
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        //HandlerThread在线程内部创建Looper和消息队列,并且在线程启动之后开启消息循环
        thread.start();
        
        //获取工作线程的Looper
        mServiceLooper = thread.getLooper();
        //创建ServiceHandler,绑定工作线程的Looper,这样子ServiceHandler就可以往消息队
        //中分发消息
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        //发送消息,添加到消息队列
        mServiceHandler.sendMessage(msg);
    }

    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     * @see android.app.Service#onStartCommand
      自定义的IntentService 可以不用复写 onStartCommand方法,但是一定要复写onHandleIntent方法
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        //调用onStart(intent, startId)方法
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    /**
     * 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;
    }

    /**
     * This method is invoked on the worker thread with a request to process.
     * Only one Intent is processed at a time, but the processing happens on a
     * worker thread that runs independently from other application logic.
     * So, if this code takes a long time, it will hold up other requests to
     * the same IntentService, but it will not hold up anything else.
     * When all requests have been handled, the IntentService stops itself,
     * so you should not call {@link #stopSelf}.
     *
     * @param intent The value passed to {@link
     *               android.content.Context#startService(Intent)}.
     *               This may be null if the service is being restarted after
     *               its process has gone away; see
     *               {@link android.app.Service#onStartCommand}
     *               for details.
     */
    //在自己的IntentService中重写此方法
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

3.1 onCreate()方法

@Override
    public void onCreate() {
        super.onCreate();
        //内部还是由 HandlerThread  +  Handler 来实现的
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        //HandlerThread在线程内部创建Looper和消息队列,并且在线程启动之后开启消息循环
        thread.start();
        
        //获取工作线程的Looper
        mServiceLooper = thread.getLooper();
        //创建ServiceHandler,绑定工作线程的Looper,这样子ServiceHandler就可以往消息队
        //中分发消息
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

参考之前写的HandlerThread源码理解,我们知道 IntentService 其实内部封装了 HandlerThread + Handler ,这也解释了它可以用来执行后台任务,而不会阻塞UI线程引起ANR。

3.2 onStartCommand()方法

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

补充一下基础知识,在Service生命周期中,当第一次用startService方法启动服务时回调:

onCreate()---->onStartCommand()  ; 如果再启动该正在运行的服务,它不会再走onCreate()

只会走onStartCommand() ,源码中接着会调用onStart(intent, startId) 方法,这里也可以延伸一下,我们在实际应用中,就可以多次调用startService()方法来执行多项后台任务,IntentService也会多次调用 onStart(intent, startId)方法 ,我们来看看此方法

3.3  onStart(@Nullable Intent intent, int startId) 方法

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        //发送消息,添加到消息队列
        mServiceHandler.sendMessage(msg);
    }

也就是每调用一下此方法,就会封装一个不同 startID 的Message对象,然后通过mServiceHandler分发到工作线程中去处理,由于HandlerThread 是单线程工作,所以这些任务就需要排队一个一个的处理,待处理完毕,就停止服务,这也是设计巧妙的地方,避免了多次创建IntentService.

3.4 onDestory()方法

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

   Looper消息轮询器退出

3.5 stopSelf(int startId)方法

        此方法不同于Service的stopSelf()方法,当onHandlerIntent方法执行结束后,IntentService会通过stopSelf(int startId)方法来尝试停止服务。这里之所以采用stopSelf(int startId)而不是stopSelf()方法,那是因为stopSelf()会立即停止运行的服务,而这个时候可能还有其他消息未处理,stopSelf(int startId)则会等待所有的消息都处理完毕后才会停止服务。一般来说stopSelf(int startId)在尝试停止服务之前会判断最近启动的服务次数是否和startId相等,如果相等就立刻停止服务,不相等则不停止服务,这个策略可以从 AMS 的 public boolean stopServiceToken(ComponentName className, IBinder token, int startId) 这个方法中去查证。

四、实例演示

首先我在IntentService源码中加两句打印log:

然后编译替换 framework.jar 

下面是Demo代码:

public class MainActivity extends AppCompatActivity {

    private Button mStartButton;
    private Button mStopButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mStartButton = findViewById(R.id.service_start);

        Intent intent = new Intent(this, MyIntentService.class);

        mStartButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                intent.putExtra("task_id", "任务1");
                startService(intent);

                intent.putExtra("task_id", "任务2");
                startService(intent);

                intent.putExtra("task_id", "任务3");
                startService(intent);
            }
        });

    }
}
public class MyIntentService extends IntentService {

    public MyIntentService() {
        super("myIntentService");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("test", "=====MyIntentService====onCreate====");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String taskName = intent.getStringExtra("task_id");
        Log.e("test", "mServiceHandler 处理线程名称:" + Thread.currentThread().getName());
        Log.d("test", "======通过线程睡眠10秒模拟耗时任务==="+ taskName + "====start======");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }

        Log.d("test", "======"+  taskName  + "====end======");
    }

    @Override
    public void onDestroy() {
        Log.e("test", "=====MyIntentService====onDestroy====");
        super.onDestroy();
    }

运行打印log如下:

 通过上述log分析,我们可以得出如下结论:

1. 当执行多个耗时任务时,它的工作模式是单服务(一个IntentService),单线程顺序执行任务(任务1执行--->  执行任务2 ----> 执行任务3 )----->退出服务

2. 睡眠10秒,模拟耗时任务,也不会引起ANR,说明是在子线程中执行任务,也和源码中IntentService 内部实现是HandlerThread 可以对应上。

我们把源码中IntentService加的log去掉,只打印任务执行相关的log,也更加清楚其执行过程:

11-18 18:06:20.251  3085  3085 D ok      : =====MyIntentService====onCreate====

11-18 18:06:20.253  3085  3159 D ok      : ======通过线程睡眠10秒模拟耗时任务===任务1====start======
11-18 18:06:30.253  3085  3159 D ok      : ======任务1====end======
11-18 18:06:30.255  3085  3159 D ok      : ======通过线程睡眠10秒模拟耗时任务===任务2====start======
11-18 18:06:40.255  3085  3159 D ok      : ======任务2====end======
11-18 18:06:40.257  3085  3159 D ok      : ======通过线程睡眠10秒模拟耗时任务===任务3====start======
11-18 18:06:50.257  3085  3159 D ok      : ======任务3====end======

11-18 18:06:50.261  3085  3085 E ok      : =====MyIntentService====onDestroy====

 好了,到这里就应该对IntentService的使用差不多明白了,多打印log调试理解的更深一点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/15671.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么要少用全局变量

为什么要少用全局变量&#xff1f;甚至有些公司禁止用全局变量。有一个说法是这样的&#xff0c;全局变量的最佳前缀是什么&#xff1f;答&#xff1a;// 接下来就粗略说说这个问题。 1、全局变量和局部变量 &#xff08;1&#xff09;全局变量&#xff1a;定义在函数外&…

RocketMQ NameServer 概览

&#x1f34a; Java学习&#xff1a;Java从入门到精通总结 &#x1f34a; 深入浅出RocketMQ设计思想&#xff1a;深入浅出RocketMQ设计思想 &#x1f34a; 绝对不一样的职场干货&#xff1a;大厂最佳实践经验指南 &#x1f4c6; 最近更新&#xff1a;2022年11月18日 &#…

析构函数详解

析构函数1.概念与特性2.工作原理4.析构的顺序如果一个类中什么成员都没有&#xff0c;那么该类简称为空类。而空类中其实并不是真的什么都没有&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员函数。构造函数&#xff1a;主要完成初始化工作析构函…

内网渗透神器CobaltStrike之配置与基础操作(一)

CobaltStrike简介 Cobalt Strike: C/S架构的商业渗透软件&#xff0c;适合多人进行团队协作&#xff0c;可模拟APT做模拟对抗&#xff0c;进行内网渗透。 Cobalt Strike 一款GUI的框架式渗透工具&#xff0c;集成了端口转发、服务扫描&#xff0c;自动化溢出&#xff0c;多模…

megahit源码迁移解析

megahit源码迁移大作业 在进行megahit源码迁移前需要清理自己的实验环境 1、链接鲲鹏服务器 2、进入源码存放地址/opt/portadv/portadmin/sourcecode 环境准备&#xff0c;清理之前实验环境后下载本次实验所需的源码 之前存在的其他文件&#xff0c;删除code 登录代码迁移工…

Python常见操作的时间复杂度

Python常见操作的时间复杂度 本文整理了Python中常见数据结构操作的时间复杂度&#xff0c;旨在帮助大家了解Python操作的性能&#xff0c;协助运行更快的代码。 文章目录标注方法List操作Set操作Deque操作标注方法 程序时间复杂度一般用"大O表示法&#xff08;Big-O no…

windows11系统WSL2安装ubuntu20.04桌面

文章目录1. MobaXterm安装2.WSL安装xfce desktop3. 连接桌面参考链接1. MobaXterm安装 这个比较简单&#xff0c;没介绍 2.WSL安装xfce desktop 安装命令 sudo apt-get install xfce4-terminal sudo apt-get install xfce4安装完之后需要稍微配置一下&#xff1a; export …

【并发编程六】c++进程通信——信号量(semaphore)

【并发编程六】c进程通信——信号量&#xff08;semaphore&#xff09;一、概述二、信号量三、原理四、过程1、进程A过程2、进程B过程五、demo1、进程A2、进程B六、输出七、windows api介绍1. 创建信号量 CreateSemaphore()2. 打开信号量 OpenSemaphore()3. 等待 WaitForSingle…

力扣LeatCode算法题第三题-无重复字符的最长子串

要求&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 我一开始采用的第一种方法是使用hashmap去比对大小&#xff0c;在idea上可以跑通程序&#xff0c;但在leatcode的编译器中&#xff0c;无法通过字符串s"" 和s"…

苹果推送和开发证书更新

1.背景 推送证书&#xff08;生产Apple Push Services和开发APNs Development iOS&#xff09;的有效期都是一年&#xff0c;将要过期的时候&#xff0c;苹果官方会发邮件提醒。 2.csr和推送证书更新 打开mac电脑&#xff0c;找到启动台-->其他&#xff0c;打开钥匙串访问。…

uni-app入门:WXML数据绑定

1.简单数据绑定 2.组件属性数据绑定 3.运算绑定 3.1三元运算符 3.2算数运算 3.3字符串拼接运算 3.4逻辑判断运算 正文 WXML全称&#xff1a;wexin markup language,微信标签语言&#xff0c;可以理解为web中的html&#xff0c;…

JavaEE之HTTP协议 Ⅱ

文章目录一、认识请求"报头"(header)1.HOST2.Content-Length3.Content-Type4.User-Agent (简称 UA)5.Referer4.Cookie二、HTTP响应详解1. 认识"状态码"(status code)2. Content-Type3. 如何构造HTTP请求总结一、认识请求"报头"(header) 这里的键…

php资源列表|开发者应知晓

PHP PSR 代码标准 官网: https://www.php-fig.org原文: https://github.com/php-fig/fi...中文: https://psr.phphub.orgPHP资源列表PHP设计模式PHP知识技能树PHP资源站 PHP WeeklyCodeVisuallyPHP LeagueKnpLabs开发项目 FastAdmin - 基于 ThinkPHP5 + Bootstrap 的极速后台开…

ubuntu搭建Elasticsearch过程与问题

目录 一.下载Elasticsearh 二.解压 创建需要的文件目录 三.修改配置文件 四.遇到的问题 1.root账号启动问题 2.创建文件不授权切换到非root用户test的时候报错 3.启动最后还是报错 4.浏览器请求http://localhost:9200 报错&#xff1a;received plaintext http traffic…

【python】算法与数据结构作业记录分析

目录 算法与数据结构实验题 9.21 朋友圈 ★实验任务 ★数据输入 ★数据输出 输入示例 输出示例 代码实现 效果展示 算法与数据结构实验题 9.24 水杯 ★实验任务 ★数据输入 ★数据输出 输入示例 输出示例 代码实现 效果展示 算法与数据结构实验题 9.21 朋友圈 …

实验(四):LCD1602显示实验

一、实验目的与任务 实验目的&#xff1a; 1. 掌握LCD1602显示控制方法&#xff1b; 2. 掌握利用Proteus进行单片机控制系统的仿真及调试方法。 3. 掌握单片机开发板的使用。 任务&#xff1a; 1.根据要求编写程序&#xff0c;并写出原理性注释&#xff1b; 2. 将检查程序运行的…

Java入门项目——读书管理系统

Java简单实现读书管理系统一、前言二、思路及整体框架三、代码展示1.有关读书包&#xff08;Book&#xff09;2.有关用户包3.有关操作书的包一、前言 相信有很多小伙伴学习完了【JavaSE】基础语法&#xff0c;想知道自己到底学的怎么样&#xff0c;或则学完不知道这么把知识点…

JavaFX之Scene Builder的使用(开发一款GUI小工具原来这么简单)

文章目录一、前言二、JavaFX与Scene Builder下载三、Scene Builder的使用四、详细教学&#xff08;示例&#xff09;4.1 环境配置4.2 创建fxml文件以及Controller类文件4.3 自定义界面4.4 运行我们的程序五、拓展总结博主个人社区&#xff1a;开发与算法学习社区 博主个人主页&…

创建.gitignore文件并使用

创建 .gitignore文件 第一种方式 在项目根目录下直接创建一个文件&#xff0c;后缀改成 .gitignore 即可。 第二种方式 用git创建&#xff0c;到根目录下&#xff0c;执行 touch .gitignore&#xff0c;即可看见目录下已经出现了该忽略文件。 添加忽略规则 # 忽略所有以 …

httpOnly对于抵御Session劫持的个人小结

Ⅰ 什么是http only?起到什么防护作用&#xff1f; cookie中设置了HttpOnly属性&#xff0c;那么通过js脚本将无法读取到cookie信息&#xff0c;主要防护的攻击手段&#xff1a;XSS不能通过document对象直接获取cookie Ⅱ 怎么绕过http only的防护&#xff08;三种&#xff…