Los procesos en segundo plano pueden consumir mucha memoria y batería. Por ejemplo, una emisión implícita puede iniciar muchos procesos en segundo plano que se registraron para recibirla, incluso si esos procesos no realizan mucho trabajo. Esto puede afectar de manera considerable tanto el rendimiento del dispositivo como la experiencia del usuario.
Para solucionar este problema, en Android 7.0 (API nivel 24) se aplican las siguientes restricciones:
- Las apps que se orientan a Android 7.0 (API nivel 24) y versiones posteriores no recibirán emisiones de
CONNECTIVITY_ACTION
si declaran su receptor de emisión en el manifiesto. Las apps seguirán recibiendo emisiones deCONNECTIVITY_ACTION
si registran suBroadcastReceiver
conContext.registerReceiver()
y ese contexto sigue siendo válido. - Las apps no pueden enviar ni recibir emisiones
ACTION_NEW_PICTURE
niACTION_NEW_VIDEO
. Esta optimización afecta a todas las apps, no solo a las orientadas a Android 7.0 (API nivel 24).
Si tu app usa alguno de estos intents, debes quitarles las dependencias tan pronto como sea posible para orientar de manera correcta a dispositivos con Android 7.0 o versiones posteriores. El marco de trabajo de Android proporciona varias soluciones para mitigar la necesidad de estas emisiones implícitas. Por ejemplo, JobScheduler
y el nuevo WorkManager proporcionan mecanismos eficaces para programar operaciones de red cuando se cumplen condiciones específicas, como una conexión a una red no medida. Ahora también puedes usar JobScheduler
para reaccionar a los cambios realizados en los proveedores de contenido. Los objetos JobInfo
encapsulan los parámetros que JobScheduler
usa para programar el trabajo. Cuando se cumplen las condiciones del trabajo, el sistema lo ejecuta en el JobService
.
En este documento, aprenderemos cómo usar métodos alternativos, como JobScheduler
, para adaptar tu app a esas nuevas restricciones.
Restricciones iniciadas por el usuario
A partir de Android 9 (API nivel 28), si una app muestra comportamientos perjudiciales como los que se describen en Android vitals, el sistema le solicita al usuario que restrinja el acceso de esa app a los recursos del sistema.
Si el sistema nota que una app está consumiendo recursos excesivos, le envía una notificación al usuario y le da la opción de restringir las acciones de la app. Entre los comportamientos que pueden activar la notificación, se incluyen los siguientes:
- Bloqueos de activación excesivos: 1 bloqueo de activación parcial retenido durante una hora cuando la pantalla está apagada.
- Servicios en segundo plano excesivos: Si la app está orientada a niveles de API inferiores a 26 y tiene demasiados servicios en segundo plano.
Las restricciones precisas que se imponen son determinadas por el fabricante del dispositivo. Por ejemplo, en las compilaciones de AOSP, las apps restringidas no pueden ejecutar tareas, activar alarmas ni usar la red, excepto cuando están en primer plano. (A fin de conocer los criterios que se usan para determinar si una app está en primer plano, consulta Limitaciones del servicio en segundo plano). Las restricciones específicas se enumeran en Restricciones de administración de energía.
Restricciones para la recepción de emisiones de actividad de red
Las apps que se orienten a Android 7.0 (API nivel 24) no recibirán emisiones de CONNECTIVITY_ACTION
si se registran para recibirlas en su manifiesto, y no se iniciarán los procesos que dependan de esta emisión. Esto podría ser un problema para las apps que deseen detectar los cambios de red o realizar actividades de red masivas cuando se conecte el dispositivo a una red no medida. En el marco de trabajo de Android, ya existen varias soluciones para evitar esta restricción, pero elegir la correcta depende del objetivo de tu app.
Nota: Un BroadcastReceiver
registrado con Context.registerReceiver()
continúa recibiendo estas emisiones mientras se ejecuta la app.
Cómo programar trabajos de red en conexiones no medidas
Cuando uses la clase JobInfo.Builder
para compilar tu objeto JobInfo
, aplica el método setRequiredNetworkType()
y pasa JobInfo.NETWORK_TYPE_UNMETERED
como parámetro de trabajo. En el siguiente ejemplo de código, se programa un servicio para que se ejecute cuando el dispositivo se conecte a una red no medida y se cargue:
Kotlin
const val MY_BACKGROUND_JOB = 0 ... fun scheduleJob(context: Context) { val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val job = JobInfo.Builder( MY_BACKGROUND_JOB, ComponentName(context, MyJobService::class.java) ) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .build() jobScheduler.schedule(job) }
Java
public static final int MY_BACKGROUND_JOB = 0; ... public static void scheduleJob(Context context) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); JobInfo job = new JobInfo.Builder( MY_BACKGROUND_JOB, new ComponentName(context, MyJobService.class)) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .build(); js.schedule(job); }
Cuando se cumplan las condiciones para el trabajo, la app recibirá una devolución de llamada para ejecutar el método onStartJob()
en la JobService.class
especificada. Para ver más ejemplos de la implementación de JobScheduler
, consulta la app de ejemplo de JobScheduler.
Una nueva alternativa a JobScheduler es WorkManager, una API que te permite programar tareas en segundo plano que necesitan una finalización garantizada, independientemente de si el proceso de la app está activo o no. WorkManager elige la forma adecuada de ejecutar el trabajo (ya sea directamente en un subproceso del proceso de la app o en JobScheduler, FirebaseJobDispatcher o AlarmManager) según factores como el nivel de API del dispositivo. Además, WorkManager no requiere Servicios de Play y proporciona varias funciones avanzadas, como encadenar tareas o verificar el estado de una tarea. Para obtener más información, consulta WorkManager.
Cómo supervisar la conectividad de red mientras se ejecuta la app
Las apps que están en ejecución pueden detectar CONNECTIVITY_CHANGE
con un BroadcastReceiver
registrado. Sin embargo, la API de ConnectivityManager
proporciona un método más sólido para solicitar una devolución de llamada solo cuando se cumplen las condiciones de red especificadas.
Los objetos NetworkRequest
definen los parámetros de la validación de llamada de la red en términos de NetworkCapabilities
. Creas objetos NetworkRequest
con la clase NetworkRequest.Builder
. registerNetworkCallback()
luego pasa el objeto NetworkRequest
al sistema. Cuando se cumplen las condiciones de red, la app recibe una devolución de llamada para ejecutar el método onAvailable()
definido en su clase ConnectivityManager.NetworkCallback
.
La app continúa recibiendo devoluciones de llamada hasta que se cierra o llama a unregisterNetworkCallback()
.
Restricciones para la recepción de emisiones de imagen y video
En Android 7.0 (API nivel 24), las apps no pueden enviar ni recibir emisiones de ACTION_NEW_PICTURE
ni de ACTION_NEW_VIDEO
. Esta restricción ayuda a aliviar el rendimiento y afecta la experiencia del usuario cuando se deben activar varias apps para procesar una nueva imagen o un nuevo video. Como alternativa, Android 7.0 (API nivel 24) extiende JobInfo
y JobParameters
.
Cómo activar trabajos durante cambios de URI de contenido
Para activar trabajos durante cambios de URI de contenido, en Android 7.0 (API nivel 24) se extiende la API de JobInfo
con los siguientes métodos:
-
JobInfo.TriggerContentUri()
- Encapsula los parámetros necesarios para activar un trabajo durante los cambios de URI de contenido.
-
JobInfo.Builder.addTriggerContentUri()
-
Pasa un objeto
TriggerContentUri
aJobInfo
. UnContentObserver
controla el URI de contenido encapsulado. Si hay varios objetosTriggerContentUri
asociados a un trabajo, el sistema realizará una devolución de llamada incluso aunque se informe un cambio en un solo URI de contenido. -
Si cambia algún desencadenante del URI determinado, agrega el
TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS
para activar el trabajo. Esta marca corresponde al parámetronotifyForDescendants
que se pasa aregisterContentObserver()
.
Nota: No se puede usar TriggerContentUri()
junto con setPeriodic()
ni setPersisted()
. Para supervisar de forma continua los cambios en el contenido, programa una nueva JobInfo
antes de que el JobService
de la app termine de controlar la devolución de llamada más reciente.
En el siguiente código de muestra, se programa un trabajo para que se active cuando el sistema informe un cambio en el URI de contenido, MEDIA_URI
:
Kotlin
const val MY_BACKGROUND_JOB = 0 ... fun scheduleJob(context: Context) { val jobScheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler val job = JobInfo.Builder( MY_BACKGROUND_JOB, ComponentName(context, MediaContentJob::class.java) ) .addTriggerContentUri( JobInfo.TriggerContentUri( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS ) ) .build() jobScheduler.schedule(job) }
Java
public static final int MY_BACKGROUND_JOB = 0; ... public static void scheduleJob(Context context) { JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); JobInfo.Builder builder = new JobInfo.Builder( MY_BACKGROUND_JOB, new ComponentName(context, MediaContentJob.class)); builder.addTriggerContentUri( new JobInfo.TriggerContentUri(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS)); js.schedule(builder.build()); }
Cuando el sistema informa un cambio en los URI especificados, la app recibe una devolución de llamada y se pasa un objeto JobParameters
al método onStartJob()
en MediaContentJob.class
.
Cómo determinar qué autoridades de contenido activaron un trabajo
En Android 7.0 (API nivel 24), también se extiende JobParameters
para permitir que las apps reciban información útil sobre qué autoridades de contenido y URI activaron el trabajo:
-
Uri[] getTriggeredContentUris()
- Muestra un arreglo de URI que activaron el trabajo. Este será
null
si ninguno de los URI activó el trabajo (por ejemplo, se activó el trabajo debido a una fecha límite o a otro motivo), o si el número de URI modificados es mayor que 50. -
String[] getTriggeredContentAuthorities()
- Muestra un arreglo de strings de autoridades de contenido que activaron el trabajo.
Si el arreglo que se muestra no es
null
, usagetTriggeredContentUris()
para recuperar los detalles de los URI que se modificaron.
En el siguiente código de muestra, se anula el método JobService.onStartJob()
y se registran las autoridades de contenido y los URI que activaron el trabajo:
Kotlin
override fun onStartJob(params: JobParameters): Boolean { StringBuilder().apply { append("Media content has changed:\n") params.triggeredContentAuthorities?.also { authorities -> append("Authorities: ${authorities.joinToString(", ")}\n") append(params.triggeredContentUris?.joinToString("\n")) } ?: append("(No content)") Log.i(TAG, toString()) } return true }
Java
@Override public boolean onStartJob(JobParameters params) { StringBuilder sb = new StringBuilder(); sb.append("Media content has changed:\n"); if (params.getTriggeredContentAuthorities() != null) { sb.append("Authorities: "); boolean first = true; for (String auth : params.getTriggeredContentAuthorities()) { if (first) { first = false; } else { sb.append(", "); } sb.append(auth); } if (params.getTriggeredContentUris() != null) { for (Uri uri : params.getTriggeredContentUris()) { sb.append("\n"); sb.append(uri); } } } else { sb.append("(No content)"); } Log.i(TAG, sb.toString()); return true; }
Cómo optimizar aún más tu app
Optimizar tus apps para que se ejecuten en dispositivos con poca memoria, o en condiciones de poca memoria, puede mejorar el rendimiento y la experiencia del usuario. Si quitas las dependencias de los servicios en segundo plano y los receptores de emisión implícita registrados en manifiestos, tu app funcionará mejor en esos dispositivos. Aunque Android 7.0 (API nivel 24) toma medidas para reducir algunos de estos problemas, te recomendamos optimizar la app a fin de que se ejecute sin usar en absoluto estos procesos en segundo plano.
Android 7.0 (API nivel 24) presenta algunos comandos adicionales de Android Debug Bridge (ADB) que puedes usar para probar el comportamiento de la app con esos procesos en segundo plano inhabilitados:
- Para simular condiciones en las que las emisiones implícitas y los servicios en segundo plano no están disponibles, ingresa el siguiente comando:
-
$ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND ignore
- Para volver a habilitar las emisiones implícitas y los servicios en segundo plano, ingresa el siguiente comando:
-
$ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow