Los procesos en segundo plano pueden consumir mucha memoria y batería. Por ejemplo, una transmisió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 (nivel de API 24) se aplican las siguientes restricciones:
- Las apps que se orientan a Android 7.0 (nivel de API 24) y versiones posteriores no recibirán transmisiones de
CONNECTIVITY_ACTION
si declaran su receptor de emisión en el manifiesto. Las apps seguirán recibiendo transmisiones deCONNECTIVITY_ACTION
si registran suBroadcastReceiver
conContext.registerReceiver()
, y ese contexto sigue siendo válido. - Las apps no pueden enviar ni recibir transmisiones
ACTION_NEW_PICTURE
niACTION_NEW_VIDEO
. Esta optimización afecta a todas las apps, no solo a las orientadas a Android 7.0 (nivel de API 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 framework de Android proporciona varias soluciones para mitigar la necesidad de estas transmisiones 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
de tu app.
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
En la página Uso de batería de la configuración del sistema, el usuario puede elegir entre las siguientes opciones:
- Sin restricciones: Permite los trabajos en segundo plano, que podrían consumir más batería.
- Optimizado (predeterminada): Optimiza la capacidad de una app para realizar trabajos en segundo plano, según la forma en que el usuario interactúe con la app.
- Restringida: Evita completamente que una app se ejecute en segundo plano. Las apps podrían no funcionar como deberían.
Si una app muestra algunos de los comportamientos inadecuados que se describen en Android vitals, el sistema puede solicitarle 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 servicios en segundo plano excesivos.
Las restricciones precisas que se imponen son determinadas por el fabricante del dispositivo. Por ejemplo, en las compilaciones de AOSP que se ejecutan en Android 9 (nivel de API 28) o versiones posteriores, las apps que se ejecutan en segundo plano con el estado "restringido" tienen las siguientes limitaciones:
- No pueden iniciar servicios en primer plano.
- Los servicios en primer plano existentes se quitan de este primer plano.
- No se activan alarmas.
- No se ejecutan trabajos.
Además, si una app se orienta a Android 13 (nivel de API 33) o versiones posteriores y se encuentra en estado "restringido", el sistema no entregará la transmisión de BOOT_COMPLETED
ni la de LOCKED_BOOT_COMPLETED
hasta que la app se inicie por otros motivos.
Las restricciones específicas se enumeran en Restricciones para la administración de batería.
Restricciones para la recepción de transmisiones de actividad de red
Las apps que se orienten a Android 7.0 (nivel de API 24) no recibirán transmisiones de CONNECTIVITY_ACTION
si se registran para recibirlas en su manifiesto, y no se iniciarán los procesos que dependan de esta transmisió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 framework 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 transmisiones 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 la siguiente muestra 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 a fin de 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 devolució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 transmisiones de imagen y video
En Android 7.0 (nivel de API 24), las apps no pueden enviar ni recibir transmisiones 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 (nivel de API 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 (nivel de API 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 elemento subordinado del URI determinado, agrega la marca
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 de contenido 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 (nivel de API 24), también se extiende JobParameters
para permitir que las apps reciban información útil sobre qué autoridades de contenido y URIs activaron el trabajo:
-
Uri[] getTriggeredContentUris()
-
Muestra un arreglo de URIs que activaron el trabajo. Este será
null
si ninguno de los URIs 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 URIs modificados es mayor que 50. -
String[] getTriggeredContentAuthorities()
-
Muestra un array de cadenas de autoridades de contenido que activaron el trabajo.
Si el arreglo que se muestra no es
null
, usagetTriggeredContentUris()
para recuperar los detalles de los URIs 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 transmisión implícita registrados en manifiestos, tu app funcionará mejor en esos dispositivos. Aunque Android 7.0 (nivel de API 24) toma medidas para reducir algunos de estos problemas, te recomendamos que optimices la app a fin de que se ejecute sin usar en absoluto estos procesos en segundo plano.
Los siguientes comandos de Android Debug Bridge (ADB) pueden ayudarte a probar el comportamiento de la app con procesos en segundo plano inhabilitados:
- Para simular condiciones en las que las transmisiones 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 transmisiones implícitas y los servicios en segundo plano, ingresa el siguiente comando:
-
$ adb shell cmd appops set <package_name> RUN_IN_BACKGROUND allow
- Puedes simular que el usuario coloca la app en el estado "restringido" para el uso de batería en segundo plano. Esta configuración impide que la app se ejecute en segundo plano. Para ello, ejecuta el siguiente comando en una ventana de la terminal:
-
$ adb shell cmd appops set <PACKAGE_NAME> RUN_ANY_IN_BACKGROUND deny