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 JobScheduler
        val 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 运行时系统允许最大超时时长
true 30分钟
true 回调即停止
false N/A 10 分钟
package com.fadi.blockjob

import 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: Context

    companion object {
        val FORGROUND_GUARD_ID = 0x22
        val 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? = null

            notificationChannel = NotificationChannel(
                CHANNEL_ONE_ID,
                CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_LOW
            )

            nb.setChannelId(CHANNEL_ONE_ID)

            val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            manager.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 {
        //连接后,创建一个输入流来读取response
        var bufferedReader = BufferedReader(InputStreamReader(_is,"utf-8"))
        var line = ""
        var stringBuilder = StringBuilder();
        var response = "";
        //每次读取一行,若非空则添加至 stringBuilder
        while (true) {
            line = bufferedReader.readLine() ?: break //当有内容时读取一行数据,否则退出循环
            stringBuilder.append(line)
        }
        //读取所有的数据后,赋值给 response
        response = 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.
  • 8
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

法迪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值