Programador de trabajos de Android
En esta guía se describe cómo programar trabajos en segundo plano mediante la API del programador de trabajos de Android, que está disponible en dispositivos Android que ejecutan Android 5.0 (nivel de API 21) y versiones posteriores.
Información general
Una de las mejores formas de mantener la respuesta de una aplicación Android al usuario es asegurarse de que el trabajo complejo o de larga duración se realice en segundo plano. Sin embargo, es importante que el trabajo en segundo plano no afecte negativamente a la experiencia del usuario con el dispositivo.
Por ejemplo, un trabajo en segundo plano puede sondear un sitio web cada tres o cuatro minutos para consultar los cambios en un conjunto de datos determinado. Este proceso, aparentemente benigno, tendría un efecto desastroso en la duración de la batería. La aplicación reactivará el dispositivo de forma repetida, elevará la CPU a un estado de energía mayor, encenderá los radios, realizará las solicitudes de red y, luego, procesará los resultados. La situación empeora porque el dispositivo no se apaga inmediatamente y vuelve al estado inactivo de baja energía. Un trabajo en segundo plano mal programado puede mantener involuntariamente el dispositivo en un estado con requisitos de energía innecesarios y excesivos. Esta actividad aparentemente inocente (sondear un sitio web) dejará al dispositivo inutilizable durante un período de tiempo relativamente corto.
Android proporciona las siguientes API para ayudar a realizar trabajos en segundo plano, pero por sí solas no son suficientes para la programación inteligente de trabajos.
- Servicios de intención: los servicios de intención son excelentes para realizar el trabajo, pero no proporcionan ninguna forma de programarlo.
- AlarmManager: estas API permiten programar el trabajo, pero no proporcionan ninguna forma de realizarlo. Además, AlarmManager solo permite restricciones basadas en el tiempo, lo que significa que se producirá una alarma en un momento determinado o transcurrido un período de tiempo determinado.
- Receptores de difusión: una aplicación Android puede configurar receptores de difusión para que realicen algún trabajo en respuesta a eventos o intenciones de todo el sistema. Sin embargo, los receptores de difusión no proporcionan ningún control sobre cuándo se debe ejecutar el trabajo. Además, los cambios en el sistema operativo Android se restringirán cuando funcionen los receptores de difusión o los tipos de trabajo a los que puedan responder.
Hay dos características clave para realizar eficazmente el trabajo en segundo plano (a veces denominado tareas en segundo plano o trabajo):
- Programación inteligente del trabajo: es importante que cuando una aplicación realice trabajos en segundo plano lo haga de forma sensata. Lo ideal es que la aplicación no exija que se ejecute un trabajo. En su lugar, la aplicación debe especificar las condiciones que deben cumplirse para que se ejecute el trabajo y, luego, programar ese trabajo con el sistema operativo que lo realizará cuando se cumplan las condiciones. De esta manera, Android puede ejecutar el trabajo para garantizar la máxima eficacia en el dispositivo. Por ejemplo, las solicitudes de red se pueden procesar por lotes para ejecutarse todas al mismo tiempo con el fin de usar al máximo la sobrecarga que conllevan las redes.
- Encapsulación del trabajo: el código para realizar el trabajo en segundo plano debe encapsularse en un componente discreto que se pueda ejecutar con independencia de la interfaz de usuario y sea relativamente fácil de volver a programar si el trabajo no se completa.
El programador de trabajos de Android es un marco integrado en el sistema operativo Android que proporciona una API fluida para simplificar la programación de trabajos en segundo plano. El programador de trabajos de Android consta de los siguientes tipos:
Android.App.Job.JobScheduler
es un servicio del sistema que se usa para programar, ejecutar y, si es necesario, cancelar los trabajos en nombre de una aplicación de Android.Android.App.Job.JobService
es una clase abstracta que se debe extender con la lógica que ejecutará el trabajo en el subproceso principal de la aplicación. Esto significa queJobService
es responsable de cómo se realice el trabajo de forma asincrónica.- Un objeto
Android.App.Job.JobInfo
contiene los criterios para indicar a Android cuándo se debe ejecutar el trabajo.
Para programar el trabajo con el programador de trabajos de Android, una aplicación de Xamarin.Android debe encapsular el código en una clase que extienda la clase JobService
. JobService
tiene tres métodos de ciclo de vida a los que se puede llamar durante la vigencia del trabajo:
bool OnStartJob(JobParameters parameters):
JobScheduler
llama a este método para realizar el trabajo y se ejecuta en el subproceso principal de la aplicación. Es responsabilidad deJobService
realizar el trabajo de forma asincrónica y devolvertrue
si queda trabajo ofalse
si se ha completado.Cuando
JobScheduler
llama a este método, solicitará y conservará un wakelock de Android mientras dure el trabajo. Una vez finalizado el trabajo, es responsabilidad deJobService
indicar aJobScheduler
este hecho mediante la llamada al métodoJobFinished
(descrito a continuación).JobFinished(JobParameters parameters, bool needsReschedule): este método se debe llamar mediante
JobService
para indicar aJobScheduler
que el trabajo se ha completado. Si no se llama aJobFinished
,JobScheduler
no quitará el wakelock, lo que provocará un desgaste innecesario de la batería.bool OnStopJob(JobParameters parameters): se llama cuando Android detiene el trabajo antes de tiempo. Debe devolver
true
si el trabajo se debe volver a programar en función de los criterios de reintento (que se describen a continuación con más detalle).
Es posible especificar restricciones o desencadenadores que controlarán cuándo un trabajo puede o debe ejecutarse. Por ejemplo, es posible restringir un trabajo para que solo se ejecute cuando el dispositivo se esté cargando o iniciar un trabajo cuando se haga una foto.
En esta guía se explica de forma detallada cómo implementar una clase JobService
y programarla con JobScheduler
.
Requisitos
El programador de trabajos de Android requiere el nivel de API 21 de Android (Android 5.0) o posterior.
Uso del programador de trabajos de Android
Para usar la API JobScheduler de Android, siga estos tres pasos:
- Implemente un tipo JobService para encapsular el trabajo.
- Use un objeto
JobInfo.Builder
para crear el objetoJobInfo
que contendrá los criterios deJobScheduler
para ejecutar el trabajo. - Programe el trabajo mediante
JobScheduler.Schedule
.
Implementación de JobService
Todo el trabajo realizado por la biblioteca del programador de trabajos de Android debe realizarse en un tipo que extienda la clase abstracta Android.App.Job.JobService
. Crear un objeto JobService
es muy parecido a crear un objeto Service
con el marco de Android:
- Extienda la clase
JobService
. - Decore la subclase con
ServiceAttribute
y establezca el parámetroName
en una cadena formada por el nombre del paquete y el nombre de la clase (vea el ejemplo siguiente). - Establezca la propiedad
Permission
deServiceAttribute
en la cadenaandroid.permission.BIND_JOB_SERVICE
. - Invalide el método
OnStartJob
, y agregue el código para realizar el trabajo. Android invocará este método en el subproceso principal de la aplicación para ejecutar el trabajo. El trabajo que tarde más de unos milisegundos se debería ejecutar en un subproceso para evitar que se bloquee la aplicación. - Cuando se complete el trabajo,
JobService
debe llamar al métodoJobFinished
. Este método es el modo en queJobService
indica aJobScheduler
que se ha completado el trabajo. Si no se llama aJobFinished
,JobService
someterá al dispositivo a exigencias innecesarias, lo que acortará la duración de la batería. - También es una buena idea reemplazar el método
OnStopJob
. Android llama a este método cuando se está cerrando el trabajo antes de que finalice y proporciona aJobService
la oportunidad de eliminar los recursos correctamente. Este método debe devolvertrue
si es necesario volver a programar el trabajo ofalse
si no es aconsejable volver a ejecutarlo.
El código siguiente es un ejemplo del objeto JobService
más sencillo para una aplicación, donde se usa TPL para realizar de forma asincrónica algo de trabajo:
[Service(Name = "com.xamarin.samples.downloadscheduler.DownloadJob",
Permission = "android.permission.BIND_JOB_SERVICE")]
public class DownloadJob : JobService
{
public override bool OnStartJob(JobParameters jobParams)
{
Task.Run(() =>
{
// Work is happening asynchronously
// Have to tell the JobScheduler the work is done.
JobFinished(jobParams, false);
});
// Return true because of the asynchronous work
return true;
}
public override bool OnStopJob(JobParameters jobParams)
{
// we don't want to reschedule the job if it is stopped or cancelled.
return false;
}
}
Creación de JobInfo para programar un trabajo
Las aplicaciones de Xamarin.Android no crean instancias de JobService
directamente, sino que pasarán un objeto JobInfo
a JobScheduler
. JobScheduler
creará una instancia del objeto JobService
solicitado, y programará y ejecutará JobService
según los metadatos de JobInfo
. Un objeto JobInfo
debe contener la siguiente información:
- JobId: es un valor de
int
que se utiliza para identificar un trabajo enJobScheduler
. Al reutilizar este valor se actualizarán los trabajos existentes. El valor debe ser único para la aplicación. - JobService: este parámetro es un objeto
ComponentName
que identifica explícitamente el tipo que debe usarJobScheduler
para ejecutar un trabajo.
Este método de extensión muestra cómo crear un objeto JobInfo.Builder
con un elemento Context
de Android, como es el caso de una actividad:
public static class JobSchedulerHelpers
{
public static JobInfo.Builder CreateJobBuilderUsingJobId<T>(this Context context, int jobId) where T:JobService
{
var javaClass = Java.Lang.Class.FromType(typeof(T));
var componentName = new ComponentName(context, javaClass);
return new JobInfo.Builder(jobId, componentName);
}
}
// Sample usage - creates a JobBuilder for a DownloadJob and sets the Job ID to 1.
var jobBuilder = this.CreateJobBuilderUsingJobId<DownloadJob>(1);
var jobInfo = jobBuilder.Build(); // creates a JobInfo object.
Una eficaz característica del programador de trabajos de Android es la posibilidad de controlar cuándo se ejecuta un trabajo o en qué condiciones se puede ejecutar. En la tabla siguiente se describen algunos de los métodos de JobInfo.Builder
que permiten a una aplicación influir en cuándo se puede ejecutar un trabajo:
Method | Descripción |
---|---|
SetMinimumLatency |
Especifica que se debe observar un retraso (en milisegundos) antes de que se ejecute un trabajo. |
SetOverridingDeadline |
Declara que el trabajo debe ejecutarse antes de que transcurra este tiempo (en milisegundos). |
SetRequiredNetworkType |
Especifica los requisitos de red de un trabajo. |
SetRequiresBatteryNotLow |
El trabajo solo se puede ejecutar cuando el dispositivo no muestra una advertencia de "batería baja" al usuario. |
SetRequiresCharging |
El trabajo solo se puede ejecutar cuando la batería se está cargando. |
SetDeviceIdle |
El trabajo se ejecutará cuando el dispositivo esté ocupado. |
SetPeriodic |
Especifica que el trabajo debe ejecutarse con regularidad. |
SetPersisted |
El trabajo debe conservarse entre reinicios del dispositivo. |
En SetBackoffCriteria
se proporcionan instrucciones sobre cuánto tiempo debe esperar JobScheduler
antes de intentar ejecutar de nuevo un trabajo. Los criterios de retroceso constan de dos partes: un retroceso en milisegundos (valor predeterminado de 30 segundos) y el tipo de retroceso que se debe usar (en ocasiones conocido como directiva de retroceso o directiva de reintentos). Las dos directivas se encapsulan en la enumeración Android.App.Job.BackoffPolicy
:
BackoffPolicy.Exponential
: una directiva de retroceso exponencial aumentará el valor de retroceso inicial de forma exponencial después de cada error. La primera vez que se produce un error en un trabajo, la biblioteca esperará el intervalo inicial especificado antes de volver a programar el trabajo; por ejemplo, 30 segundos. La segunda vez que se produce un error en el trabajo, la biblioteca esperará al menos 60 segundos antes de intentar ejecutar el trabajo. Después del tercer intento con error, la biblioteca esperará 120 segundos y así sucesivamente. Este es el valor predeterminado.BackoffPolicy.Linear
: esta estrategia es un retroceso lineal de que el trabajo se debe volver a programar para que se ejecute a intervalos establecidos (hasta que se realice correctamente). El retroceso lineal es más adecuado para el trabajo que debe realizarse lo antes posible o para los problemas que se resolverán rápidamente por sí solos.
Para más información sobre cómo crear un objeto JobInfo
, lea la documentación de Google sobre la clase JobInfo.Builder
.
Pasar parámetros a un trabajo a través de JobInfo
Los parámetros se pasan a un trabajo mediante la creación de un objeto PersistableBundle
que se pasa junto con el método Job.Builder.SetExtras
:
var jobParameters = new PersistableBundle();
jobParameters.PutInt("LoopCount", 11);
var jobBuilder = this.CreateJobBuilderUsingJobId<DownloadJob>(1)
.SetExtras(jobParameters)
.Build();
El acceso a PersistableBundle
se realiza desde la propiedad Android.App.Job.JobParameters.Extras
del método OnStartJob
de un objeto JobService
:
public override bool OnStartJob(JobParameters jobParameters)
{
var loopCount = jobParams.Extras.GetInt("LoopCount", 10);
// rest of code omitted
}
Programación de un trabajo
Para programar un trabajo, una aplicación de Xamarin.Android obtendrá una referencia al servicio del sistema JobScheduler
y llamará al método JobScheduler.Schedule
con el objeto JobInfo
creado en el paso anterior. JobScheduler.Schedule
se devolverá inmediatamente con uno de dos valores enteros:
- JobScheduler.ResultSuccess: el trabajo se ha programado correctamente.
- JobScheduler.ResultFailure: el trabajo no se pudo programar. Esto se debe normalmente a conflictos de los parámetros
JobInfo
.
Este código es un ejemplo de programación de un trabajo y notificación al usuario de los resultados del intento de programación:
var jobScheduler = (JobScheduler)GetSystemService(JobSchedulerService);
var scheduleResult = jobScheduler.Schedule(jobInfo);
if (JobScheduler.ResultSuccess == scheduleResult)
{
var snackBar = Snackbar.Make(FindViewById(Android.Resource.Id.Content), Resource.String.jobscheduled_success, Snackbar.LengthShort);
snackBar.Show();
}
else
{
var snackBar = Snackbar.Make(FindViewById(Android.Resource.Id.Content), Resource.String.jobscheduled_failure, Snackbar.LengthShort);
snackBar.Show();
}
Cancelación de un trabajo
Es posible cancelar todos los trabajos programados o uno solo mediante el método JobsScheduler.CancelAll()
o el método JobScheduler.Cancel(jobId)
:
// Cancel all jobs
jobScheduler.CancelAll();
// to cancel a job with jobID = 1
jobScheduler.Cancel(1)
Resumen
En esta guía se describe cómo usar el programador de trabajos de Android para realizar un trabajo en segundo plano de forma inteligente. Se explica cómo encapsular el trabajo para que se realice como un objeto JobService
y cómo usar JobScheduler
para programar ese trabajo, y especificar los criterios con JobTrigger
; también se describe cómo se deben administrar los errores con RetryStrategy
.