Android 9.0 JobScheduler(三) 从Job的创建到执行

Android 8.0+ 后台限制下,用JobScheduler实现进程保活的完整避坑指南 本文深入解析Android 8.0+后台限制下如何利用JobScheduler实现合规的进程保活方案。从JobScheduler核心机制、版本兼容性处理到高级调度策略,提供完整的避坑指南和实战代码示例,帮助开发者在严格的后台限制环境中保持应用功能稳定运行。 阅读详情

上一篇文章:JobScheduler(二)——JobScheduler框架介绍及JSS的启动

现在来看看Job的添加流程,当应用程序通过JobScheduler.schedule(jobinfo)添加一个Job,到它最终完成调度,这个过程是怎样的呢?现在就来看看这个过程。

首先来看其时序图:

Job的创建到执行

受限于图片大小,以上时序图中对一些流程进行了省略,只画出了重要的步骤,不过所有的步骤,都会在下面的内容分析中说明。关于以上流程中涉及到的类,在上一篇文章中都进行了说明。下面开始对该图流程进行分解。

1.客户端创建Job

一个应用需要通过Job执行任务时,通过JobScheduler来完成:

JobScheduler mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
private ComponentName mServiceComponent;
//根据JobService创建一个ComponentName对象
mServiceComponent = new ComponentName(this, MyJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);
builder.setMinimumLatency(1000);//设置延迟调度时间
builder.setOverrideDeadline(2000);//设置最大延迟截至时间
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//设置所需网络类型
builder.setRequiresDeviceIdle(true);//设置在DeviceIdle时执行Job
builder.setRequiresCharging(true);//设置在充电时执行Job
builder.setExtras(extras);//设置一个额外的附加项
mJobScheduler.schedule(builder.build());//调度Job

当JobScheduler.schedule(jobinfo)时,实际调用的是JobSchedulerImpl中的schedule()方法,该方法如下:

    @Override
    public int schedule(JobInfo job) {
   
   
        try {
   
   
            //调用进入JobSchedulerService
            return mBinder.schedule(job);
        } catch (RemoteException e) {
   
   
            return JobScheduler.RESULT_FAILURE;
        }
    }

在该方法中,mBinder就是JobSchedulerService.JobSchedulerStub类的实例,因为在注册JobSchedulerService服务时,在SystemServiceRegistry.java中:

        registerService(Context.JOB_SCHEDULER_SERVICE, JobScheduler.class,
                new StaticServiceFetcher<JobScheduler>() {
   
   
            @Override
            public JobScheduler createService() throws ServiceNotFoundException {
   
   
                IBinder b = ServiceManager.getServiceOrThrow(Context.JOB_SCHEDULER_SERVICE);
                return new JobSchedulerImpl(IJobScheduler.Stub.asInterface(b));
            }});

在这里,首先通过ServiceManager.getServiceOrThrow()拿到JobSchedulerStub对象,然后作为实力化JobSchedulerImpl的参数传入。

接着下一步分析,接下来将进入JobSchedulerService.JobSchedulerStub的schedule()中:

       @Override
        public int schedule(JobInfo job) throws RemoteException {
   
   
            if (DEBUG) {
   
   
                Slog.d(TAG, "Scheduling job: " + job.toString());
            }
            final int pid = Binder.getCallingPid();
            final int uid = Binder.getCallingUid();
            final int userId = UserHandle.getUserId(uid);

            //权限检查
            enforceValidJobRequest(uid, job);
            //如果通过JobInfo.Builder.setPersisted(true)设置了该Job在每次启动时执行,则对应应用需要RECEIVE_BOOT_COMPLETED权限
            if (job.isPersisted()) {
   
   
                if (!canPersistJobs(pid, uid)) {
   
   
                    throw new IllegalArgumentException("Error: requested job be persisted without"
                            + " holding RECEIVE_BOOT_COMPLETED permission.");
                }
            }

            //校验携带的标记是否合理
            validateJobFlags(job, uid);

            long ident = Binder.clearCallingIdentity();
            try {
   
   
                return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
                        null);
            } finally {
   
   
                Binder.restoreCallingIdentity(ident);
            }
        }

