Optimización en segundo plano

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:

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 esta página, aprenderemos a 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 emisiones 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 emisiones 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 a JobInfo. Un ContentObserver controla el URI de contenido encapsulado. Si hay varios objetos TriggerContentUri 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ámetro notifyForDescendants que se pasa a registerContentObserver().

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, tu 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, usa getTriggeredContentUris() 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 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
    
  • 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