
La aplicación iFunny en la que estamos trabajando ha estado disponible en las tiendas por más de cinco años. Durante este tiempo, el equipo móvil tuvo que pasar por diferentes enfoques y migraciones entre herramientas, y hace un año hubo un momento para cambiar de una solución autoescrita y buscar algo más "de moda" y generalizado. Este artículo es un pequeño resumen sobre lo que se ha estudiado, qué soluciones se han analizado y con qué han terminado.
¿Por qué necesitamos todo esto?Decidamos inmediatamente en honor de qué es este artículo y por qué este tema resultó ser importante para el equipo de desarrollo de Android:
- Hay muchos escenarios en los que necesita ejecutar tareas fuera del marco de la interfaz de usuario activa;
- el sistema impone una gran cantidad de restricciones al lanzamiento de tales tareas;
- Resultó ser bastante difícil elegir entre las soluciones existentes, ya que cada herramienta tiene sus ventajas y desventajas.
Cronología del desarrollo de eventos.
Android 0
AlarmManager, Handler, Servicio
Inicialmente, sus soluciones se implementaron para lanzar tareas en segundo plano basadas en servicios. También había un mecanismo que vinculaba las tareas con el ciclo de vida y podía cancelarlas y restaurarlas. Esto fue adecuado para el equipo durante mucho tiempo, ya que la plataforma no impuso ninguna restricción en tales tareas.
Google aconsejó hacer esto según el siguiente diagrama:

