Migrar serviços em primeiro plano para jobs de transferência de dados iniciados pelo usuário

O Android 14 aplica regras rígidas sobre a possibilidade de apps usarem serviços em primeiro plano.

Além disso, estamos introduzindo no Android 14 uma nova API para especificar que um job precisa ser um job de transferência de dados iniciado pelo usuário. Essa API é útil para casos de uso que exigem uma transferência de dados iniciada pelo usuário mais longa, como o download de um arquivo de um servidor remoto. Esses tipos de tarefas precisam usar um job de transferência de dados iniciado pelo usuário.

Esses tipos de jobs de transferência de dados são iniciados pelo usuário. Eles exigem uma notificação, são iniciados imediatamente e podem ser executados por um período prolongado, conforme as condições do sistema permitirem. É possível executar vários jobs de transferência de dados iniciados pelo usuário ao mesmo tempo.

Os jobs iniciados pelo usuário precisam ser agendados enquanto o aplicativo está visível para o usuário (ou em uma das condições permitidas). Depois que todas as restrições forem atendidas, os jobs iniciados pelo usuário podem ser executados pelo SO, sujeito a restrições de integridade do sistema. O sistema também pode usar o tamanho de payload estimado para determinar o período de execução do job.

Permissão para jobs de transferência de dados iniciados pelo usuário

Os jobs de transferência de dados iniciados pelo usuário exigem uma nova permissão para serem executados: RUN_USER_INITIATED_JOBS. O sistema concede essa permissão automaticamente. Ele gera uma SecurityException quando você não declara a permissão no manifesto do app.

Processo para programar jobs de transferência de dados iniciados pelo usuário

Para executar um job iniciado pelo usuário, siga estas etapas:

  1. Se esta for a primeira vez que você declara uma API com o JobScheduler, declare a JobService e as permissões associadas no seu manifesto. Além disso, defina uma subclasse concreta de JobService para sua transferência de dados:

    <service android:name="com.example.app.CustomTransferService"
            android:permission="android.permission.BIND_JOB_SERVICE"
            android:exported="false">
            ...
    </service>
    
    class CustomTransferService : JobService() {
      ...
    }
    
  2. Declare a permissão RUN_USER_INITIATED_JOBS no manifesto:

    <manifest ...>
        <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" />
        <application ...>
            ...
        </application>
    </manifest>
    
  3. Chame o novo método setUserInitiated() ao criar um objeto JobInfo. Também é recomendável oferecer uma estimativa de tamanho de payload chamando setEstimatedNetworkBytes() ao criar seu job:

    val networkRequestBuilder = NetworkRequest.Builder()
            .addCapability(NET_CAPABILITY_INTERNET)
            .addCapability(NET_CAPABILITY_NOT_METERED)
            // Add or remove capabilities based on your requirements
            .build()
    
    val jobInfo = JobInfo.Builder()
            // ...
            .setUserInitiated(true)
            .setRequiredNetwork(networkRequestBuilder.build())
            .setEstimatedNetworkBytes(1024 * 1024 * 1024)
            // ...
            .build()
    
  4. Programe o job antes do início da transferência enquanto o aplicativo está visível ou na lista de condições permitidas:

    val jobScheduler: JobScheduler =
        context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
    jobScheduler.schedule(jobInfo)
    
  5. Quando o job estiver sendo executado, chame setNotification() no objeto JobService. Esse valor é usado para informar ao usuário que o job está em execução, tanto no gerenciador de tarefas quanto na área de notificação da barra de status:

    class CustomTransferService : JobService() {
      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)
          // Do the job execution.
      }
    }
    
  6. Atualize a notificação periodicamente para manter o usuário informado sobre o status e o progresso do job. Se não for possível determinar o tamanho da transferência antes de programar o job ou se precisar atualizar o tamanho da transferência estimado, use a nova API, updateEstimatedNetworkBytes(), para atualizar o tamanho da transferência quando ele for conhecido.

  7. Quando a execução for concluída, chame jobFinished() para sinalizar ao sistema que o job foi concluído ou que ele precisa ser reagendado.

