JobScheduler 调用导致的运行时长30分钟的功耗问题

2024-09-07 23:20

本文主要是介绍JobScheduler 调用导致的运行时长30分钟的功耗问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、SDK 的使用情况与功耗影响

案例是否导致功耗变大
onStartJob return true 且子线程没有调用jobFinished()告知系统功耗变大,最长带来30分钟的partial wakelock 长持锁
onStartJob return true 且子线程调用jobFinished()告知系统功耗有影响,主要线程执行时长,标准是30秒内
onStartJob return false正常,同时系统会设定最长10分钟的超时倒计时监控运行时长,时间一到强制停止Job
不断设置setOverrideDeadline实现超过5分钟的间隔功耗有影响
设置多个job id功耗有影响,超过一定数量(100个)系统会强退该应用

最长带来30分钟的partial wakelock 长持锁的原因:
系统为了给onStartJob返回true的任务更灵活的运行时长确保不因系统休眠任务而中断休眠,会申请2把锁让CPU无法休眠

  • JobConcurrencyManager:会申请局部变量的wakelock锁(类型PARTIAL_WAKE_LOCK),这个会很快释放
  • JobServiceContext:会申请全局变量的wakelock锁(类型PARTIAL_WAKE_LOCK),这个锁会带来Job运行期最小10分钟(DEFAULT_RUNTIME_MIN_GUARANTEE_MS),最大30分钟(DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)的唤醒时长

例如:

/**
* 功耗异常持锁日志:主要观察持锁时长为 30 min (由 DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS 定义)
* +18h28m28s835ms (2) 063 +job=u0a469:"com.fadi.blockjob/.GuardJobService"
* +18h28m30s075ms (2) 063 -job=u0a469:"com.fadi.blockjob/.GuardJobService"
* +18h31m33s982ms (2) 064 +job=u0a469:"com.fadi.blockjob/.GuardJobService"
* +18h32m34s448ms (2) 064 +longwake=u0a469:"*job*、/com.fadi.blockjob/.GuardJobService"
* +18h53m16s038ms (2) 066 -job=u0a469:"com.fadi.blockjob/.BlockJobService"
* JobConcurrencyManager:会申请局部变量的wakelock锁(类型PARTIAL_WAKE_LOCK),这个会很快释放
* JobServiceContext:会申请全局变量的wakelock锁(类型PARTIAL_WAKE_LOCK),这个时最长30分钟的锁
*/

二、Job 运行时长的SDK 使用案例

2.1 设置Job

重点看:setOverrideDeadline

