Android源码阅读WorkMangaer - 4

前言

由于笔者目前水平限制,表达能力有限,尽请见谅。

WorkManager 是 Android Jetpack 库的一部分,提供了一种向后兼容的方式来安排可延迟的异步任务,这些任务即使在应用退出或设备重启后也应该继续执行,它是 Android 推荐的解决方案,用于处理需要保证执行的后台任务。WorkManager 适合用于那些不需要立即执行的任务,但最终需要完成的任务。

上文主要深挖到了最佳调度器BestAvailableBackgroundScheduler的选择方式,接下来先将继续深入调度器的原理。

正文

SystemJobScheduler调度器

系统最推荐的调度器(设备支持下的最佳调度器)。

类的参数如下:

 对于最关键的schedule函数,代码如下:

具体来看他做了这些事

  1. IdGenerator生成新的作业ID生成器。
  2. 方法接收一个或多个WorkSpec对象,方法遍历这些WorkSpec对象,准备将它们转换为JobScheduler作业。
  3. 开启数据库事务,然后根据WorkSpec的Id获取WorkSpec,如果WorkSpec不再数据库中,或者其状态不是已入队(ENQUEUED),则跳过调度,并将当前事务标记为成功。
  4. 接下来首先检查是否已经为这个WorkSpec分配了一个系统作业ID(SystemIdInfo),如果没有生成一个新的,新ID在mConfiguration定义的最小和最大作业ID范围内。
  5. 如果infonull,则创建一个新的SystemIdInfo对象,其中包含了WorkSpec的生成ID和新分配的作业ID。
  6. 接着,将这个新的SystemIdInfo对象插入到数据库中,在WorkSpecJobScheduler作业之间建立持久的关联。

scheduleInternal是实际执行调度逻辑的方法,将WorkSpec转换为JobInfo对象,并通过JobScheduler安排执行。

同时针对API23进行特殊处理(Android6.0)

在Android API 23(Android 6.0)上,JobScheduler只有在队列中至少有两个作业时才会启动作业,为了兼容,SystemJobScheduler会在API 23上为每个作业进行双重调度,方法通过调用getPendingJobIds来获取当前已调度但尚未执行的作业ID列表,然后为当前WorkSpec选择一个新的作业ID进行第二次调度,emmm。

getPendingJobIds函数如下

