Если вам необходимо выполнить передачу данных, которая может занять много времени, вы можете создать задание JobScheduler и указать его как задание передачи данных, инициированное пользователем (UIDT) . Задания UIDT предназначены для длительных передач данных, инициированных пользователем устройства, например, для загрузки файла с удаленного сервера. Задания UIDT были введены в Android 14 (уровень API 34).
Задания по передаче данных, инициированные пользователем, запускаются самим пользователем. Для выполнения этих заданий требуется уведомление, они запускаются немедленно и могут выполняться в течение длительного времени в зависимости от условий системы. Можно одновременно запускать несколько заданий по передаче данных, инициированных пользователем.
Задания, инициированные пользователем, должны планироваться в то время, когда приложение видно пользователю (или в одном из допустимых условий ). После выполнения всех ограничений задания, инициированные пользователем, могут быть выполнены операционной системой с учетом ограничений, связанных со здоровьем системы. Система также может использовать предоставленный расчетный размер полезной нагрузки для определения продолжительности выполнения задания.
Планирование заданий по передаче данных, инициируемых пользователем
如需运行用户发起的数据传输作业,请执行以下操作:
确保您的应用已在其清单中声明
JobService和关联的 权限:<service android:name="com.example.app.CustomTransferService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false"> ... </service>此外,还要为数据传输定义
JobService的具体子类:Kotlin
class CustomTransferService : JobService() { ... }
Java
class CustomTransferService extends JobService() { .... }
在清单中声明
RUN_USER_INITIATED_JOBS权限:<manifest ...> <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" /> <application ...> ... </application> </manifest>构建
JobInfo对象时,调用setUserInitiated()方法。(此方法从 Android 14 开始提供。) 我们还建议您在创建作业时通过调用setEstimatedNetworkBytes()提供载荷大小 估算值 。Kotlin
val networkRequestBuilder = NetworkRequest.Builder() // Add or remove capabilities based on your requirements. // For example, this code specifies that the job won't run // unless there's a connection to the internet (not just a local // network), and the connection doesn't charge per-byte. .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_METERED) .build() val jobInfo = JobInfo.Builder(jobId, ComponentName(mContext, CustomTransferService::class.java)) // ... .setUserInitiated(true) .setRequiredNetwork(networkRequestBuilder) // Provide your estimate of the network traffic here .setEstimatedNetworkBytes(1024 * 1024 * 1024, 1024 * 1024 * 1024) // ... .build()
Java
NetworkRequest networkRequest = new NetworkRequest.Builder() // Add or remove capabilities based on your requirements. // For example, this code specifies that the job won't run // unless there's a connection to the internet (not just a local // network), and the connection doesn't charge per-byte. .addCapability(NET_CAPABILITY_INTERNET) .addCapability(NET_CAPABILITY_NOT_METERED) .build(); JobInfo jobInfo = JobInfo.Builder(jobId, new ComponentName(mContext, CustomTransferService.class)) // ... .setUserInitiated(true) .setRequiredNetwork(networkRequest) // Provide your estimate of the network traffic here .setEstimatedNetworkBytes(1024 * 1024 * 1024, 1024 * 1024 * 1024) // ... .build();
执行作业时,对
JobService对象调用setNotification()。调用setNotification()会在任务管理器和状态栏通知区域中告知用户作业正在运行。执行完成后,调用
jobFinished()以向系统表明作业已完成,或者应重新调度作业。Kotlin
class CustomTransferService: JobService() { private val scope = CoroutineScope(Dispatchers.IO) @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) override fun onStartJob(params: JobParameters): Boolean { val notification = Notification.Builder(applicationContext, NOTIFICATION_CHANNEL_ID) .setContentTitle("My user-initiated data transfer job") .setSmallIcon(android.R.mipmap.myicon) .setContentText("Job is running") .build() setNotification(params, notification.id, notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH) // Execute the work associated with this job asynchronously. scope.launch { doDownload(params) } return true } private suspend fun doDownload(params: JobParameters) { // Run the relevant async download task, then call // jobFinished once the task is completed. jobFinished(params, false) } // Called when the system stops the job. override fun onStopJob(params: JobParameters?): Boolean { // Asynchronously record job-related data, such as the // stop reason. return true // or return false if job should end entirely } }
Java
class CustomTransferService extends JobService{ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) @Override public boolean onStartJob(JobParameters params) { Notification notification = Notification.Builder(getBaseContext(), NOTIFICATION_CHANNEL_ID) .setContentTitle("My user-initiated data transfer job") .setSmallIcon(android.R.mipmap.myicon) .setContentText("Job is running") .build(); setNotification(params, notification.id, notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH) // Execute the work associated with this job asynchronously. new Thread(() -> doDownload(params)).start(); return true; } private void doDownload(JobParameters params) { // Run the relevant async download task, then call // jobFinished once the task is completed. jobFinished(params, false); } // Called when the system stops the job. @Override public boolean onStopJob(JobParameters params) { // Asynchronously record job-related data, such as the // stop reason. return true; // or return false if job should end entirely } }
定期更新通知,让用户了解作业的状态和进度。如果在安排作业之前无法确定传输大小,或者需要更新估算的传输大小,请在知道传输大小之后使用新的 API
updateEstimatedNetworkBytes()更新传输大小。
建议
如需有效地运行 UIDT 作业,请执行以下操作:
明确定义网络限制和作业执行限制,以指定应何时执行作业。
在
onStartJob()中异步执行任务;例如,您可以使用 协程 来执行此操作。如果您不异步运行任务,则工作会在主线程上运行,并且可能会阻塞主线程,从而导致 ANR。为避免作业运行时间超出必要时长,请在传输完成时(无论成功还是失败)调用
jobFinished()。这样,作业就不会运行超出必要时长。如需了解作业停止的原因,请实现onStopJob()回调方法并调用JobParameters.getStopReason()。
Обратная совместимость
目前还没有支持 UIDT 作业的 Jetpack 库。因此,我们建议您使用代码来限制更改,以验证您是否在 Android 14 或更高版本上运行。在较低的 Android 版本中,您可以将 WorkManager 的前台服务实现用作回退方法。
以下是检查相应系统版本的代码示例:
Kotlin
fun beginTask() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { scheduleDownloadFGSWorker(context) } else { scheduleDownloadUIDTJob(context) } } private fun scheduleDownloadUIDTJob(context: Context) { // build jobInfo val jobScheduler: JobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler jobScheduler.schedule(jobInfo) } private fun scheduleDownloadFGSWorker(context: Context) { val myWorkRequest = OneTimeWorkRequest.from(DownloadWorker::class.java) WorkManager.getInstance(context).enqueue(myWorkRequest) }
Java
public void beginTask() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { scheduleDownloadFGSWorker(context); } else { scheduleDownloadUIDTJob(context); } } private void scheduleDownloadUIDTJob(Context context) { // build jobInfo JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); jobScheduler.schedule(jobInfo); } private void scheduleDownloadFGSWorker(Context context) { OneTimeWorkRequest myWorkRequest = OneTimeWorkRequest.from(DownloadWorker.class); WorkManager.getInstance(context).enqueue(myWorkRequest) }
Остановить задания UIDT
用户和系统都可以停止用户发起的传输作业。
Пользователь, из диспетчера задач
用户可以停止显示在任务管理器中的用户发起的传输作业。
在用户按 Stop 时,系统会执行以下操作:
- 立即终止应用的进程,包括正在运行的所有其他作业或前台服务。
- 不针对任何正在运行的作业调用
onStopJob()。 - 阻止重新调度用户可见的作业。
因此,建议在发布的作业通知中提供控件,以便顺利停止和重新调度作业。
请注意,在特殊情况下,Stop 按钮不会显示在任务管理器中的作业旁边,或者该作业根本不会显示在任务管理器中。
По системе
В отличие от обычных заданий, на задания по передаче данных, инициированные пользователем, не влияют квоты резервных сегментов приложений . Однако система по-прежнему останавливает задание при возникновении любого из следующих условий:
- Ограничение, определенное разработчиком, больше не соблюдается.
- Система определяет, что задание выполнялось дольше, чем необходимо для завершения задачи передачи данных.
- Системе необходимо уделять первоочередное внимание работоспособности системы и останавливать задания из-за повышенного теплового состояния.
- Процесс приложения завершается из-за нехватки памяти устройства.
Когда задание останавливается системой по причинам , отличным от нехватки памяти устройства, система вызывает onStopJob() и повторяет выполнение задания в момент, который система считает оптимальным. Убедитесь, что ваше приложение может сохранять состояние передачи данных, даже если onStopJob() не вызывается, и что ваше приложение может восстановить это состояние при повторном вызове onStartJob() .
Условия, разрешенные для планирования заданий по передаче данных, инициируемых пользователем
Приложения могут запускать инициированное пользователем задание по передаче данных только в том случае, если приложение находится в видимом окне или выполняются определенные условия:
- Если приложение может запускать действия в фоновом режиме , оно также может запускать в фоновом режиме инициируемые пользователем задания по передаче данных.
- Если в приложении есть действие в стеке существующей задачи на экране «Последние» , это само по себе не позволяет выполнить задание по передаче данных, инициированное пользователем.
Если выполнение задания запланировано на момент, когда необходимые условия не выполняются, задание завершается с ошибкой и возвращается код ошибки RESULT_FAILURE .
Ограничения, разрешенные для заданий передачи данных, инициируемых пользователем
Для обеспечения оптимального выполнения задач Android предоставляет возможность назначать ограничения каждому типу задач. Эти ограничения доступны начиная с Android 13.
Примечание : В приведенной ниже таблице сравниваются только ограничения, различающиеся для каждого типа заданий. Для получения информации обо всех ограничениях см. страницу разработчика JobScheduler или раздел «Ограничения заданий» .
В таблице ниже показаны различные типы заданий, поддерживающие заданное ограничение задания, а также набор ограничений заданий, поддерживаемых WorkManager. Используйте строку поиска перед таблицей, чтобы отфильтровать таблицу по имени метода ограничения задания.
Вот ограничения, допустимые для заданий по передаче данных, инициируемых пользователем:
-
setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL) -
setClipData() -
setEstimatedNetworkBytes() -
setMinimumNetworkChunkBytes() -
setPersisted() -
setNamespace() -
setRequiredNetwork() -
setRequiredNetworkType() -
setRequiresBatteryNotLow() -
setRequiresCharging() -
setRequiresStorageNotLow()
Тестирование
下面列出了有关如何手动测试应用作业的一些步骤:
- 如需获取作业 ID,请获取在构建作业时定义的值。
如需立即运行作业或重试已停止的作业,请在终端窗口中运行以下命令:
adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
如需模拟系统强行停止作业(由于系统运行状况或超出配额条件),请在终端窗口中运行以下命令:
adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID
См. также
Дополнительные ресурсы
Дополнительные сведения о передаче данных, инициируемой пользователем, см. в следующих дополнительных ресурсах:
- Практический пример интеграции UIDT: Карты Google повысили надежность загрузки на 10 % благодаря API передачи данных, инициируемому пользователем.