A finales de 2018, no tiene sentido entender esto, es suficiente para evaluar la magnitud del desastre.
De hecho, a nadie le importaba cuánto trabajo está sucediendo en el fondo. Las aplicaciones hicieron lo que quisieron y cuando quisieron.
Pros :
disponible en todas partes;
accesible a todos.
Contras :
el sistema restringe el trabajo en todos los sentidos;
sin lanzamientos por condición;
La API es mínima y necesita escribir mucho código.
Android 5. Lollipop
Jobcheduler
Después de 5 (!) Años, más cerca de 2015, Google notó que las tareas se inician de manera ineficiente. Los usuarios comenzaron a quejarse regularmente de que sus teléfonos se estaban agotando simplemente acostados en una mesa o en su bolsillo.
Con el lanzamiento de Android 5, apareció una herramienta como JobScheduler. Este es un mecanismo con cuya ayuda es posible llevar a cabo varios trabajos en segundo plano, cuyo comienzo se optimizó y simplificó debido al sistema centralizado para iniciar estas tareas y la capacidad de establecer condiciones para este mismo lanzamiento.
En el código, todo esto parece bastante simple: se anuncia un servicio en el que llegan los eventos de inicio y finalización.
Desde los matices: si desea realizar el trabajo de forma asincrónica, desde onStartJob debe iniciar la secuencia; lo principal es no olvidar llamar al trabajo Método terminado al final del trabajo, de lo contrario el sistema no soltará WakeLock, su tarea no se considerará completada y se perderá.
public class JobSchedulerService extends JobService { @Override public boolean onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
Desde cualquier lugar de la aplicación, puede iniciar este trabajo. Las tareas se realizan en nuestro proceso, pero se inician a nivel de IPC. Existe un mecanismo centralizado que controla su ejecución y activa la aplicación solo en los momentos necesarios para esto. También puede establecer varias condiciones de activación y transferir datos a través del paquete.
JobInfo task = new JobInfo.Builder(JOB_ID, serviceName) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .build(); JobScheduler scheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE); scheduler.schedule(task);
En general, comparado con nada, esto ya era algo. Pero este mecanismo solo está disponible con API 21, y en el momento del lanzamiento de Android 5.0 sería extraño dejar de admitir todos los dispositivos antiguos (han pasado 3 años y todavía admitimos cuatro).
Pros :
La API es simple;
condiciones para el lanzamiento.
Contras :
Disponible a partir de API 21de hecho, solo con API 23;
Es fácil cometer un error.
Android 5. Lollipop
Administrador de red de gcm
También se presentó un análogo de JobScheduler: GCM Network Manager. Esta es una biblioteca que proporcionaba una funcionalidad similar, pero que ya funcionaba con la API 9. Es cierto que a cambio requería los servicios de Google Play. Aparentemente, la funcionalidad necesaria para que JobScheduler funcione, comenzó a entregarse no solo a través de la versión de Android, sino también a nivel de GPS. Cabe señalar que los desarrolladores del marco cambiaron de opinión muy rápidamente y decidieron no conectar su futuro con el GPS. Gracias a ellos por eso.
Todo se ve absolutamente idéntico. Mismo servicio:
public class GcmNetworkManagerService extends GcmTaskService { @Override public int onRunTask(TaskParams taskParams) { doWork(taskParams); return 0; } }
El mismo inicio de tarea:
OneoffTask task = new OneoffTask.Builder() .setService(GcmNetworkManagerService.class) .setTag(TAG) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .build(); GcmNetworkManager mGcmNetworkManager = GcmNetworkManager.getInstance(this); mGcmNetworkManager.schedule(task);
Esta similitud de la arquitectura fue dictada por la funcionalidad heredada y el deseo de obtener una migración simple entre herramientas.
Pros :
API similar a JobScheduler;
Disponible a partir de API 9.
Contras :
Debes tener los servicios de Google Play
Es fácil cometer un error.Android 5. Lollipop
WakefulBroadcastReceiver
A continuación, escribiré algunas palabras sobre uno de los mecanismos básicos que se utiliza en JobScheduler y que está disponible directamente para los desarrolladores. Este es WakeLock y su WakefulBroadcastReceiver basado.
Con WakeLock, puede evitar que el sistema se suspenda, es decir, mantenga el dispositivo en un estado activo. Esto es necesario si queremos hacer un trabajo importante.
Al crear WakeLock, puede especificar su configuración: mantenga la CPU, la pantalla o el teclado.
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE) PowerManager.WakeLock wl = pm.newWakeLock(PARTIAL_WAKE_LOCK, "name") wl.acquire(timeout);
Basado en este mecanismo, funciona el WakefulBroadcastReceiver. Iniciamos el servicio y mantenemos WakeLock.
public class SimpleWakefulReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent service = new Intent(context, SimpleWakefulService.class); startWakefulService(context, service); } }
Una vez que el servicio ha completado el trabajo necesario, lo liberamos a través de métodos similares.
A través de 4 versiones, este BroadcastReceiver quedará obsoleto, y las siguientes alternativas se describirán en developer.android.com:
- JobScheduler;
- Syncadapter
- DownloadManager
- FLAG_KEEP_SCREEN_ON para Window.
Android 6. Marshmallow
DozeMode: dormir sobre la marcha
Luego, Google comenzó a aplicar varias optimizaciones para las aplicaciones que se ejecutan en el dispositivo. Pero lo que es la optimización para el usuario es una limitación para el desarrollador.
El primer paso fue DozeMode, que pone el dispositivo en modo de suspensión si permanece inactivo durante un tiempo determinado. En las primeras versiones, duró una hora, en versiones posteriores, la duración del sueño se redujo a 30 minutos. Periódicamente, el teléfono se despierta, realiza todas las tareas pendientes y se duerme nuevamente. La ventana DozeMode se expande exponencialmente. Todas las transiciones entre modos se pueden rastrear a través de adb.
Cuando se produce DozeMode, se imponen las siguientes restricciones a la aplicación:
- el sistema ignora todos los WakeLock;
- AlarmManager se retrasa;
- JobScheduler no funciona;
- SyncAdapter no funciona;
- El acceso a la red es limitado.
También puede agregar su aplicación a la lista blanca para que no se encuentre bajo las limitaciones de DozeMode, pero al menos Samsung ignoró por completo esta lista.
Android 6. Marshmallow
AppStandby: aplicaciones inactivas
El sistema identifica las aplicaciones que están inactivas y les impone las mismas restricciones que en DozeMode.
Una aplicación se envía al aislamiento si:
- no tiene un proceso en primer plano;
- no tiene una notificación activa;
- no agregado a la lista de exclusión.
Android 7. Turrón
Optimizaciones de fondo. Esbelto
Svelte es un proyecto en el que Google está tratando de optimizar el consumo de RAM por las aplicaciones y el sistema en sí.
En Android 7, en el marco de este proyecto, se decidió que las transmisiones implícitas no son muy efectivas, ya que son escuchadas por una gran cantidad de aplicaciones y el sistema gasta una gran cantidad de recursos cuando ocurren estos eventos. Por lo tanto, se prohibieron los siguientes tipos de eventos para la declaración en el manifiesto:
- CONECTIVIDAD_ACCIÓN;
- ACTION_NEW_PICTURE;
- ACTION_NEW_VIDEO.
Android 7. Turrón
FirebaseJobDispatcher
Al mismo tiempo, se publicó una nueva versión del marco de inicio de tareas: FirebaseJobDispatcher. De hecho, fue el GCM NetworkManager completo, que se ordenó un poco y se hizo un poco más flexible.
Visualmente, todo se veía exactamente igual. Mismo servicio:
public class JobSchedulerService extends JobService { @Override public boolean onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
La única diferencia entre él era la capacidad de instalar su controlador. Un controlador es la clase responsable de la estrategia de inicio de la tarea.
El inicio de las tareas en sí no ha cambiado con el tiempo.
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); Job task = dispatcher.newJobBuilder() .setService(FirebaseJobDispatcherService.class) .setTag(TAG) .setConstraints(Constraint.ON_UNMETERED_NETWORK, Constraint.DEVICE_IDLE) .build(); dispatcher.mustSchedule(task);
Pros :
API similar a JobScheduler;
Disponible a partir de API 9.
Contras :
Debes tener los servicios de Google Play
Es fácil cometer un error.
Fue alentador instalar mi controlador para deshacerme del GPS. Incluso buscamos, pero finalmente encontramos lo siguiente:


