La mayoría de las aplicaciones tienen tareas en segundo plano, existiendo la necesidad de ejecutarlos en un hilo diferente al de la UI. Usando este tipo de tareas, esperamos que la aplicación responda a nuestras interacciones, y sea robusta, especialmente durante situaciones desfavorables. Pero, ¿podemos tener el mejor sistema de tareas en segundo plano?

Mejorando las tareas en segundo plano en Android

Android ofrece en su framework varias alternativas para crear este tipo de tareas, por ejemplo, AsyncTasks, Loaders y Services con un Thread Pool, pero tienen los siguientes problemas:

AsyncTasks están muy acopladas a la UI . Se pueden resetar con la rotación de un dispositivo.

están muy . Se pueden resetar con la rotación de un dispositivo. Loaders son buenos para tareas de disco pero no para grandes peticiones de internet .

son buenos para tareas de disco pero . Services, aunque están muy bien desacoplados de la UI, cuanto más servicios tengas ejecutando a la vez, más difícil será la concurrencia y priorización .

Para solventar estos problema, tenemos la librería Android Priority Job Queue, que se trata de una implementación de una Pila de Trabajos especificamente escrita para Android con la que podrás planificar tareas en segundo plano, mejorando la experiencia de usuario y la estabilidad de la aplicación.

Más ventajas

También, con esta librería puedes:

Desacoplar la lógica de la aplicación de las actividades o fragmentos de una manera sencialla, haciendo que tu código sea más robusto, fácil de refactorizarlo y testearlo.

de las actividades o fragmentos de una manera sencialla, haciendo que tu código sea más robusto, fácil de refactorizarlo y testearlo. Priorizar tareas en segundo plano, ejecutando unas antes que otras.

en segundo plano, ejecutando unas antes que otras. Ejecutar tareas sólo si hay conexión a internet , y detectar los cambios de conexión para que la pila lance estos trabajos que se ejecuten cuando ésta vuelve .

tareas sólo , y los de conexión para que la pila estos que se ejecuten ésta . Mantener el estado de tu trabajo en el mismo punto en el que estaba después de una situación inesperada como un crash y restaurlo donde falló .

en el mismo punto en el que estaba después de una situación inesperada como un crash y . Ejecutar trabajos en paralelo o agruparlos para ejecutarlos en serie .

trabajos o para ejecutarlos . Planificar tareas , restrasándolas después de un determinado tiempo.

, restrasándolas después de un determinado tiempo. Integrarlos con otros planificadores de trabajos como JobScheduler o GCMNetworkManager.

como JobScheduler o GCMNetworkManager. etc.

¿Cómo empiezo a usar la librería?

Primero, necesitas incluir la librería en tu proyecto. Está disponible en el repositorio de Maven Central, así que simplemente, añade la siguiente línea en las dependencias de tu script gradle:

compile 'com.birbit:android-priority-jobqueue:2.0.0'

Recomendamos mucho usar una librería de eventos con Priority Job Queue, por ejemplo Event Bus de Green Robot.

compile 'org.greenrobot:eventbus:3.0.0'

Segundo, necesitas configurar tu JobManager. Hay una extensa documentación de cómo configurar en su página wiki. Por ejemplo,

Configuration.Builder builder = new Configuration.Builder(context) .minConsumerCount(1) // always keep at least one consumer alive .maxConsumerCount(3) // up to 3 consumers at a time .loadFactor(3) // 3 jobs per consumer .consumerKeepAlive(120) // wait 2 minute .customLogger(new CustomLogger() { private static final String TAG = "JOBS"; @Override public boolean isDebugEnabled() { return true; } @Override public void d(String text, Object... args) { Log.d(TAG, String.format(text, args)); } @Override public void e(Throwable t, String text, Object... args) { Log.e(TAG, String.format(text, args), t); } @Override public void e(String text, Object... args) { Log.e(TAG, String.format(text, args)); } @Override public void v(String text, Object... args) { } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { builder.scheduler(FrameworkJobSchedulerService.createSchedulerFor(context, AppJobService.class), true); } else { int enableGcm = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context); if (enableGcm == ConnectionResult.SUCCESS) { builder.scheduler(GcmJobSchedulerService.createSchedulerFor(context, AppGcmJobService.class), true); } } mJobManager = new JobManager(builder.build());

Tercero, necesitas crear tus trabajos según tus preferencias. Mira en este enlace para saber con qué parámetros puedes configurar cada trabajo.

