Usa la librería Android Priority Job Queue para tus tareas en segundo plano

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.
  • Loaders son buenos para tareas de disco pero no para grandes peticiones de internet.
  • 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 Queueque 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.
  • Priorizar tareas 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.
  • 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ó.
  • Ejecutar trabajos en paralelo o agruparlos para ejecutarlos en serie.
  • Planificar tareas, restrasándolas después de un determinado tiempo.
  • Integrarlos con otros planificadores de trabajos como JobScheduler o GCMNetworkManager.
  • etc.

starting - background tasks

¿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.
  • onRun: 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.
  • 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

3 comentarios en «Usa la librería Android Priority Job Queue para tus tareas en segundo plano»

  1. Muchas gracias por este post.
    Andaba buscado precisamente, como priorizar algunas tareas de otras.
    Lamentablemente android no permite hacer algo así con su interfaz, por lo que hay que hacer lo que se comenta en este artículo.

    saludos!

    Responder
    • Hola,

      Yo antes usaba Volley en todos mis proyectos, pero cuando descubrí Retrofit cambié de opinión. Para describir las diferencias entre una y otra librería, se debería de hacer en otra entrada en el blog, no es algo simple.

      Yo si quieres te comento las principales diferencias: simplicidad, es una librería Java (no necesita de una instancia de Context para funcionar), puedes usar varios conversores (JSON, XML) y llamadas Multipart con la misma instancia de Retrofit…

      Además, Volley tiene un cacheo en memoria de las requests, para mi gusto malo, que al cabo del tiempo empieza a dejar de llamar con llamadas síncronas.

      Como ya he dicho anteriormente, si tuviera que empezar un proyecto de nuevo, me decantaría sin ninguna duda por Retrofit. Pero también es un punto de vista personal, a lo mejor la posibilidad de extensión de Volley es esencial para ti y Retrofit no te conviene.

      Responder

Deja un comentario

¿Necesitas una estimación?

Calcula ahora

Centro de preferencias de privacidad

Cookies propias

__unam, gdpr 1P_JAR, DV, NID, _icl_current_language

Cookies de analítica

Estas cookies nos ayudan a comprender cómo los usuarios interactúan con nuestra página web.

_ga, _gat_UA-42883984-1, _gid, _hjIncludedInSample,

Cookies de suscripción

Estas cookies se utilizan para ejecutar funciones de la Web, como no mostrar el banner publicitario y / o recordar la configuración del usuario dentro de la sesión.

tl_3832_3832_2 tl_5886_5886_12 tve_leads_unique

Otra