Google lo sabe, pero estas tareas permanecen abiertas durante varios años.
Android 7. Turrón
Android Job por Evernote
Como resultado, la comunidad no pudo soportarlo, y apareció una solución hecha a sí misma en forma de una biblioteca de Evernote. No fue el único, pero fue la solución de Evernote que pudo establecerse y "entrar en la gente".
En términos arquitectónicos, esta biblioteca era más conveniente que sus predecesoras.
Ha aparecido la entidad responsable de crear tareas. En el caso de JobScheduler, se crearon a través de la reflexión.
class SendLogsJobCreator : JobCreator { override fun create(tag: String): Job? { when (tag) { SendLogsJob.TAG -> return SendLogsJob() } return null } }
Hay una clase separada, que es la tarea misma. En JobScheduler, todo esto se volcó en un interruptor dentro de OnStartJob.
class SendLogsJob : Job() { override fun onRunJob(params: Params): Result { return doWork(params) } }
El inicio de las tareas es idéntico, pero además de los eventos heredados, Evernote también agregó los suyos, como el inicio de tareas diarias, tareas únicas y el lanzamiento dentro de la ventana.
new JobRequest.Builder(JOB_ID) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED) .build() .scheduleAsync();
Pros :
API conveniente;
compatible con todas las versiones;
No necesita los servicios de Google Play.
Contras :
Solución de terceros.
Los chicos apoyaron activamente su biblioteca. Aunque hubo bastantes problemas críticos, funcionó en todas las versiones y en todos los dispositivos. Como resultado, el año pasado nuestro equipo de Android eligió una solución de Evernote, ya que las bibliotecas de Google cortaron una gran capa de dispositivos que no pueden admitir.
En el interior, trabajó en soluciones de Google, en casos extremos, con AlarmManager.
Android 8. Oreo
Límites de ejecución en segundo plano
Volvamos a nuestras limitaciones. Con el advenimiento del nuevo Android, han llegado nuevas optimizaciones. Los chicos de Google encontraron otro problema. Esta vez todo resultó en servicios y transmisiones (sí, nada nuevo).
startService si las aplicaciones están en segundo planotransmisión implícita en manifiesto
En primer lugar, estaba prohibido iniciar servicios desde el fondo. En el "marco de la ley" quedaron solo los servicios en primer plano. Ahora se puede decir que los servicios están en desuso.
La segunda limitación es la misma transmisión. Esta vez se prohibió registrar TODAS las transmisiones implícitas en el manifiesto. La transmisión implícita es una transmisión que está destinada no solo a nuestra aplicación. Por ejemplo, hay Acción ACTION_PACKAGE_REPLACED, y hay ACTION_MY_PACKAGE_REPLACED. Entonces, el primero es implícito.
Pero cualquier transmisión aún puede registrarse a través de Context.registerBroadcast.
Android 9. Pie
Gerente de trabajo
En esta optimización se ha detenido todavía. Quizás los dispositivos comenzaron a funcionar rápida y cuidadosamente en términos de consumo de energía; quizás los usuarios se han quejado menos al respecto.
En Android 9, los desarrolladores del marco se acercaron a la herramienta para iniciar tareas. En un intento por resolver todos los problemas apremiantes, se introdujo una biblioteca en Google I / O para iniciar las tareas en segundo plano de WorkManager.
Google recientemente ha estado tratando de dar forma a su visión de la arquitectura de la aplicación de Android y ofrece a los desarrolladores las herramientas necesarias para ello. Así que había componentes arquitectónicos con LiveData, ViewModel y Room. WorkManager parece un complemento razonable para su enfoque y paradigma.
Si hablamos de cómo está organizado el WorkManager en el interior, entonces no hay ningún avance tecnológico en él. De hecho, este es un contenedor de soluciones existentes: JobScheduler, FirebaseJobDispatcher y AlarmManager.
createBestAvailableBackgroundScheduler static Scheduler createBestAvailableBackgroundScheduler(Context, WorkManager) { if (Build.VERSION.SDK_INT >= MIN_JOB_SCHEDULER_API_LEVEL) { return new SystemJobScheduler(context, workManager); } try { return tryCreateFirebaseJobScheduler(context); } catch (Exception e) { return new SystemAlarmScheduler(context); } }
El código de selección es bastante simple. Pero debe tenerse en cuenta que JobScheduler está disponible a partir de API 21, pero lo usan solo con API 23, ya que las primeras versiones eran bastante inestables.
Si la versión es inferior a 23, entonces a través de la reflexión tratamos de encontrar FirebaseJobDispatcher, de lo contrario, utilizamos AlarmManager.
Vale la pena señalar que el envoltorio salió bastante flexible. Esta vez, los desarrolladores dividieron todo en entidades separadas, y arquitectónicamente parece conveniente:
- Trabajador - lógica de trabajo;
- WorkRequest: lógica del inicio de la tarea;
- WorkRequest.Builder - parámetros;
- Restricciones - condiciones;
- WorkManager: un gerente que administra las tareas;
- WorkStatus: estado de la tarea.

