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