编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.设计高效日志框架库
  • 02.国际化项目方案实践
  • 03.设计通用缓存框架
  • 04.设计轻量级线程池库
  • 05.设计通用轮训方案
  • 06.设计异步线程框架
  • 07.设计隐私合规检测库
  • /zh/android/plan/08.设备串口通信实践.html
  • 09.设计TTS语音实践
  • 10.设计悬浮窗开发实践
  • 13.SDK基础架构实践

05.设计通用轮训方案

目录介绍

  • 01.项目整体概述
    • 1.1 业务背景介绍
    • 1.2 什么是轮询
    • 1.3 什么是心跳
    • 1.4 需求技术分析
    • 1.5 心跳和轮询对比
    • 1.6 推送中心跳和轮询
  • 02.常见思路和做法
    • 2.0 实现业务轮训思路
    • 2.1 Thread.sleep实现轮询
    • 2.2 ScheduledExecutorService实现轮询
    • 2.3 Timer实现定时性周期性任务轮训
    • 2.4 使用Handler不断发送消息来实现轮训
    • 2.5 采用AlarmManager实现轮询
    • 2.6 使用Socket实现心跳
    • 2.7 各种方式有什么区别
    • 2.8 执行轮询任务分析
  • 03.各种方式性能分析
    • 3.1 sleep实现轮询性能分析
    • 3.2 线程池实现轮询性能分析
    • 3.3 Timer实现轮询性能分析
    • 3.4 Handler实现轮询性能分析
    • 3.5 AlarmManager实现轮询分析
    • 3.6 Socket实现心跳性能分析
  • 04.各种方式原理解读
    • 4.1 sleep实现轮询原理解读
    • 4.2 线程池实现轮询原理解读
    • 4.3 Timer实现轮询原理解读
    • 4.4 Handler实现轮询原理解读
    • 4.5 AlarmManager实现原理解读
    • 4.6 Socket实现心跳原理解读

01.项目整体概述

1.1 业务背景介绍

  • 实时推送
    • 使用轮询的方式可以实现实时推送功能。当客户端连接到服务器之后,服务器就可以通过轮询不断的向客户端推送实时消息。
  • 监控系统
    • 实现监控系统的功能。当需要监控某个系统的状态时,可以使用轮询不断的查询该系统的状态信息,并及时的进行预防措施,或者做数据上报操作。用轮询!
  • 二维码扫码
    • 比如超市的支付硬件设备,会一直轮训扫码。当你靠近二维码支付的时候,就立马读取到二维码数据进行支付。用轮询!
  • 即时通信聊天
    • 比如你跟某个人聊天,这个时候你们两个之间的通信,则会使用建立长链接。用心跳!

1.2 什么是轮询

  • 轮询(Poll)
    • 是一种计算机程序查询状态的方式,是指定期访问一个或多个设备或其他资源以获取它们的状态。Android端间隔固定时间主动做一些事情,相比而言,耗电,占用资源,消息可能会延迟。
  • 简单理解轮询
    • 简单理解就是 App 端每隔一定的时间重复请求的操作就叫做轮询操作,比如:App 端每隔一段时间上报一次定位信息,App 端每隔一段时间拉去一次用户状态等,这些应该都是轮询请求。

1.3 什么是心跳

  • 心跳的应用场景
    • 场景有推送,统计,即时通讯。采用socket进行长连接,客户端和服务端双向推送消息。

1.4 需求技术分析

  • 业务需求的实现目标
    • 1.项目中多处要使用到轮询,刚开始在业务代码中实现,随着业务增多,轮询功能和业务耦合严重。为了让该功能完全解耦合,因此简单封装!
    • 2.不管是哪一种技术方式实现轮询操作,功能上都可以满足业务需求,都需要有开始轮询,停止轮询,销毁等API方法。抽象一套统一的API接口
  • 注意事项说明
    • 轮训操作一般都有一定的生命周期,比如在某个页面打开时启动轮训操作,在某个页面关闭时取消轮训操作;
    • 轮训操作的请求间隔需要根据具体的需求确定,还要注意轮询;