public FetchLocationByAddressJob(String address) { super(new Params(JobConstants.PRIORITY_NORMAL) .requireNetwork() .singleInstanceBy(TAG) .addTags(TAG) ); ... }

Además del constructor del trabajo, necesitas sobreescribir estos métodos:

onAdded: Llamado cuando el trabajo se ha añadido correctamente a la pila. Es un magnífico lugar para decirle a la UI a través de un evento que el trabajo se ejecutará cuando se reunan sus requisitos.

Llamado cuando el trabajo se ha añadido correctamente a la pila. Es un magnífico lugar para decirle a la UI a través de un evento que el trabajo se ejecutará cuando se reunan sus requisitos. onRun: Aquí es donde va toda la lógica de la tarea. Por ejemplo, una peticióna a un servicio web.

Aquí es donde va toda la lógica de la tarea. Por ejemplo, una peticióna a un servicio web. shouldReRunOnThrowable: En este método podemos detectar si queremos volver a ejecutar el trabajo ante una situación inesperada o cancelarlo. Por ejemplo. imaginad que una petición necesita autenticación y el token ya no es válido, entonces puedes cancelar este trabajo y notificar a la app que el usuario debe de autenticarse.

En este método podemos detectar si queremos volver a ejecutar el trabajo ante una situación inesperada o cancelarlo. Por ejemplo. imaginad que una petición necesita autenticación y el token ya no es válido, entonces puedes cancelar este trabajo y notificar a la app que el usuario debe de autenticarse. onCancel: Si se ejecuta dicha función, quiere decir que el trabajo ha fallado ya sea porque se ha excedido el número de reintentos que tiene el trabajo o porque se ha cancelado en el método shouldReRunOnThrowable.

Y por último, crea y añade trabajos a la pila, se ejecutarán cuando se reunan sus requisitos.

public class FetchLocationByAddressJob extends Job { private String mAddress; public static final String TAG = FetchLocationByAddressJob.class.getCanonicalName(); public FetchLocationByAddressJob(String address) { super(new Params(JobConstants.PRIORITY_NORMAL) .requireNetwork() .singleInstanceBy(TAG) .addTags(TAG) ); mAddress = address; } @Override public void onAdded() { // Store address in database FakeDatabase.setLastAddress(getApplicationContext(), mAddress); } @Override public void onRun() throws Throwable { String address = FakeDatabase.getLastAddress(getApplicationContext()); GeocodingInterface service = AppRetrofitManager.getGeocodingInterface(); Call request = service.getInfoByAddress(address); Geocode geocode = AppRetrofitManager.performRequest(request); // Ensure we have information about the search address if (geocode == null || geocode.getResults().size() <= 0) { EventBus.getDefault().post(new LocationFailedEvent()); return; } // Just retrieve the first result EventBus.getDefault().post(new LocationFetchedEvent(geocode.getResults().get(0))); } @Override protected void onCancel(int cancelReason, @Nullable Throwable throwable) { EventBus.getDefault().post(new LocationFailedEvent()); } @Override protected RetryConstraint shouldReRunOnThrowable(@NonNull Throwable throwable, int runCount, int maxRunCount) { if (throwable instanceof ErrorRequestException) { ErrorRequestException error = (ErrorRequestException) throwable; int statusCode = error.getResponse().raw().code(); if (statusCode >= 400 && statusCode < 500) { return RetryConstraint.CANCEL; } } return RetryConstraint.RETRY; } }

Si quieres ver un proyecto entero usando esta este tipo de tareas en segundo plano con esta librería, echa un vistazo a esta app.

Consejo

Nunca uses llamadas síncronas de Volley dentro de un trabajo, usa Retrofit mejor.

Cuando llevas ya unos cuantos trabajos ejecutados, a veces, a cabo del tiempo, te puedes dar cuenta que todo este tipo de tareas en segundo plano que hacen una petición síncrona con Volley fallan por un extraño error. Da igual que canceles todos los trabajos y reinicies la pila cuando ésto pasa. Todos fallarán incluso poniendo un timeout mayor que 0, diferente al de por defecto.

Depurando puedes ver que el servidor devuelve una respuesta correcta, pero falla cuando Volley comprueba el estado de la petición.

Mis sospechas por lo que pasa este error es porque Volley cachea las peticiones, originando algún tipo de bloqueo que hace que todos los demás fallen.

Así que, no uses Volley con esta librería ya que te volverá loco.

Artículos relacionados

Informes en formato PDF en Android

ASO – Posiciona tu app en el App Store o Google Play

Detección de dispositivos Bluetooth en Android

OCR en Android