Las condiciones de lanzamiento se heredaron de JobScheduler.
Se puede observar que el desencadenante para cambiar el URI apareció solo con la API 23. Además, puede suscribirse al cambio no solo de un URI específico, sino también de todos los anidados utilizando el indicador en el método.
Si hablamos de nosotros, entonces, en la etapa alfa, se decidió cambiar a WorkManager.
Hay varias razones para esto. Evernote tiene un par de errores críticos que los desarrolladores de la biblioteca prometen solucionar con la transición a una versión con WorkManager integrado. Y ellos mismos están de acuerdo en que la decisión de Google niega las ventajas de Evernote. Además, esta solución se adapta bien a nuestra arquitectura, ya que usamos componentes de arquitectura.
Además, me gustaría mostrar con un simple ejemplo cómo estamos tratando de usar este enfoque. Al mismo tiempo, no es muy crítico si tiene un WorkManager o JobScheduler.


Veamos un ejemplo con un caso muy simple: haciendo clic en volver a publicar o en Me gusta.
Ahora todas las aplicaciones están tratando de escapar de las solicitudes de bloqueo a la red, ya que esto pone nervioso al usuario y lo hace esperar, aunque en este momento puede realizar compras dentro de la aplicación o mirar anuncios.
En tales casos, los datos locales cambian primero: el usuario ve inmediatamente el resultado de su acción. Luego, en segundo plano, hay una solicitud al servidor, si falla, los datos se restablecen a su estado inicial.
A continuación, mostraré un ejemplo de cómo se ve con nosotros.
JobRunner contiene la lógica para iniciar tareas. Sus métodos describen la configuración de tareas y parámetros de paso.
JobRunner.java fun likePost(content: IFunnyContent) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() val input = Data.Builder() .putString(LikeContentJob.ID, content.id) .build() val request = OneTimeWorkRequest.Builder(LikeContentJob::class.java) .setInputData(input) .setConstraints(constraints) .build() WorkManager.getInstance().enqueue(request) }
La tarea en sí dentro del WorkManager es la siguiente: tomamos la identificación de los parámetros y llamamos al método en el servidor para que me guste este contenido.
Tenemos una clase base que contiene la siguiente lógica:
abstract class BaseJob : Worker() { final override fun doWork(): Result { val workerInjector = WorkerInjectorProvider.injector() workerInjector.inject(this) return performJob(inputData) } abstract fun performJob(params: Data): Result }
En primer lugar, le permite alejarse un poco del conocimiento explícito de Worker. También contiene la lógica de inyección de dependencia a través de WorkerInjector.
WorkerInjectorImpl.java @Singleton public class WorkerInjectorImpl implements WorkerInjector { @Inject public WorkerInjectorImpl() {} @Ovierride public void inject(Worker job) { if (worker instanceof AppCrashedEventSendJob) { Injector.getAppComponent().inject((AppCrashedEventSendJob) job); } else if (worker instanceof CheckNativeCrashesJob) { Injector.getAppComponent().inject((CheckNativeCrashesJob) job); } } }
Simplemente representa las llamadas a Dagger, pero nos ayuda en las pruebas: reemplazamos las implementaciones de inyectores e implementamos el entorno necesario en las tareas.
fun void testRegisterPushProvider() { WorkManagerTestInitHelper.initializeTestWorkManager(context) val testDriver = WorkManagerTestInitHelper.getTestDriver() WorkerInjectorProvider.setInjector(TestInjector())
class LikePostInteractor @Inject constructor( val iFunnyContentDao: IFunnyContentDao, val jobRunner: JobRunner) : Interactor { fun execute() { iFunnyContentDao.like(getContent().id) jobRunner.likePost(getContent()) } }
Interactor es la entidad que el ViewController extrae para iniciar el paso del script (en este caso, me gusta). Marcamos el contenido localmente como "cargado" y enviamos la tarea para su ejecución. Si la tarea falla, se elimina lo similar.
class IFunnyContentViewModel(val iFunnyContentDao: IFunnyContentDao) : ViewModel() { val likeState = MediatorLiveData<Boolean>() var iFunnyContentId = MutableLiveData<String>() private var iFunnyContentState: LiveData<IFunnyContent> = attachLiveDataToContentId(); init { likeState.addSource(iFunnyContentState) { likeState.postValue(it!!.hasLike) } } }
Utilizamos los componentes de arquitectura de Google: ViewModel y LiveData. Así es como se ve nuestro ViewModel. Aquí conectamos la actualización del objeto en el DAO con el estado de me gusta.
IFunnyContentViewController.java class IFunnyContentViewController @Inject constructor( private val likePostInteractor: LikePostInteractor, val viewModel: IFunnyContentViewModel) : ViewController { override fun attach(view: View) { viewModel.likeState.observe(lifecycleOwner, { updateLikeView(it!!) }) } fun onLikePost() { likePostInteractor.setContent(getContent()) likePostInteractor.execute() } }
ViewController, por un lado, se suscribe a cambiar el estado de los me gusta, por otro lado, inicia el paso del script que necesitamos.
Y eso es prácticamente todo el código que necesitamos. Queda por agregar el comportamiento de la Vista en sí con el me gusta y la implementación de su DAO; si usa Room, simplemente registre los campos en el objeto. Se ve bastante simple y efectivo.
Para resumir
JobScheduler, GCM Network Manager, FirebaseJobDispatcher:- no los uses
- no lea más artículos sobre ellos
- no mires informes
- No pienses cuál elegir.
Trabajo de Android por Evernote:- Dentro usarán el WorkManager;
- Los errores críticos se difuminan entre las soluciones.
WorkManager:- API LEVEL 9+;
- independiente de los servicios de Google Play;
- Encadenamiento / InputMergers;
- enfoque reactivo;
- soporte de Google (quiero creerlo).