Os jobs de transferência de dados iniciados pelo usuário podem ser interrompidos

O usuário e o sistema podem interromper jobs de transferência iniciados pelo usuário.

Pelo usuário, no gerenciador de tarefas

O usuário pode interromper um job de transferência de dados iniciado pelo usuário que aparece no Gerenciador de tarefas.

Quando o usuário pressiona Stop, o sistema:

  • encerra o processo do app imediatamente, incluindo todos os outros jobs ou serviços em execução no primeiro plano;
  • não chama onStopJob() para nenhum job em execução;
  • impede que os jobs visíveis ao usuário sejam reprogramados.

Por esses motivos, é recomendável fornecer controles na notificação postada para o job para permitir uma interrupção e reprogramação normais.

Em circunstâncias especiais, o botão Stop não aparece ao lado do job no Gerenciador de tarefas, ou então o job não é mostrado no gerenciador de tarefas.

Pelo sistema

与常规作业不同,用户发起的数据传输作业不受应用待机模式存储分区配额的影响。但是,如果出现以下任一情况,系统仍会停止作业:

  • 不再满足开发者定义的约束条件。
  • 系统确定该作业的运行时间超出了完成数据传输任务所需的时间。
  • 系统需要优先考虑系统运行状况,并因发热程度上升而停止作业。
  • 应用进程因设备内存不足而被终止。

系统停止作业(并非因为内存不足)时,系统会调用 onStopJob(),系统会在系统认为最佳的时间重试作业。检查您的应用是否可以保留数据传输状态(即使未调用 onStopJob()),并且您的应用可以在再次调用 onStartJob() 时恢复此状态。

Condições permitidas para programar jobs de transferência de dados iniciados pelo usuário

只有当应用处于可见窗口中或满足特定条件时,应用才能启动用户发起的数据传输作业。为确定何时可以安排用户发起的数据传输作业,系统会采用允许应用在特殊情况下从后台启动 activity 的一组相同条件。值得注意的是,这组条件与后台启动的前台服务限制的豁免集不同。

上述说明的例外情况包括:

  • 如果应用可以从后台启动 activity,则也可以从后台启动用户发起的数据传输作业。
  • 如果应用在最近用过屏幕上现有任务的返回堆栈中有 activity,单靠这一点并不允许运行用户发起的数据传输作业。

如果作业安排在允许的条件列表中未列出的其他时间,则作业将失败并返回 RESULT_FAILURE 错误代码。

Restrições permitidas para jobs de transferência de dados iniciados pelo usuário

To support jobs running at optimal points, Android offers the ability to assign constraints to each job type. These constraints are already available as of Android 13.

Note: The following table only compares the constraints that vary between each job type. See JobScheduler developer page or work constraints for all constraints.

The following table shows the different job types that support a given job constraint, as well as the set of job constraints that WorkManager supports. Use the search bar before the table to filter the table by the name of a job constraint method.

These are the constraints allowed with user-initiated data transfer jobs:

  • setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL)
  • setClipData()
  • setEstimatedNetworkBytes()
  • setMinimumNetworkChunkBytes()
  • setPersisted()
  • setNamespace()
  • setRequiredNetwork()
  • setRequiredNetworkType()
  • setRequiresBatteryNotLow()
  • setRequiresCharging()
  • setRequiresStorageNotLow()

Testes

下面列出了有关如何手动测试应用作业的一些步骤:

  • 如需获取作业 ID,请获取在构建作业时定义的值。
  • 如需立即运行作业或重试已停止的作业,请在终端窗口中运行以下命令:

    adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
    
  • 如需模拟系统强行停止作业(由于系统运行状况或超出配额条件),请在终端窗口中运行以下命令:

    adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID