Optimizaciones en segundo plano

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:

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 usa JobScheduler para programar tu trabajo. Cuando se cumplen las condiciones del trabajo, el sistema lo ejecuta en el JobService de tu app.

En este documento, obtendrás información sobre cómo usar métodos alternativos, como JobScheduler, para adaptar tu app a estas 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 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, 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() continuará recibiendo estas emisiones mientras se ejecute la app.

Cómo programar trabajos de red en conexiones no medidas

Cuando uses la clase JobInfo.Builder para compilar un objeto JobInfo, aplica el método setRequiredNetworkType() y pasa JobInfo.NETWORK_TYPE_UNMETERED como un 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 cumplen las condiciones del trabajo, la app recibe 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 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 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 . Debes crear objetos NetworkRequest con la clase NetworkRequest.Builder. Luego, registerNetworkCallback() 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() que se definió 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. En Android 7.0 (API nivel 24), se amplía JobInfo y JobParameters a fin de proporcionar una solución alternativa.

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 amplía la API 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 TriggerContentUri objeto a JobInfo. Un ContentObserver supervisa el URI de contenido encapsulado. Si hay varios objetos TriggerContentUri asociados con un trabajo, el sistema proporciona una devolución de llamada, incluso si informa un cambio en solo uno de los URI de contenido.
Agrega la marca TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS para activar el trabajo si cambia alguno de los descendientes del URI específico. Esta marca corresponde al parámetro notifyForDescendants pasado a registerContentObserver().

Nota: No es posible usar TriggerContentUri() junto con setPeriodic() o 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 procesar 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 (API nivel 24), también se amplía 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, usa getTriggeredContentUris() para obtener información sobre qué URI cambiaron.

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