在以上方法中 ,只有一个功能:检查权限是否授予,检查标记是否正确,然后将调用进入JobSchedulerService中的scheduleAsPackage()方法中,该方法如下:

2.Binder调用进入服务端(JobSchedulerService)

    public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
            int userId, String tag) {
   
   
        try {
   
   
            //根据包名判断该应用是否允许启动,如果该应用禁用,则return RESULT_FAILURE
            if (ActivityManager.getService().isAppStartModeDisabled(uId,
                    job.getService().getPackageName())) {
   
   
                Slog.w(TAG, "Not scheduling job " + uId + ":" + job.toString()
                        + " -- package not allowed to start");
                return JobScheduler.RESULT_FAILURE;
            }
        } catch (RemoteException e) {
   
   
        }

        synchronized (mLock) {
   
   
            //查找是否已存在该Job(先找uid,再找uid中的jobId)
            final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());

            //初始化时work == null toCancel == null,故不会执行该if
            if (work != null && toCancel != null) {
   
   
                if (toCancel.getJob().equals(job)) {
   
   
                    toCancel.enqueueWorkLocked(ActivityManager.getService(), work);
                    toCancel.maybeAddForegroundExemption(mIsUidActivePredicate);
                    return JobScheduler.RESULT_SUCCESS;
                }
            }

            //1.根据JoInfo创建一个JobStatus对象,JobStatus是job在内部的唯一标识
            JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
            //2.对于没有时间限制,且当前应用处于active时,会对job添加一个INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION标记
            jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate);

            //每个应用最多只能有100个Job
            if (ENFORCE_MAX_JOBS && packageName == null) {
   
   
                if (mJobs.countJobsForUid(uId) > MAX_JOBS_PER_APP) {
   
   
                    Slog.w(TAG, "Too many jobs for uid " + uId);
                    throw new IllegalStateException("Apps may not schedule more than "
                                + MAX_JOBS_PER_APP + " distinct jobs");
                }
            }
            //设置为准备状态,并申请URI权限
            jobStatus.prepareLocked(ActivityManager.getService());

            if (toCancel != null) {
   
   
                cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
            }
            if (work != null) {
   
   
                jobStatus.enqueueWorkLocked(ActivityManager.getService(), work);
            }
            //3.开始跟踪记录这个Job
            startTrackingJobLocked(jobStatus, toCancel);

            //4.如果Job此时已经满足其调度的所有条件,则加入执行列表中调度它
            //否则什么也不做,等待控制器触发监听后改变其状态
            if (isReadyToBeExecutedLocked(jobStatus)) {
   
   
                // This is a new job, we can just immediately put it on the pending
                // list and try to run it.
                //mJobPackageTracker记录
                mJobPackageTracker.notePending(jobStatus);
                //5.添加到"将要运行job"队列中
                addOrderedItem(mPendingJobs, jobStatus, mEnqueueTimeComparator);
                //6.运行队列中的Job
                maybeRunPendingJobsLocked();
            }
        }
        return JobScheduler.RESULT_SUCCESS;//返回给客户端
    }

这个方法较长,并且其中部分逻辑不属于添加Job时的,所以下面对添加Job相关的逻辑分别进行总结,共有以下几个:

2.1.创建Job内部唯一标识——JobStatus

