Android 14 applies strict rules on when apps are allowed to use foreground services.
Also in Android 14 we are introducing a new API to specify that a job must be a user-initiated data transfer job. This API is helpful for use cases that require longer-duration, user-initiated transferring of data, such as downloading a file from a remote server. These types of tasks should use a user-initiated data transfer job.
User-initiated data transfer jobs are started by the user. These jobs require a notification, start immediately, and may be able to run for an extended period of time as system conditions allow. You can run several user-initiated data transfer jobs concurrently.
User initiated jobs must be scheduled while the application is visible to the user (or in one of the allowed conditions). After all constraints are met, user initiated jobs can be executed by the OS, subject to system health restrictions. The system may also use the provided estimated payload size to determine how long the job executes.
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:
Se esta é a primeira vez que você declara uma API com o JobScheduler, declare o
JobService
e as permissões associadas no manifesto. Além disso, defina uma subclasse concreta deJobService
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() { ... }
Declare a permissão
RUN_USER_INITIATED_JOBS
no manifesto:<manifest ...> <uses-permission android:name="android.permission.RUN_USER_INITIATED_JOBS" /> <application ...> ... </application> </manifest>
Chame o novo método
setUserInitiated()
ao criar um objetoJobInfo
. Também é recomendável oferecer uma estimativa de tamanho de payload chamandosetEstimatedNetworkBytes()
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()
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)
Quando o job estiver sendo executado, chame
setNotification()
no objetoJobService
. 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. } }
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 estimado da transferência, use a nova API,
updateEstimatedNetworkBytes()
, para atualizar o tamanho da transferência quando ele for conhecido.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
Unlike regular jobs, user-initiated data transfer jobs are unaffected by App Standby Buckets quotas. However, the system still stops the job if any of the following conditions occur:
- A developer-defined constraint is no longer met.
- The system determines that the job has run for longer than necessary to complete the data transfer task.
- The system needs to prioritize system health and stop jobs due to increased thermal state.
- The app process is killed due to low device memory.
When the job is stopped by the system (not by the low-memory case), the system
calls onStopJob()
, and the system retries the job at a time that the system
deems to be optimal. Check that your app can persist data transfer state, even
if onStopJob()
isn't called, and that your app can restore this state when
onStartJob()
is called again.
Condições permitidas para programar jobs de transferência de dados iniciados pelo usuário
Os apps só poderão iniciar um job de transferência de dados iniciado pelo usuário se estiverem na janela visível ou se determinadas condições forem atendidas. Para determinar quando um job de transferência de dados iniciado pelo usuário pode ser programado, o sistema aplica a mesma lista de condições que permitem que os apps iniciem uma atividade em segundo plano em casos especiais. Essa lista de condições não é igual ao conjunto de isenções para restrições de serviço em primeiro plano iniciadas em segundo plano.
Confira algumas exceções:
- Se um app puder iniciar atividades em segundo plano, ele também poderá iniciar jobs de transferência de dados iniciados pelo usuário em segundo plano.
- Se um app tiver uma atividade na backstack de uma tarefa na tela Recentes, isso, por si só, não permitirá que um job de transferência de dados iniciado pelo usuário seja executado.
Se o job estiver programado para algum outro momento não listado nas condições permitidas,
ele falhará e retornará um código de erro RESULT_FAILURE
.
Restrições permitidas para jobs de transferência de dados iniciados pelo usuário
Para oferecer suporte a jobs em execução nos pontos ideais, no Android é possível atribuir restrições a cada tipo de job. Essas restrições já estão disponíveis desde o Android 13.
Observação: a tabela a seguir compara somente as restrições que variam entre cada tipo de job. Consulte a Página do desenvolvedor do JobScheduler ou as restrições de trabalho para conferir todas as restrições.
A tabela a seguir mostra os diferentes tipos de job que aceitam determinada restrição, bem como o conjunto de restrições de jobs com suporte ao WorkManager. Use a barra de pesquisa antes da tabela para filtrá-la pelo nome de um método de restrição de job.
Estas são as restrições permitidas com jobs de transferência de dados iniciados pelo usuário:
setBackoffCriteria(JobInfo.BACKOFF_POLICY_EXPONENTIAL)
setClipData()
setEstimatedNetworkBytes()
setMinimumNetworkChunkBytes()
setPersisted()
setNamespace()
setRequiredNetwork()
setRequiredNetworkType()
setRequiresBatteryNotLow()
setRequiresCharging()
setRequiresStorageNotLow()
Testes
A lista a seguir mostra algumas etapas para testar os jobs do app manualmente:
- Para encontrar o ID do job, acesse o valor definido após a criação dele.
Para executar um job imediatamente ou repetir um job interrompido, execute o comando a seguir em uma janela do terminal:
adb shell cmd jobscheduler run -f APP_PACKAGE_NAME JOB_ID
Para simular o fechamento forçado de um job (devido a uma condição de integridade do sistema ou falta de cotas), execute o seguinte comando em uma janela do terminal:
adb shell cmd jobscheduler timeout TEST_APP_PACKAGE TEST_JOB_ID