1.5 心跳和轮询对比

  • 心跳与轮询的区别
    • 主机在检查与从机之间的连接(判断与从机之间的连接是否断开)时,一般有心跳与轮询这两种方式。这两个方式都需要主机定时逐个查询从机的状态,但它们查询的策略有所不同。
  • 轮询
    • 在轮询方式中,主机逐个查询的方式是主动向从机发送一条查询信息,然后根据从机的应答情况来判断从机的状态。
    • 比方说,主机要求从机返回一个状态码来代表当前从机所处的状态,但如果从机没有应答,就认为与从机之间的连接已经断开。
  • 心跳
    • 在心跳方式中,主机逐个查询的方式是直接从一种状态信息表中查询,此状态信息表上记录了所有从机的状态信息,而此状态信息表是由各个从机自己主动去更新的。
    • 如果有从机长期没有去更新此表,就认为与该从机之间的连接已经断开。

1.6 推送中心跳和轮询

02.常见思路和做法

2.1 实现业务轮训思路

  • 具体有哪些可以实现轮训执行任务的操作
    • 第一种方案:使用Thread.sleep实现轮询。一直做while循环
    • 第二种方案:使用ScheduledExecutorService实现轮询
    • 第三种方案:使用Timer实现定时性周期性任务轮训
    • 第四种方案:使用Handler不断发送消息来实现轮训
  • 从轮询执行角度分析任务执行类型
    • 第一种属于固定速率执行,每个执行都相对于初始执行的计划执行时间进行调度。如果由于任何原因而延迟执行,则两个或更多执行将快速连续发生以“赶上”。
    • 第二种属于固定延迟执行,每个执行都相对于前一次执行的实际执行时间进行调度。如果由于任何原因延迟执行,后续执行也将被延迟。
  • 每一种方案对应的执行类型说明
    • 第一种,第四种:是属于固定延迟执行
    • 第二种,第三种:是属于固定速率执行

2.2 使用Thread.sleep实现轮询

  • 简单来说,写一个线程执行while死循环操作。然后使用sleep让当前线程睡眠,然后执行任务操作。
    public void simplePolling() {
        while (true) {
            try {
                // 线程睡眠1秒钟
                Thread.sleep(1000);
                // 轮询的代码
                System.out.println("polling...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

2.3 使用ScheduledExecutorService实现轮询

  • 通过创建ScheduledExecutorService线程池实现轮询,scheduleAtFixedRate方法会在0秒后开始轮询,并且每隔1秒进行轮询。
    public void scheduledPolling() {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            // 轮询的代码
            System.out.println("polling...");
        }, 0, 1, TimeUnit.SECONDS);
    }

2.4 使用Timer实现定时性周期性任务轮训

  • 使用timer定时执行TimerTask,然后开始延迟轮询操作
    timer = new Timer();
    timer.schedule(new CommitTimer(), 0, getSleepTime());
    private class CommitTimer extends TimerTask {
        @Override
        public void run() {
            // 轮询的代码
            doAction();
        }
    }

2.5 使用Handler不断发送消息来实现轮训

  • 在HandleMessage方法中执行任务,任务结束后向MessageQueue中添加延时消息。
    HandlerThread handlerThread = new HandlerThread("LooperThread");
    handlerThread.start();
    handler = new Handler(handlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == MSG_GET_COMPARE_RESULT) {
                // 轮询的代码
                doAction();
                //向MessageQueue中添加延时消息,后重复执行以上任务
                handler.sendEmptyMessageDelayed(MSG_GET_COMPARE_RESULT, getSleepTime());
            }
        }
    };

2.6 采用AlarmManager实现轮询

  • 采用Service服务和闹钟AlarmManager实现轮询
    //开启轮询服务
    //使用AlarmManger的setRepeating方法设置定期执行的时间间隔(seconds秒)和需要执行的Service
    manager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
            LOOP_INTERVAL_SECS * 1000, pendingIntent);
            
    // 关闭轮询服务
    Log.i("LoopRequestService", "LoopService关闭轮询服务...");
    context.stopService(intent);
  • 针对AlarmManager和Service之间的任务心跳处理

2.7 各种方式有什么区别

  • 使用Thread.sleep实现轮询
    • 使用Thread.sleep实现轮询,当销毁之后,没有办法再次开始循环,因为线程interrupt,线程死亡。再次使用需要新创建线程对象!
  • 使用ScheduledExecutorService实现轮询
    • 使用ScheduledExecutorService线程池实现轮训,销毁之后还是可以再次开始循环操作。
  • 使用Timer实现定时性周期性任务轮训
    • 使用timer定时执行TimerTask,如果有异步任务,下次任务开始执行时需要判断上次任务是否完成,从而导致任务间隔时间不可控。
  • 使用Handler不断发送消息来实现轮训
    • 如果有异步任务,只需在异步任务执行完毕后再向MessageQueue中添加延时消息,任务间隔时间可控。性能开销相对很低!
  • 使用Service服务和闹钟AlarmManager实现轮询
    • 开启一个Service,这样消耗显而易见,在app内存持续暴涨情况下这个服务又被kill的可能。
    • 可以把Service声明成进程,以及提高到前台,也可以采用广播来启动,但逃不过第三方安全软件kill的可能

2.8 执行轮询任务分析

  • 使用Thread.sleep实现轮询
    • 问题1:线程sleep出现了异常是否会中断循环?
    • 答案是:无法通过中断形式退出线程。因为在sleep里catch到了InterruptedException。然后在这里线程的状态被更新了。所以线程并不能停止。当进行Thread.interrupted进行判断时,发现还是是false。
    • 问题2:如何跳出while循环操作不执行doAction任务?
    • 答案是:直接通过一个布尔值变量控制,注意它并没有干掉线程循环,只是通过条件让其不执行任务操作。
    • 问题3:使用sleep方法延迟阻塞线程,对轮询的性能消耗是否有大的影响?有没有更好的方式?
    • 答案是:探讨中
  • 使用Timer和Service实现轮询分析
    • 很可能出现的情况就是屏幕熄灭后一段时间,服务就被停止了,当然轮询也就被停止了。
  • 使用闹钟AlarmManager实现轮询分析
    • 一种是利用系统服务,一种是自己通过循环。显然使用系统服务具有更高的稳定性,而且恰好解决了休眠状态下轮询中断的问题,因为AlarmManager是始终运行者的。

03.各种方式性能分析

3.1 sleep实现轮询性能分析

3.2 线程池实现轮询性能分析

3.3 Timer实现轮询性能分析

3.4 Handler实现轮询性能分析

3.5 AlarmManager实现轮询分析

04.各种方式原理解读

4.1 sleep实现轮询原理解读

4.2 线程池实现轮询原理解读

4.3 Timer实现轮询原理解读

4.4 Handler实现轮询原理解读

4.5 AlarmManager实现原理解读

参考内容

  • 线程循环抽象库

    • LooperThread,轮询工具库
  • 心跳工具库

    • HeartBeatServer,
  • 长连接推送库

    • WebSocketLib,
  • WebSocket安卓客户端实现及代码封装

    • https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650243616&idx=1&sn=7d7d270461965cdcecfabedbc40d43ac&chksm=8863734fbf14fa59c2f50b826270a15bd86ca13e689efc153d1c10835271e87d070a99d18bd3&scene=38#wechat_redirect
  • 源码角度,分析OkHttp实现WebSocket | 握手/保活/数据处理...

    • https://mp.weixin.qq.com/s/oRwUhGELgSD9ih1fhucLpw
  • 雨露均沾的OkHttp—WebSocket长连接(使用篇)

    • https://juejin.cn/post/6847009772198166536
贡献者: yangchong211
上一篇
04.设计轻量级线程池库
下一篇
06.设计异步线程框架