9

背景

过去,我使用前台的IntentService来处理接踵而至的各种事件。然后它在 Android 11 到来时被弃用(Android R,API 30),据说它更喜欢使用使用setForegroundAsync的 Worker,所以我这样做了。

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

问题

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

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

问题是,我不确定它是如何工作的。之前,我们有一个通知应该显示给用户,因为它正在使用前台服务(至少对于“旧”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()

甚至OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST的文档似乎也很奇怪:

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

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

我不明白为什么setExpedited要创建。

问题

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

2 回答 2

6

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

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

ad 3. 那是个谜。在这里我们可以找到模糊的陈述:

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

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

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

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

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

示例用法(日志上传工作者):

@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)
        }
    }
}
于 2021-07-21T11:36:05.327 回答
4

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

只有 aOneTimeWorkRequest可以加急运行,因为这些是时间敏感的。任何 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()
        }
    }
}


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

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


WorkManager.getInstance(context)
    .enqueue(request)

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

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

于 2021-10-29T09:45:51.307 回答