@Nullableprivate static List<Integer> getPendingJobIds(@NonNull Context context,@NonNull JobScheduler jobScheduler,@NonNull String workSpecId) {List<JobInfo> jobs = getPendingJobs(context, jobScheduler);if (jobs == null) {return null;}// We have at most 2 jobs per WorkSpecList<Integer> jobIds = new ArrayList<>(2);for (JobInfo jobInfo : jobs) {WorkGenerationalId id = getWorkGenerationalIdFromJobInfo(jobInfo);if (id != null && workSpecId.equals(id.getWorkSpecId())) {jobIds.add(jobInfo.getId());}}return jobIds;}

本函数不再多研究了,刚刚遇到的有意思的函数是另外一个:

将一个特定的WorkSpec转换为JobScheduler可识别的任务并进行调度。

@VisibleForTestingpublic void scheduleInternal(@NonNull WorkSpec workSpec, int jobId) {JobInfo jobInfo = mSystemJobInfoConverter.convert(workSpec, jobId);Logger.get().debug(TAG,"Scheduling work ID " + workSpec.id + "Job ID " + jobId);try {int result = mJobScheduler.schedule(jobInfo);if (result == JobScheduler.RESULT_FAILURE) {Logger.get().warning(TAG, "Unable to schedule work ID " + workSpec.id);if (workSpec.expedited&& workSpec.outOfQuotaPolicy == RUN_AS_NON_EXPEDITED_WORK_REQUEST) {// Falling back to a non-expedited job.workSpec.expedited = false;String message = String.format("Scheduling a non-expedited job (work ID %s)", workSpec.id);Logger.get().debug(TAG, message);scheduleInternal(workSpec, jobId);}}} catch (IllegalStateException e) {// This only gets thrown if we exceed 100 jobs.  Let's figure out if WorkManager is// responsible for all these jobs.List<JobInfo> jobs = getPendingJobs(mContext, mJobScheduler);int numWorkManagerJobs = jobs != null ? jobs.size() : 0;String message = String.format(Locale.getDefault(),"JobScheduler 100 job limit exceeded.  We count %d WorkManager "+ "jobs in JobScheduler; we have %d tracked jobs in our DB; "+ "our Configuration limit is %d.",numWorkManagerJobs,mWorkDatabase.workSpecDao().getScheduledWork().size(),mConfiguration.getMaxSchedulerLimit());Logger.get().error(TAG, message);IllegalStateException schedulingException = new IllegalStateException(message, e);// If a SchedulingExceptionHandler is defined, let the app handle the scheduling// exception.Consumer<Throwable> handler = mConfiguration.getSchedulingExceptionHandler();if (handler != null) {handler.accept(schedulingException);} else {// Rethrow a more verbose exception.throw schedulingException;}} catch (Throwable throwable) {// OEM implementation bugs in JobScheduler cause the app to crash. Avoid crashing.Logger.get().error(TAG, "Unable to schedule " + workSpec, throwable);}}

这里mSystemJobInfoConverter就发挥作用了,将WorkSpec对象转换为JobScheduler需要的JobInfo对象,对于这个方法也就不多探究了,

具体就是将WorkSpec定义的约束和配置映射到JobInfo的相应属性上,如设置任务的执行条件(比如网络状态、设备充电状态等)。

接着使用JobSchedulerschedule方法尝试调度任务。schedule方法返回一个结果码,如果调度失败(JobScheduler.RESULT_FAILURE),则记录警告日志,如果一个加急的WorkSpecworkSpec.expeditedtrue)无法调度,且其outOfQuotaPolicy策略设置为RUN_AS_NON_EXPEDITED_WORK_REQUEST,则会尝试将其作为非加急任务重新调度。

catch就捕获各种异常,如有定义异常处理器,就传给异常处理器,如果没有就打印。

GreedyScheduler调度器

GreedyScheduler 是 一个为了尽快执行任务而设计的调度器,不依赖于系统的 JobScheduler,而是直接在应用进程中尝试执行任务。

其参数如下

  • Configuration :WorkManager的配置信息,如最小延迟时间、任务重试策略。
  • Processors:管理和执行后台任务。
  • WorkLauncher:实际启动执行任务。
  • TaskExecutor :执行任务的线程池管理器。
  • WorkConstraintsTracker :监视任务约束条件。
  • DelayedWorkTracker:跟踪和调度需要延迟执行的任务。

关键的调度方法如下

 

  • 首先通过checkDefaultProcess方法检查当前进程是否为应用的默认进程。如果不是则记录日志并退出方法。
  • registerExecutionListenerIfNeeded方法确保GreedyScheduler注册了对工作执行的监听,允许调度器在工作任务执行完成后接收回调。
  • 对于每一个传入的WorkSpec对象,检查这项工作是否已经有一个对应的启动令牌,有则跳过此任务。

计算下一次执行任务的时间

  • 如果工作已计划在未来执行,并且存在延迟工作跟踪器,则安排该工作在未来执行。
  • 如果工作有特定的约束条件,且当前环境不满足这些约束,则跳过该工作。
  • 如果工作无需延迟且无特殊约束或当前环境满足约束条件,则准备立即执行该工作。
  1. 接下来synchronized (mLock)确保对mConstrainedWorkSpecs访问和修改的线程安全。
  2. if (!constrainedWorkSpecs.isEmpty())确保只有在存在至少一个有约束条件的WorkSpec时,才进行后续操作。
  3. 如果mConstrainedWorkSpecs包括了约束Id,创建一个新的Job来监听这个WorkSpec的约束条件,并把Job和WorkSpec关联。

这个Job会持续监听与WorkSpec相关的约束条件是否被满足,采用协程作业。

@Overridepublic void onConstraintsStateChanged(@NonNull WorkSpec workSpec,@NonNull ConstraintsState state) {WorkGenerationalId id = generationalId(workSpec);if (state instanceof ConstraintsState.ConstraintsMet) {// it doesn't help against races, but reduces useless load in the systemif (!mStartStopTokens.contains(id)) {Logger.get().debug(TAG, "Constraints met: Scheduling work ID " + id);StartStopToken token = mStartStopTokens.tokenFor(id);mTimeLimiter.track(token);mWorkLauncher.startWork(token);}} else {Logger.get().debug(TAG, "Constraints not met: Cancelling work ID " + id);StartStopToken runId = mStartStopTokens.remove(id);if (runId != null) {mTimeLimiter.cancel(runId);int reason = ((ConstraintsState.ConstraintsNotMet) state).getReason();mWorkLauncher.stopWorkWithReason(runId, reason);}}}

WorkSpec的约束条件满足状态变化时,这个方法会被调用,

外层if检查约束条件是否都满足了,如果未满足,则从mStartStopTokens集合中移除对应的启动令牌,停止跟踪该工作的执行状态,继续使用stopWorkWithReason(runId, reason)停止工作任务的执行,reason参数指明停止执行的原因。

被嵌套的if检查mStartStopTokens集合中是否已经存在对应WorkSpec的启动令牌,如果不存在对应的启动令牌,说明这项工作尚未开始执行,可以继续启动过程。

系列文章未完待续。

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

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

相关文章

Java中 List 集合,通过 Stream 流进行排序总结

一、数据准备 public class OrderTest {private String channelCode;private BigDecimal rate;// 省略 getter、setter、toString()、constructor }List<OrderTest> orderTestList new ArrayList<>();OrderTest z09 new OrderTest("Z09", new BigDeci…

C++细节

背景知识&#xff1a; 面向对象的编程中&#xff0c;类&#xff08;Class&#xff09;是创建对象的蓝图或模板&#xff0c;它包含了数据&#xff08;通常称为属性或变量&#xff09;和行为&#xff08;通常称为方法或函数&#xff09;。将数据封装为私有&#xff08;private&am…

VUE之首次加载项目缓慢

最近公司有个大型的项目&#xff0c;使用vue2开发的&#xff0c;但是最终开发完成之后&#xff0c;项目发布到线上&#xff0c;首次加载项目特别缓慢&#xff0c;有时候至少三十秒才能加载完成&#xff0c;加载太慢了&#xff0c;太影响用户体验了&#xff0c;最近研究了一下优…

tcp和udp分别是什么?udp和tcp的区别

TCP和UDP是计算机网络中常见的两种传输层协议&#xff0c;它们在实际应用中具有不同的特点和用途。本文将对TCP和UDP进行介绍&#xff0c;并分析它们之间的区别。 TCP和UDP分别是什么&#xff1f; TCP&#xff08;Transmission Control Protocol&#xff09; TCP是一种面向连…

架构整洁之道-读书总结

1 概述 1.1 关于本书 《架构整洁之道》&#xff08;Clean Architecture: A Craftsman’s Guide to Software Structure and Design&#xff09;是由著名的软件工程师Robert C. Martin&#xff08;又称为Uncle Bob&#xff09;所著。这本书提供了软件开发和架构设计的指导原则…

绘图技巧 | 双变量映射地图可视化绘制方法

本期推文我们绘制不常见的双变量主题地图&#xff0c;该类地图可以很好的在地图上用颜色展示两个变量的信息&#xff0c;相较于单一变量映射地图&#xff0c;此类地图表达的信息更加丰富和全面。本期推文主要涉及的内容如下&#xff1a; 双变量映射地图(Bivariate Choropleth M…

PPT录制视频保存在哪?我来告诉你答案

在如今的信息化时代&#xff0c;ppt已经成为了工作中必不可少的工具。而ppt录制视频能够更好地展示ppt内容&#xff0c;方便观众随时随地观看。可是很多人不知道ppt录制视频保存在哪&#xff0c;本文将为您介绍ppt录制视频的保存位置&#xff0c;为读者提供关于ppt录屏的实用技…

[Linux]知识整理(持续更新)

前言 Linux的目录结构 Linux的目录结构是一个树型结构 Windows 系统可以拥有多个盘符, 如 C盘、D盘、E盘 Linux没有盘符这个概念, 只有一个根目录 /, 所有文件都在它下面 Linux路径的描述方式 第一章 基本命令 命令格式 例:ls –la /etc 说明: 1)个别命令使用不遵循…

ELMOS

where L W o L_{Wo} LWo​() is the o-th softmax classifer&#xff0c; L U o L_{Uo} LUo​() is the o-th projector 辅助信息 作者未提供代码

16:00面试,16:06就出来了,问的问题有点变态。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

使用LLaVA模型实现以文搜图和以图搜图

本文将会详细介绍如何使用多模态模型——LLaVA模型来实现以文搜图和以图搜图的功能。本文仅为示例Demo&#xff0c;并不能代表实际的以文搜图和以图搜图的技术实现方案。 1、实现原理 使用多模态模型获取图片的标题和详细描述以文搜图功能&#xff1a;使用ES实现查询匹配&…

Qt 计算两条直线QLineF的重叠部分,如何存在则返回该直线

计算两条直线QLineF的重叠部分 效果 使用示例 QLineF intersection;bool isSuccess GetOverlapSegment(line1, line2, intersection);源码 // 判断两个QLineF对象在斜率相同时是否存在重叠部分 bool DoLineOverlapWithSameSlope(const QLineF& line1, const QLineF&…

为什么说用了10年C++的程序员也不敢说自己精通C++?

在编程领域&#xff0c;C无疑是一门具有极高复杂度和深度的语言&#xff0c;它以其卓越的性能、灵活性和广泛的应用范围赢得了众多开发者青睐。然而&#xff0c;一个现象令人深思&#xff1a;即使有程序员使用C长达十年之久&#xff0c;他们仍可能谦逊地表示自己并未真正“精通…

Cartoon FX Remaster

“卡通FX重制”是一个风格化的粒子系统预制件集合。您可以使用附带的“卡通FX简易编辑器”轻松自定义每个效果的颜色和其他属性。 卡通FX Remaster是卡通FX包1的新版本,从头开始重新制作了全新的效果。 ⭐新:通过卡通FX重制捆绑包,以20%的折扣获得所有4个卡通FX重制包! 50多…

python.类

1.类用class定义 name等是属性 是成员变量 定义完类了要定义对象 class Student: #定义了一个Student的类nameNoneageNonegenderNonesdu1Student() #创建了一个Student类的对象 sdu1.nameleo sdu1.age20 sdu1.gendermaleprint(sdu1.name) print(sdu1.age) print(sdu1.…

【数据分享】1929-2023年全球站点的逐月平均海平面压力(Shp\Excel\免费获取)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 有关气象指标的监测站点数据&#xff0c;之前我们分享过1929-2023年全球气象站…

Vue.js 3.4的新特性

Vue.js 3.4的新特性 目前&#xff0c;Vue.js的版本已经更新到3.4&#xff0c;这次更新不仅带来了性能上的飞跃&#xff0c;还引入了许多新特性&#xff0c;进一步优化了开发效率。 1. 性能提升 在性能方面&#xff0c;Vue.js 3.4 全新重写了模板解析器。与之前基于正则表达式…

Matlab从入门到精通课程

教程介绍 MATLAB是美国MathWorks公司出品的商业数学软件&#xff0c;用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人&#xff0c;控制系统等领域。 学习地址 链接&#xff1a;https://pan.baidu.com/s/1PxGarBwQusMzwPVqcE…

RAG笔记:常见问题以及解决方法

1 内容缺失 知识库中缺少必要的上下文信息。当知识库没有包含正确答案时&#xff0c;RAG 系统可能会给出一个貌似合理但实际上错误的回答&#xff0c;而不是明确表示它不知道答案。 1.1 解决方法 1.1.1 设置阈值 在回答问题前先设定一个质量标准。如果召回内容达不到标准或…

Vulnhub:DR4G0N B4LL: 1

目录 信息收集 1、arp 2、nmap WEB web信息收集 gobuster 隐藏目录发现 图片隐写 ssh登录 提权 get user 系统信息收集 get root 信息收集 1、arp ┌──(root㉿ru)-[~/kali/vulnhub] └─# arp-scan -l …