在应用中创建一个Job时,是以JobInfo为“单位”进行,而进入Framework层后,Job的唯一标识为JobStatus,每一个Job都对应一个唯一的JobStatus对象,JobStatus对象也正是在这个方法中实例化的,来看看如何实例化:

    public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
            int sourceUserId, String tag) {
   
   
        //当前时间
        final long elapsedNow = sElapsedRealtimeClock.millis();
        //Job运行最早/最晚时间点
        final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
        if (job.isPeriodic()) {
   
   //判断是否是定期Job
            //由setPeriodic()方法设置的
            latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
            //Job运行最早时间点
            earliestRunTimeElapsedMillis = latestRunTimeElapsedMillis - job.getFlexMillis();
        } else {
   
   
            //如果不是定期Job,分别加setMinimumLatency()和setOverrideDeadline()设置的时间值
            earliestRunTimeElapsedMillis = job.hasEarlyConstraint() ?
                    elapsedNow + job.getMinLatencyMillis() : NO_EARLIEST_RUNTIME;
            latestRunTimeElapsedMillis = job.hasLateConstraint() ?
                    elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
        }
        //客户端JobService完全限定名
        String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName();

        //获取该应用处于哪个待机桶中(standbyBucket,应用待机桶,将应用划分在5个桶中,不同的桶有不同的省电策略)
        int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
                sourceUserId, elapsedNow);
        JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
        //获取心跳时间???
        long currentHeartbeat = js != null
                ? js.baseHeartbeatForApp(jobPackage, sourceUserId, standbyBucket)
                : 0;
        //实例化一个JobStatus对象
        return new JobStatus(job, callingUid, resolveTargetSdkVersion(job), sourcePkg, sourceUserId,
               
Android进阶——更节电的后台任务JobScheduler 机制使用详解 系统提供了一种条件周期性执行的后台任务,无需开发者自己去唤醒,达到配置的条件便会自动执行 阅读详情

相关推荐

Android 8.0+ 后台限制下,用JobScheduler实现进程保活的完整代码与避坑指南

本文深入探讨了在Android 8.0+后台限制下,如何利用JobScheduler实现高效进程保活的完整方案。通过分析JobService的核心实现、多版本适配策略及优化技巧,帮助开发者提升后台任务执行成功率至89%,同时确保应用符合系统省电政策。

weixin_30770783的博客 383

告别玄学保活:实测JobScheduler在不同厂商Android系统(小米、华为、OPPO)上的拉活效果与适配方案

本文深度实测了JobScheduler在小米MIUI、华为HarmonyOS和OPPO ColorOS等主流Android系统上的进程保活效果,揭示了各厂商系统的差异化限制。通过详细的适配方案和代码示例,帮助开发者解决后台任务调度难题,提升应用在碎片化Android生态中的稳定性与兼容性。

weixin_30480075的博客 455

Android JobService和JobScheduler 原理剖析

请支持原创~~ 版本基于:Android R 前言 最近在研究Iorap 的时候碰到了JobScheduler,这套机制是在Android L中就提出来的,而之前没有使用到也就不知其存在。简单过滤了下framework 的代码,发现虽然是一个小模块,却是五脏俱全、环环相扣。由于时间原因,本文主要结合实际情形简单分析JobScheduler 的实现过程。 框架图 本图来源:https://blog.csdn.net/allisonchen/article/details/792...

私房菜 4340

Transformer数学推导——Q38 旋转位置编码(RoPE)的复数形式 对注意力分数的几何影响分析

RoPE 的复数形式为自然语言处理中的位置编码提供了创新且有效的解决方案。通过深入的数学理论分析,明确了其对注意力分数的几何影响及在捕捉位置信息方面的优势。在 LLM 的实际应用中,RoPE 复数形式在多个任务和场景中展现出强大的性能,特别是在处理长序列文本时,能够有效捕捉长距离依赖、缓解位置编码局限性并提升处理效率。然而,RoPE 复数形式也面临计算复杂度高、超参数调整困难和位置信息平衡等挑战。通过合理的优化策略和与其他技术的结合,有望进一步提升其性能。

墨顿随笔 1419

SpringBoot 自定义注解来实现动态切换数据源

/ 初始化数据源@Override@Inherited@Pointcut(“@annotation(包名.aop.DataSource)”) public void@Pointcut 注解定义了一个切点,名称为 dynamicDataSource。@annotation(包名.aop.DataSource) 表示这个切点匹配所有带有 @DataSource 注解的方法。

晴天飛 雪的博客 1838

国民技术N32G45X低功耗问题之更改全局变量和栈在RAM中的地址

最近项目中用的MCU是国民技术的N32G45X,MCU的资源很丰富,RAM也很大,配套的库和例程也比较全,用起来还是不错的,但是在调试低功耗的时候还是费了不少劲。 首先由于项目低功耗需要,只能选择停机模式2(STOP2)。根据数据手册的描述,在此模式下外部低速时钟开启, RTC运行, R-SRAM保持,所有I/O状态保持,独立看门狗处于关闭状态,25℃的条件下电流为10个微安。 用的这款MCU的RAM是144KB,分为SRAM(128KB)和R-SRAM( Retention SRAM – 16KB),如果

狂想家 2486

pentaho-kettle依赖拉取失败?试试这个Maven仓库配置的终极解决方案

本文详细解析了Pentaho Kettle依赖拉取失败的常见原因,并提供了三种Maven仓库配置方案,包括基础pom.xml配置、全局settings.xml设置以及企业级私有镜像仓库搭建。通过实战验证的方法和常见问题排查指南,帮助开发者彻底解决pentaho-kettle依赖问题,提升项目构建效率。

weixin_30275415的博客 205

7、Python高光谱分析工具(PyHAT)简介

Python高光谱分析工具(PyHAT)是一个开源、跨平台的Python库及图形用户界面,用于行星光谱数据的可视化与分析。它支持多种高光谱传感器数据,如火星勘测轨道飞行器的CRISM、月船1号的M3以及SELENE Kaguya SP点光谱仪,提供预处理、衍生产品生成、统计学习和机器学习分析功能。PyHAT基于Pandas和NumPy构建,兼容Jupyter和QGIS环境,旨在解决现有光谱分析方法缺乏开源实现、难以探索性分析和算法分散的问题。其模块化架构涵盖数据输入/输出、通用光谱变换、测绘与点观测工具及高

aa123的博客 139

海康DS-7604N-K1/RTB固件包

【摘要】海康DS-7604N-K1/RTB萤石云设备发布V4.30.091版本升级程序(2022.09.19),仅适用于指定型号。升级前需先升级至V3.4.106基础版本,刷机会重置设备为未激活状态,需重新设置admin密码。该机型支持密码/手势登录,无密码时可通过U盘升级。注意事项:升级存在风险,需确认程序适用性;升级过程严禁断电;必须阅读压缩包内的刷机说明。此为定制机型,非必要不建议升级。(150字)

海康威视技术支持 1577

矿石生成(1)

这个提供了标准化的矿石放置配置模板简化了常见和稀有矿石的配置过程遵循了Minecraft原版的世界生成模式提高了代码的可读性和可维护性通过使用这个类,模组开发者可以更轻松地配置矿石的生成规则,而不需要每次都手动编写完整的放置修饰符列表。这种设计模式在原版Minecraft和其他大型模组中也很常见,是一种良好的编程实践。// 导入语句...// 字段和方法...包声明表明这个类位于luckymod模组的WorldGen包中,专门处理世界生成相关配置。导入语句。

qq_24992377的博客 137

4.DJI-PSDK云台x-port控制:

使用PSDK 的“云台控制”功能,开发者需要先设计负载设备的云台并开发出控制云台的程序,将云台的控制函数注册到PSDK 指定的接口后,用户通过使用DJI Pilot、基于MSDK 开发的移动端App 及遥控器即可控制基于PSDK 开发的具有云台功能的负载设备,同时获得负载设备的相关信息,如姿态等。云台的姿态如 图2.云台姿态 所示,根据用户的控制指令,云台能够调整姿态;云台的关节如 图1.云台关节 所示,云台关节是云台上带动负载设备转动的结构件:云台电机,云台关节角即云台电机转动的角度。

哒宰的博客 857

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

上一篇: Android 9.0 JobScheduler(二) JobScheduler框架结构简述及JobSchedulerService的启动
下一篇: Android 9.0 JobScheduler(四) Job约束条件的控制
FightFightFight
博客等级 码龄11年 508粉丝 174原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值