object JobUtils {fun startGuardJobService(context: Context): Int {val jobScheduler: JobScheduler =context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobSchedulerval builder: JobInfo.Builder = JobInfo.Builder(88,ComponentName(context.packageName, GuardJobService::class.java.name))builder.setMinimumLatency(10000) // 最小倒计时时长builder.setOverrideDeadline(600_000) // 最大倒计时长return jobScheduler.schedule(builder.build())}
}

2.2 Job触发事件的子线程耗时处理

重点关注:onStartJob 的返回值 和 jobFinished 有没有被调用

功耗现象:

onStartJob的返回值是否调用jobFinished运行时系统允许最大超时时长
true30分钟
true回调即停止
falseN/A10 分钟
package com.fadi.blockjobimport android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.job.JobParameters
import android.app.job.JobService
import android.content.Context
import android.util.Log
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL/*** 耗电Job demo 示例*/
class GuardJobService : JobService(){lateinit var mContext: Contextcompanion object {val FORGROUND_GUARD_ID = 0x22val HIGHT_POWER = true}override fun onCreate() {super.onCreate()Log.d("shz_GuardJobService", " onCreate")startMyForeground()mContext = this}override fun onDestroy() {super.onDestroy()stopForeground(true)}/*** java.lang.RuntimeException:* 写了一个Job的流氓应用,注册100个Job(相同JOB_ID算同一个,需要变更JOB_ID才可以有效计算),触发一个应用最大限额100个(华为100,Pixcel 150),直接被强退处理。* 由于统计Job次数统计是写在内存中,且应用死亡或时间变更也是不更新次数,故只能重启才能继续使用流氓应用。* java.lang.RuntimeException:Unable to create service com.fadi.blockjob.GuardJobService:java.lang.IllegalStateException: Apps may not schedule more than 100 distinct jobs*/override fun onStartJob(params: JobParameters?): Boolean {Log.d("shz_GuardJobService", "============= onStartJob beginning=============")val mThread = Thread()mThread.run {Log.d("shz_GuardJobService", "donging start")for (i in 0..200) {sendRequestWithHttpClient() // 执行联网耗时任务}Log.d("shz_GuardJobService", "donging end")/*** onStartJob return ture 时必须在耗时任务完成时调用 jobFinished,及时通知系统释放Job唤醒锁** 当任务完成后,手动调用这个方法通知系统任务完成,然后系统释放相应的wakelock锁,** 第1个参数是 onStartJob 传来的参数.* 第2个参数表示是否尝试回滚策略,如果是不得以要执行的这个方法,true表示按构造时指定的回滚策略重新安排.** 默认的回滚策略不会让任务在系统睡眠期间执行,而只是把它重新添加到任务队列中,在系统修整维护期间才执行这个任务.**/if (HIGHT_POWER) {/*** 功耗异常持锁日志:主要观察持锁时长为 30 min* +18h28m28s835ms (2) 063 +job=u0a469:"com.fadi.blockjob/.GuardJobService"* +18h28m30s075ms (2) 063 -job=u0a469:"com.fadi.blockjob/.GuardJobService"* +18h31m33s982ms (2) 064 +job=u0a469:"com.fadi.blockjob/.GuardJobService"* +18h32m34s448ms (2) 064 +longwake=u0a469:"*job*、/com.fadi.blockjob/.GuardJobService"* +18h53m16s038ms (2) 066 -job=u0a469:"com.fadi.blockjob/.BlockJobService"*/Log.d("shz_GuardJobService", "ignore execute jobFinished that cause high power")} else {/*** 正常持锁日志:* +18h28m28s835ms (2) 063 +job=u0a469:"com.fadi.blockjob/.GuardJobService"* +18h28m30s075ms (2) 063 -job=u0a469:"com.fadi.blockjob/.GuardJobService"*/jobFinished(params, false)Log.d("shz_GuardJobService", "call jobFinished")}}mThread.start()Log.d("shz_GuardJobService", "============= onStartJob ending=============")/*** 返回true表示任务在手动调用jobFinished结束或系统条件不满足而停止前一直在活跃状态,* 耗电风险点:服务断续运行,这时系统为这个任务保留wakelock锁.直到jobFinished或onStopJob调用.** 返回false表示任务正常结束,这时系统会释放与这个任务关联的wakelock锁.** 如果任务简短并且同步的那么应该返回false,如果异步的应用在任务完成后手动调用jobFinished*/return true // ture 则等待子线程完成工作}override fun onStopJob(params: JobParameters?): Boolean {Log.d("shz_GuardJobService", "********* onStopJob beginning************")val result = JobUtils.startGuardJobService(mContext)Log.d("shz_GuardJobService", "SchedulerSettings result = $result")Log.d("shz_GuardJobService", "********* onStopJob ending************")/*** 返回true表示你还希望在按照构造里指定的重试策略重试,当这个任务里有多条工作内容时,** 要返回true,表示这个任务需要重新布置执行未完成的工作..* 返回false表示结束不重试,但是不管返回什么,当前这个任务必需停止.*/return true// 调用JobSchdeuler.cancel会触发onStopJob回调}private fun startMyForeground() {val nb = Notification.Builder(this)if (android.os.Build.VERSION.SDK_INT >= 26) {val CHANNEL_ONE_ID = "channel_id_foreground"val CHANNEL_ONE_NAME = "Channel One"var notificationChannel: NotificationChannel? = nullnotificationChannel = NotificationChannel(CHANNEL_ONE_ID,CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_LOW)nb.setChannelId(CHANNEL_ONE_ID)val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManagermanager.createNotificationChannel(notificationChannel)}nb.setSmallIcon(R.mipmap.ic_launcher)nb.setContentTitle("Block guard job")nb.setContentText("Block guard Job notification")try {startForeground(FORGROUND_GUARD_ID, nb.build())} catch (e: Exception) {e.printStackTrace()}}private fun sendRequestWithHttpClient() {try {//定义地址val url = URL("https://chaoshi.tmall.com/")//打开连接val http: HttpURLConnection = url.openConnection() as HttpURLConnection//得到连接状态val nRC: Int = http.getResponseCode()if (nRC == HttpURLConnection.HTTP_OK) {//取得数据val _is: InputStream = http.getInputStream()val netInfo = is2String(_is)//Log.d("GuardJobService", "sendRequestWithHttpClient: $netInfo")}} catch (e: Exception) {e.printStackTrace()}}fun  is2String(_is: InputStream): String {//连接后,创建一个输入流来读取responsevar bufferedReader = BufferedReader(InputStreamReader(_is,"utf-8"))var line = ""var stringBuilder = StringBuilder();var response = "";//每次读取一行,若非空则添加至 stringBuilderwhile (true) {line = bufferedReader.readLine() ?: break //当有内容时读取一行数据,否则退出循环stringBuilder.append(line)}//读取所有的数据后,赋值给 responseresponse = stringBuilder.toString().trim()return response}
}

三、为什么系统运行Job最长执行30分钟呢?

3.1 日志现象

/**
* 功耗异常持锁日志:主要观察持锁时长: 30 min
* +18h28m28s835ms (2) 063 +job=u0a469:"com.fadi.blockjob/.GuardJobService"
* +18h28m30s075ms (2) 063 -job=u0a469:"com.fadi.blockjob/.GuardJobService"
* +18h31m33s982ms (2) 064 +job=u0a469:"com.fadi.blockjob/.GuardJobService"
* +18h32m34s448ms (2) 064 +longwake=u0a469:"*job*、/com.fadi.blockjob/.GuardJobService"
* +18h53m16s038ms (2) 066 -job=u0a469:"com.fadi.blockjob/.BlockJobService"
*/

3.2 Job 运行分析

事件执行时序

#### 3.2.1 App->>JobScheduler:schedule()
#### 3.2.2 JobScheduler->>JobSchedulerService:scheduleAsPackage()
#### 3.2.3 JobSchedulerService->>JobSchedulerService:maybeRunPendingJobsLocked()
#### 3.2.4 JobSchedulerService->>JobConcurrencyManager:assignJobsToContextsLocked()
#### 3.

这篇关于JobScheduler 调用导致的运行时长30分钟的功耗问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1146458

相关文章

解决Java中基于GeoTools的Shapefile读取乱码的问题

《解决Java中基于GeoTools的Shapefile读取乱码的问题》本文主要讨论了在使用Java编程语言进行地理信息数据解析时遇到的Shapefile属性信息乱码问题,以及根据不同的编码设置进行属... 目录前言1、Shapefile属性字段编码的情况:一、Shp文件常见的字符集编码1、System编码

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

Java终止正在运行的线程的三种方法

《Java终止正在运行的线程的三种方法》停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作,停止一个线程可以用Thread.stop()方法,但最好不要用它,本文给大家介绍了... 目录前言1. 停止不了的线程2. 判断线程是否停止状态3. 能停止的线程–异常法4. 在沉睡中停止5

Redis解决缓存击穿问题的两种方法

《Redis解决缓存击穿问题的两种方法》缓存击穿问题也叫热点Key问题,就是⼀个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,本文给大家介绍了Re... 目录引言解决办法互斥锁(强一致,性能差)逻辑过期(高可用,性能优)设计逻辑过期时间引言缓存击穿:给

Vue 调用摄像头扫描条码功能实现代码

《Vue调用摄像头扫描条码功能实现代码》本文介绍了如何使用Vue.js和jsQR库来实现调用摄像头并扫描条码的功能,通过安装依赖、获取摄像头视频流、解析条码等步骤,实现了从开始扫描到停止扫描的完整流... 目录实现步骤:代码实现1. 安装依赖2. vue 页面代码功能说明注意事项以下是一个基于 Vue.js

Java程序运行时出现乱码问题的排查与解决方法

《Java程序运行时出现乱码问题的排查与解决方法》本文主要介绍了Java程序运行时出现乱码问题的排查与解决方法,包括检查Java源文件编码、检查编译时的编码设置、检查运行时的编码设置、检查命令提示符的... 目录一、检查 Java 源文件编码二、检查编译时的编码设置三、检查运行时的编码设置四、检查命令提示符

Jackson库进行JSON 序列化时遇到了无限递归(Infinite Recursion)的问题及解决方案

《Jackson库进行JSON序列化时遇到了无限递归(InfiniteRecursion)的问题及解决方案》使用Jackson库进行JSON序列化时遇到了无限递归(InfiniteRecursi... 目录解决方案‌1. 使用 @jsonIgnore 忽略一个方向的引用2. 使用 @JsonManagedR

关于Docker Desktop的WSL报错问题解决办法

《关于DockerDesktop的WSL报错问题解决办法》:本文主要介绍关于DockerDesktop的WSL报错问题解决办法的相关资料,排查发现是因清理%temp%文件夹误删关键WSL文件,... 目录发现问题排查过程:解决方法其实很简单:重装之后再看就能够查到了:最后分享几个排查这类问题的小www.cp

SpringBoot利用dynamic-datasource-spring-boot-starter解决多数据源问题

《SpringBoot利用dynamic-datasource-spring-boot-starter解决多数据源问题》dynamic-datasource-spring-boot-starter是一... 目录概要整体架构构想操作步骤创建数据源切换数据源后续问题小结概要自己闲暇时间想实现一个多租户平台,

VSCode中C/C++编码乱码问题的两种解决方法

《VSCode中C/C++编码乱码问题的两种解决方法》在中国地区,Windows系统中的cmd和PowerShell默认编码是GBK,但VSCode默认使用UTF-8编码,这种编码不一致会导致在VSC... 目录问题方法一:通过 Code Runner 插件调整编码配置步骤方法二:在 PowerShell