setExpedited 如何工作?它是否与前台服务一样好且可靠?

How does setExpedited work and is it as good&reliable as a foreground service?

背景

以前我用前景IntentService to handle various events that come one after another. Then it was deprecated when Android 11 came (Android R, API 30) and it was said to prefer to use Worker that uses setForegroundAsync代替,所以我就这样做了。

val builder = NotificationCompat.Builder(context,...)...
setForegroundAsync(ForegroundInfo(notificationId, builder.build()))

问题

As Android 12 came (Android S, API 31), 之后只有一个版本,现在 setForegroundAsync 被标记为已弃用,我被告知使用 setExpedited 代替:

* @deprecated Use {@link WorkRequest.Builder#setExpedited(OutOfQuotaPolicy)} and
* {@link ListenableWorker#getForegroundInfoAsync()} instead.

事实上,我不确定它是如何工作的。之前,我们有一个通知应该显示给用户,因为它正在使用前台服务(至少对于“旧”Android 版本)。现在看来 getForegroundInfoAsync 用于它,但我认为它不会用于 Android 12 :

Prior to Android S, WorkManager manages and runs a foreground service on your behalf to execute the WorkRequest, showing the notification provided in the ForegroundInfo. To update this notification subsequently, the application can use NotificationManager.

Starting in Android S and above, WorkManager manages this WorkRequest using an immediate job.

关于它的另一个线索是 (here) :

Starting in WorkManager 2.7.0, your app can call setExpedited() to declare that a Worker should use an expedited job. This new API uses expedited jobs when running on Android 12, and the API uses foreground services on prior versions of Android to provide backward compatibility.

他们拥有的唯一片段是日程安排本身:

OneTimeWorkRequestBuilder<T>().apply {
    setInputData(inputData)
    setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
}.build()

甚至 OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST 的文档看起来也很奇怪:

When the app does not have any expedited job quota, the expedited work request will fallback to a regular work request.

我想要的只是立即、可靠、不间断地做某事(或至少尽可能少的机会),并使用 OS 提供的任何东西来做。

我不明白为什么要创建 setExpedited

问题

  1. setExpedited 是如何工作的?
  2. 这个“加急工作配额”是什么? Android12 到了这种情况会发生什么?工作人员不会立即完成工作吗?
  3. setExpedited 是否与前台服务一样可靠?它总是能够立即启动吗?
  4. setExpedited有哪些优点、缺点和限制?为什么我应该更喜欢它而不是前台服务?
  5. 这是否意味着当应用程序在 Android 12 上使用此 API 时,用户将看不到任何内容?

ad 1. 我无法准确解释(没有查看代码),但从我们(开发人员)的角度来看,它就像一个作业请求队列,将您的应用程序作业配额考虑在内。所以基本上你可以依赖它 if 你的应用程序在电池方面表现得很好(不管那意味着什么......)

ad 2. 每个应用都有 一些 不能超过的配额。此配额的详细信息是(并且可能永远是)OEM 内部实施详细信息。至于达到配额 - 这正是 OutOfQuotaPolicy 标志的用途。您可以设置如果您的应用程序达到其配额 - 作业可以 运行 作为正常作业或者可以删除

ad 3. 那是个谜。 Here 我们可以找到模糊的语句:

Expedited jobs, new in Android 12, allow apps to execute short, important tasks while giving the system better control over access to resources. These jobs have a set of characteristics somewhere in between a foreground service and a regular JobScheduler job

这(据我了解)意味着工作经理将只负责短期 运行ning 工作。因此,从后台启动长时间 运行ning 作业似乎不是任何官方方式。哪一个(我的意见)是疯狂的,但是嘿 - 它就是这样。

我们所知道的是它应该立即启动,但可能会推迟。 source

ad 4. 您不能从后台 运行 前台服务。因此,如果您需要 运行 应用程序不在前台时的“几分钟任务” - 加急作业将是在 android 中完成此任务的唯一方法 12. 想要 运行 更长的任务吗?你需要让你进入前台。它可能会要求您 运行 工作只是为了显示启动 activity 的通知。比起 activity 你可以 运行 前台服务!已经兴奋了吗?

广告 5。在 android 12 他们什么也看不到。在早期版本中,加急作业将回退到前台服务。您需要覆盖 public open suspend fun getForegroundInfo(): ForegroundInfo 才能进行自定义通知。我不确定如果你不覆盖它会发生什么。

示例用法(日志上传worker):

@HiltWorker
class LogReporterWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val loggingRepository: LoggingRepository,
) : CoroutineWorker(appContext, workerParams) {
    override suspend fun doWork(): Result {
        loggingRepository.uploadLogs()
    }

    override suspend fun getForegroundInfo(): ForegroundInfo {
        SystemNotificationsHandler.registerForegroundServiceChannel(applicationContext)
        val notification = NotificationCompat.Builder(
            applicationContext,
            SystemNotificationsHandler.FOREGROUND_SERVICE_CHANNEL
        )
            .setContentTitle(Res.string(R.string.uploading_logs))
            .setSmallIcon(R.drawable.ic_logo_small)
            .setPriority(NotificationCompat.PRIORITY_LOW)
            .build()
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            ForegroundInfo(
                NOTIFICATION_ID,
                notification,
                ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
            )
        } else {
            ForegroundInfo(
                NOTIFICATION_ID,
                notification
            )
        }
    }

    companion object {
        private const val TAG = "LogReporterWorker"
        private const val INTERVAL_HOURS = 1L
        private const val NOTIFICATION_ID = 3562

        fun schedule(context: Context) {
            val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.UNMETERED)
                .build()
            val worker = PeriodicWorkRequestBuilder<LogReporterWorker>(
                INTERVAL_HOURS,
                TimeUnit.HOURS
            )
                .setConstraints(constraints)
                .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
                .addTag(TAG)
                .build()

            WorkManager.getInstance(context)
                .enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, worker)
        }
    }
}

从概念上讲,前台服务和加急工作不是一回事。

只有 OneTimeWorkRequest 可以 运行 加急,因为它们是时间敏感的。任何 Worker 都可以请求在前台 运行。这可能会成功,具体取决于应用程序的前台状态。

A Worker 可以尝试 运行 使用 setForeground[Async]() 它在前台工作,从 WorkManager 2.7 开始就这样:

class DownloadWorker(context: Context, parameters: WorkerParameters) :
    CoroutineWorker(context, parameters) {

    override suspend fun getForegroundInfo(): ForegroundInfo {
        TODO()
    }

    override suspend fun doWork(): Result {
        return try {
            setForeground(getForegroundInfo())
            Result.success()
        } catch(e: ForegroundServiceStartNotAllowedException) {
            // TODO handle according to your use case or fail.
            Result.fail()
        }
    }
}


您可以在构建时使用 setExpedited 来请求 WorkRequest 尽快成为 运行。

val request = OneTimeWorkRequestBuilder<SendMessageWorker>()
    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
    .build()


WorkManager.getInstance(context)
    .enqueue(request)

在 Android 版本之前,12 个加急作业将 运行 作为前台服务,显示通知。在 Android 12+ 上,可能不会显示通知。