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

and*_*per 42 android android-workmanager android-12

背景

以前我都是用一个前台的IntentService来处理纷至沓来的各种事件。然后当 Android 11 到来时(Android R,API 30)它就被弃用了,据说更喜欢使用使用setForegroundAsync 的Worker ,所以我就这么做了。

val builder = NotificationCompat.Builder(context,...)...
setForegroundAsync(ForegroundInfo(notificationId, builder.build()))
Run Code Online (Sandbox Code Playgroud)

问题

随着 Android 12 的到来(Android S,API 31),仅在之后的一个版本,现在setForegroundAsync被标记为已弃用,并且我被告知要使用setExpedited来代替:

* @deprecated Use {@link WorkRequest.Builder#setExpedited(OutOfQuotaPolicy)} and
* {@link ListenableWorker#getForegroundInfoAsync()} instead.
Run Code Online (Sandbox Code Playgroud)

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

在 Android S 之前,WorkManager 代表您管理和运行前台服务来执行 WorkRequest,显示 ForegroundInfo 中提供的通知。要随后更新此通知,应用程序可以使用NotificationManager。

从 Android S 及更高版本开始,WorkManager 使用即时作业来管理此 WorkRequest。

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

从 WorkManager 2.7.0 开始,您的应用程序可以调用 setExpedited() 来声明 Worker 应使用加急作业。这个新 API 在 Android 12 上运行时使用加急作业,并且该 API 使用以前版本的 Android 上的前台服务来提供向后兼容性。

他们唯一的片段是调度本身:

OneTimeWorkRequestBuilder<T>().apply {
    setInputData(inputData)
    setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
}.build()
Run Code Online (Sandbox Code Playgroud)

即使OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST的文档也看起来很奇怪:

当应用没有任何加急作业配额时,加急工作请求将回退到常规工作请求。

我想要的只是立即、可靠地、不间断地(或至少尽可能降低机会)做某事,并使用操作系统提供的任何内容来完成它。

我不明白为什么setExpedited创建。

问题

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

Nek*_*cer 15

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

广告 2. 每个应用程序都会获得一些不能超过的配额。此配额的详细信息是(并且可能始终是)OEM 内部实施细节。至于达到配额——这正是该OutOfQuotaPolicy标志的用途。您可以设置,如果您的应用程序达到其配额,作业可以像正常作业一样运行,也可以被删除

ad 3. 这是一个谜。在这里我们可以找到模糊的说法:

Android 12 中新增的加急作业允许应用执行简短而重要的任务,同时让系统更好地控制对资源的访问。这些作业具有介于前台服务和常规 JobScheduler 作业之间的一组特征

这(据我理解)意味着工作经理将仅适用于短期运行的工作。因此,这似乎不会是从后台启动长期运行作业的任何官方方式。这(我的观点)很疯狂,但是嘿 - 就是这样。

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

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

广告 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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • `- 我不确定如果您不覆盖它会发生什么`应用程序将崩溃,并显示一条消息“getForegroundInfo”未实现 (14认同)
  • @BogdanStolyarov 啊,另一个很棒的文档怪癖,很高兴知道.. (2认同)

key*_*fer 14

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

OneTimeWorkRequest由于时间敏感,因此只能快速运行。任何 Worker 都可以请求在前台运行。这可能会成功,具体取决于应用程序的前台状态。

从 WorkManager 2.7 开始,AWorker可以尝试在前台运行其工作:setForeground[Async]()

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()
        }
    }
}


Run Code Online (Sandbox Code Playgroud)

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

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


WorkManager.getInstance(context)
    .enqueue(request)
Run Code Online (Sandbox Code Playgroud)

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

何时使用前台服务和加急作业的图表

  • 该图来自 WorkManager 上的 Droidcon London 会议。我们很快就会将其添加到 d.android.com。 (2认同)