Características y API

Android 17 incluye excelentes funciones y APIs para desarrolladores. En las siguientes secciones, se resumen estas funciones para ayudarte a comenzar a usar las APIs relacionadas.

Para obtener una lista detallada de las APIs nuevas, modificadas y quitadas, consulta el informe de diferencias de la API. Para obtener detalles sobre las nuevas APIs, consulta la referencia de la API de Android. Las nuevas APIs están destacadas para que sea más fácil identificarlas.

También debes revisar las áreas en las que los cambios en la plataforma podrían afectar tus apps. Si deseas obtener más información, consulta las siguientes páginas:

Funcionalidad principal

Android 17 agrega las siguientes funciones nuevas relacionadas con la funcionalidad principal de Android.

Nuevos activadores de ProfilingManager

Android 17 agrega varios activadores del sistema nuevos a ProfilingManager para ayudarte a recopilar datos detallados para depurar problemas de rendimiento.

Los nuevos activadores son los siguientes:

  • TRIGGER_TYPE_COLD_START: El activador se activa durante el inicio en frío de la app. Proporciona una muestra de la pila de llamadas y un seguimiento del sistema en la respuesta.
  • TRIGGER_TYPE_OOM: El activador se activa cuando una app arroja un OutOfMemoryError y proporciona un volcado de montón de Java en respuesta.
  • TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE: El activador se activa cuando se cierra una app debido a un uso anormal y excesivo de la CPU, y proporciona una muestra de la pila de llamadas en respuesta.
  • TRIGGER_TYPE_ANOMALY: Detecta anomalías en el rendimiento del sistema, como llamadas de Binder excesivas y uso excesivo de memoria.

Para comprender cómo configurar el activador del sistema, consulta la documentación sobre la generación de perfiles basada en activadores y cómo recuperar y analizar la documentación de datos de generación de perfiles.

Activador de generación de perfiles para anomalías de la app

Android 17 presenta un servicio de detección de anomalías integrado en el dispositivo que supervisa los comportamientos que consumen muchos recursos y las posibles regresiones de compatibilidad. Integrado con ProfilingManager, este servicio permite que tu app reciba artefactos de generación de perfiles activados por eventos específicos detectados por el sistema.

Usa el activador TRIGGER_TYPE_ANOMALY para detectar problemas de rendimiento del sistema como llamadas de Binder excesivas y uso excesivo de memoria. Cuando una app supera los límites de memoria definidos por el SO, el activador de anomalías permite que los desarrolladores reciban volcados de montón específicos de la app para ayudar a identificar y solucionar problemas de memoria. Además, para el spam excesivo de Binder, el activador de anomalías proporciona un perfil de muestreo de pila en las transacciones de Binder.

Esta devolución de llamada de la API se produce antes de cualquier aplicación impuesta por el sistema. Por ejemplo, puede ayudar a los desarrolladores a recopilar datos de depuración antes de que el sistema cierre la app por exceder los límites de memoria.

val profilingManager =
    applicationContext.getSystemService(ProfilingManager::class.java)
val triggers = ArrayList<ProfilingTrigger>()
triggers.add(ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
val mainExecutor: Executor = Executors.newSingleThreadExecutor()
val resultCallback = Consumer<ProfilingResult> { profilingResult ->
    if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) {
        // upload profile result to server for further analysis
        setupProfileUploadWorker(profilingResult.resultFilePath)
    }
    profilingManager.registerForAllProfilingResults(mainExecutor,
                                                    resultCallback)
    profilingManager.addProfilingTriggers(triggers)
}

APIs de JobDebugInfo

Android 17 presenta nuevas APIs de JobDebugInfo para ayudar a los desarrolladores a depurar sus trabajos de JobScheduler: por qué no se ejecutan, cuánto tiempo se ejecutaron y otra información agregada.

El primer método de las APIs de JobDebugInfo expandidas es getPendingJobReasonStats(), que muestra un mapa de motivos por los que el trabajo estaba en un estado de ejecución pendiente y sus respectivas duraciones pendientes acumulativas. Este método se une a los métodos getPendingJobReasonsHistory() y getPendingJobReasons() para brindarte información sobre por qué un trabajo programado no se ejecuta como se espera, pero simplifica la recuperación de información, ya que hace que la duración y el motivo del trabajo estén disponibles en un solo método.

Por ejemplo, para un jobId especificado, el método podría mostrar PENDING_JOB_REASON_CONSTRAINT_CHARGING y una duración de 60,000 ms, lo que indica que el trabajo estuvo pendiente durante 60,000 ms debido a que no se cumplió la restricción de carga.

Reduce los bloqueos de activación con compatibilidad de objetos de escucha para las alarmas de allow-while-idle

Android 17 introduce una nueva variante de AlarmManager.setExactAndAllowWhileIdle que acepta un OnAlarmListener en lugar de un PendingIntent. Este nuevo mecanismo basado en devoluciones de llamada es ideal para las apps que actualmente dependen de wakelocks continuos para realizar tareas periódicas, como las apps de mensajería que mantienen conexiones de sockets.

Privacidad

Android 17 incluye las siguientes funciones nuevas para mejorar la privacidad del usuario.

Compatibilidad con la plataforma Encrypted Client Hello (ECH)

Android 17 introduce compatibilidad de la plataforma con Encrypted Client Hello (ECH), una mejora significativa de la privacidad para las comunicaciones de red. ECH es una extensión de TLS 1.3 que encripta la indicación del nombre del servidor (SNI) durante el protocolo de enlace TLS inicial. Esta encriptación ayuda a proteger la privacidad del usuario, ya que dificulta que los intermediarios de la red identifiquen el dominio específico al que se conecta una app.

La plataforma ahora incluye las APIs necesarias para que las bibliotecas de redes implementen ECH. Esto incluye nuevas capacidades en DnsResolver para consultar registros DNS HTTPS que contengan configuraciones de ECH y nuevos métodos en los objetos SSLEngines y SSLSockets de Conscrypt para habilitar ECH pasando estas configuraciones cuando se conecta a un dominio. Los desarrolladores pueden configurar las preferencias de ECH, como habilitarla de forma oportunista o exigir su uso, a través del nuevo elemento <domainEncryption> dentro del archivo de configuración de seguridad de la red, que se aplica de forma global o por dominio.

Se espera que las bibliotecas de redes populares, como HttpEngine, WebView y OkHttp, integren estas APIs de la plataforma en futuras actualizaciones, lo que facilitará que las apps adopten ECH y mejoren la privacidad del usuario.

Para obtener más información, consulta la documentación de Encrypted Client Hello.

Selector de contactos de Android

Android 联系人选择工具是一个标准化的可浏览界面,供用户与您的应用分享联系人。该选择工具适用于搭载 Android 17(API 级别 37)或更高版本的设备,可提供一种可保护隐私的替代方案,以取代广泛的 READ_CONTACTS 权限。您的应用无需请求访问用户的整个地址簿,而是指定所需的数据字段(例如电话号码或电子邮件地址),然后用户选择要分享的特定联系人。这样,您的应用便只能读取所选数据,从而确保精细控制,同时提供一致的用户体验,并具有内置搜索、个人资料切换和多选功能,而无需构建或维护界面。

如需了解详情,请参阅联系人选择工具文档

Seguridad

Android 17 agrega las siguientes funciones nuevas para mejorar la seguridad de los dispositivos y las apps.

Modo de Protección avanzada de Android (AAPM)

Android 高级保护模式为 Android 用户提供了一套强大的新安全功能,标志着在保护用户(尤其是面临较高风险的用户)免遭复杂攻击方面迈出了重要一步。AAPM 是一项选择启用功能,只需进行一项配置设置即可激活。用户可以随时启用该功能,以应用一套主观的安全保护措施。

这些核心配置包括:禁止安装未知来源的应用(旁加载)、限制 USB 数据信号传输,以及强制执行 Google Play 保护机制扫描,从而显著减小设备的攻击面。 开发者可以使用 AdvancedProtectionManager API 与此功能集成,以检测模式的状态,从而使应用能够在用户选择启用此模式时自动采用强化型安全姿态或限制高风险功能。

Firma de APK con PQC

Ahora Android admite un esquema de firma de APK híbrido para proteger la identidad de firma de tu app contra la posible amenaza de ataques que usen la computación cuántica. Esta función presenta un nuevo esquema de firma de APK que te permite vincular una clave de firma clásica (como RSA o EC) con un nuevo algoritmo de criptografía poscuántica (PQC) (ML-DSA).

Este enfoque híbrido garantiza que tu app siga siendo segura ante futuros ataques cuánticos y, al mismo tiempo, mantiene la compatibilidad total con versiones anteriores de Android y dispositivos que dependen de la verificación de firmas clásica.

Impacto en los desarrolladores

  • Apps que usan la firma de apps de Play: Si usas la firma de apps de Play, puedes esperar a que Google Play te dé la opción de actualizar una firma híbrida con una clave de PQC generada por Google Play, lo que garantiza que tu app esté protegida sin necesidad de administrar las claves de forma manual.
  • Apps que usan claves autoadministradas: Los desarrolladores que administran sus propias claves de firma pueden usar herramientas de compilación de Android actualizadas (como apksigner) para rotar a una identidad híbrida, que combina una clave de PQC con una nueva clave clásica. (Debes crear una clave clásica nueva, no puedes reutilizar la anterior).

Conectividad

Android 17 agrega las siguientes funciones para mejorar la conectividad de los dispositivos y las apps.

Redes satelitales restringidas

Se implementaron optimizaciones para permitir que las apps funcionen de manera eficaz en redes satelitales con ancho de banda bajo.

Experiencia del usuario y la IU del sistema

Android 17 incluye los siguientes cambios para mejorar la experiencia del usuario.

Flujo de volumen exclusivo del Asistente

Android 17 introduce un flujo de volumen del Asistente exclusivo para las apps del Asistente, para la reproducción con USAGE_ASSISTANT. Este cambio desacopla el audio del Asistente de la transmisión de medios estándar, lo que les brinda a los usuarios un control aislado sobre ambos volúmenes. Esto permite situaciones como silenciar la reproducción de contenido multimedia y mantener la audibilidad de las respuestas del Asistente, y viceversa.

Las apps del Asistente que tienen acceso al nuevo modo de audio MODE_ASSISTANT_CONVERSATION pueden mejorar aún más la coherencia del control de volumen. Las apps del Asistente pueden usar este modo para proporcionar una sugerencia al sistema sobre una sesión activa del Asistente, lo que garantiza que el flujo del Asistente se pueda controlar fuera de la reproducción activa de USAGE_ASSISTANT o con periféricos Bluetooth conectados.

Handoff

切换是 Android 17 中新增的一项功能和 API,应用开发者可以将其集成到应用中,以便为用户提供跨设备连续性。它允许用户在一个 Android 设备上启动应用 activity,然后将其转移到另一个 Android 设备。Handoff 在用户设备的后台运行,并通过各种入口点(例如接收设备上的启动器和任务栏)显示用户附近其他设备上的可用活动。

应用可以指定 Handoff 来启动相同的原生 Android 应用(如果该应用已安装在接收设备上且可供使用)。在此应用到应用流程中,用户通过深层链接跳转到指定 activity。或者,应用到网站切换功能可以作为后备选项提供,也可以通过网址切换功能直接实现。

切换支持是按 activity 实现的。如需启用 Handoff,请针对 activity 调用 setHandoffEnabled() 方法。可能需要随切换传递其他数据,以便接收设备上重新创建的 activity 可以恢复适当的状态。实现 onHandoffActivityDataRequested() 回调以返回 HandoffActivityData 对象,该对象包含用于指定 Handoff 应如何处理并在接收设备上重新创建 activity 的详细信息。

Actualización en vivo: API de color semántico

在 Android 17 中,实时更新启动了语义着色 API,以支持具有通用含义的颜色。

以下类支持语义着色:

填色游戏

  • 绿色:与安全相关。此颜色应在以下情况下使用:让别人知道您处于安全状态。
  • 橙色:用于表示警告和标记物理危险。在用户需要注意以设置更好的保护设置的情况下,应使用此颜色。
  • 红色:通常表示危险、停止。它应在需要人们紧急关注的情况下显示。
  • 蓝色:中性颜色,适用于信息性内容,应与其他内容区分开来。

以下示例展示了如何将语义样式应用于通知中的文本:

  val ssb = SpannableStringBuilder()
        .append("Colors: ")
        .append("NONE", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_UNSPECIFIED), 0)
        .append(", ")
        .append("INFO", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_INFO), 0)
        .append(", ")
        .append("SAFE", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_SAFE), 0)
        .append(", ")
        .append("CAUTION", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_CAUTION), 0)
        .append(", ")
        .append("DANGER", Notification.createSemanticStyleAnnotation(SEMANTIC_STYLE_DANGER), 0)

    Notification.Builder(context, channelId)
          .setSmallIcon(R.drawable.ic_icon)
          .setContentTitle("Hello World!")
          .setContentText(ssb)
          .setOngoing(true)
              .setRequestPromotedOngoing(true)

API de UWB Downlink-TDoA para Android 17

La medición del tiempo de diferencia de llegada (TDoA) de vínculo descendente (DL-TDoA) permite que un dispositivo determine su posición relativa con respecto a varios anclajes midiendo los tiempos de llegada relativos de las señales.

En el siguiente fragmento, se muestra cómo inicializar el [Administrador de rango][ranging-manager-ref], verificar las capacidades del dispositivo y comenzar una sesión de DL-TDoA:

Kotlin

class RangingApp {

    fun initDlTdoa(context: Context) {
        // Initialize the Ranging Manager
        val rangingManager = context.getSystemService(RangingManager::class.java)

        // Register for device capabilities
        val capabilitiesCallback = object : RangingManager.RangingCapabilitiesCallback {
            override fun onRangingCapabilities(capabilities: RangingCapabilities) {
                // Make sure Dl-TDoA is supported before starting the session
                if (capabilities.uwbCapabilities != null && capabilities.uwbCapabilities!!.isDlTdoaSupported) {
                    startDlTDoASession(context)
                }
            }
        }
        rangingManager.registerCapabilitiesCallback(Executors.newSingleThreadExecutor(), capabilitiesCallback)
    }

    fun startDlTDoASession(context: Context) {

        // Initialize the Ranging Manager
        val rangingManager = context.getSystemService(RangingManager::class.java)

        // Create session and configure parameters
        val executor = Executors.newSingleThreadExecutor()
        val rangingSession = rangingManager.createRangingSession(executor, RangingSessionCallback())
        val rangingRoundIndexes = byteArrayOf(0)
        val config: ByteArray = byteArrayOf() // OOB config data
        val params = DlTdoaRangingParams.createFromFiraConfigPacket(config, rangingRoundIndexes)

        val rangingDevice = RangingDevice.Builder().build()
        val rawTagDevice = RawRangingDevice.Builder()
            .setRangingDevice(rangingDevice)
            .setDlTdoaRangingParams(params)
            .build()

        val dtTagConfig = RawDtTagRangingConfig.Builder(rawTagDevice).build()

        val preference = RangingPreference.Builder(DEVICE_ROLE_DT_TAG, dtTagConfig)
            .setSessionConfig(SessionConfig.Builder().build())
            .build()

        // Start the ranging session
        rangingSession.start(preference)
    }
}

private class RangingSessionCallback : RangingSession.Callback {
    override fun onDlTdoaResults(peer: RangingDevice, measurement: DlTdoaMeasurement) {
        // Process measurement results here
    }
}

Java

public class RangingApp {

    public void initDlTdoa(Context context) {

        // Initialize the Ranging Manager
        RangingManager rangingManager = context.getSystemService(RangingManager.class);

        // Register for device capabilities
        RangingManager.CapabilitiesCallback capabilitiesCallback = new RangingManager.RangingCapabilitiesCallback() {
            @Override
            public void onRangingCapabilities(RangingCapabilities capabilities) {
                // Make sure Dl-TDoA is supported before starting the session
                if (capabilities.getUwbCapabilities() != null && capabilities.getUwbCapabilities().isDlTdoaSupported()) {
                    startDlTDoASession(context);
                }
            }
        };
        rangingManager.registerCapabilitiesCallback(Executors.newSingleThreadExecutor(), capabilitiesCallback);
    }

    public void startDlTDoASession(Context context) {
        RangingManager rangingManager = context.getSystemService(RangingManager.class);

        // Create session and configure parameters
        Executor executor = Executors.newSingleThreadExecutor();
        RangingSession rangingSession = rangingManager.createRangingSession(executor, new RangingSessionCallback());
        byte[] rangingRoundIndexes = new byte[] {0};
        byte[] config = new byte[0]; // OOB config data
        DlTdoaRangingParams params = DlTdoaRangingParams.createFromFiraConfigPacket(config, rangingRoundIndexes);

        RangingDevice rangingDevice = new RangingDevice.Builder().build();
        RawRangingDevice rawTagDevice = new RawRangingDevice.Builder()
                .setRangingDevice(rangingDevice)
                .setDlTdoaRangingParams(params)
                .build();

        RawDtTagRangingConfig dtTagConfig = new RawDtTagRangingConfig.Builder(rawTagDevice).build();

        RangingPreference preference = new RangingPreference.Builder(DEVICE_ROLE_DT_TAG, dtTagConfig)
                .setSessionConfig(new SessionConfig.Builder().build())
                .build();

        // Start the ranging session
        rangingSession.start(preference);
    }

    private static class RangingSessionCallback implements RangingSession.Callback {

        @Override
        public void onDlTdoaResults(RangingDevice peer, DlTdoaMeasurement measurement) {
            // Process measurement results here
        }
    }
}

Configuraciones fuera de banda (OOB)

El siguiente fragmento proporciona un ejemplo de datos de configuración OOB de DL-TDoA para Wi-Fi y BLE:

Java

// Wifi Configuration
byte[] wifiConfig = {
    (byte) 0xDD, (byte) 0x2D, (byte) 0x5A, (byte) 0x18, (byte) 0xFF, // Header
    (byte) 0x5F, (byte) 0x19, // FiRa Sub-Element
    (byte) 0x02, (byte) 0x00, // Profile ID
    (byte) 0x06, (byte) 0x02, (byte) 0x20, (byte) 0x08, // MAC Address
    (byte) 0x14, (byte) 0x01, (byte) 0x0C, // Preamble Index
    (byte) 0x27, (byte) 0x02, (byte) 0x08, (byte) 0x07, // Vendor ID
    (byte) 0x28, (byte) 0x06, (byte) 0xCA, (byte) 0xC8, (byte) 0xA6, (byte) 0xF7, (byte) 0x6F, (byte) 0x08, // Static STS IV
    (byte) 0x08, (byte) 0x02, (byte) 0x60, (byte) 0x09, // Slot Duration
    (byte) 0x1B, (byte) 0x01, (byte) 0x0A, // Slots per RR
    (byte) 0x09, (byte) 0x04, (byte) 0xE8, (byte) 0x03, (byte) 0x00, (byte) 0x00, // Duration
    (byte) 0x9F, (byte) 0x04, (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01  // Session ID
};

// BLE Configuration
byte[] bleConfig = {
    (byte) 0x2D, (byte) 0x16, (byte) 0xF4, (byte) 0xFF, // Header
    (byte) 0x5F, (byte) 0x19, // FiRa Sub-Element
    (byte) 0x02, (byte) 0x00, // Profile ID
    (byte) 0x06, (byte) 0x02, (byte) 0x20, (byte) 0x08, // MAC Address
    (byte) 0x14, (byte) 0x01, (byte) 0x0C, // Preamble Index
    (byte) 0x27, (byte) 0x02, (byte) 0x08, (byte) 0x07, // Vendor ID
    (byte) 0x28, (byte) 0x06, (byte) 0xCA, (byte) 0xC8, (byte) 0xA6, (byte) 0xF7, (byte) 0x6F, (byte) 0x08, // Static STS IV
    (byte) 0x08, (byte) 0x02, (byte) 0x60, (byte) 0x09, // Slot Duration
    (byte) 0x1B, (byte) 0x01, (byte) 0x0A, // Slots per RR
    (byte) 0x09, (byte) 0x04, (byte) 0xE8, (byte) 0x03, (byte) 0x00, (byte) 0x00, // Duration
    (byte) 0x9F, (byte) 0x04, (byte) 0x67, (byte) 0x45, (byte) 0x23, (byte) 0x01  // Session ID
};

Si no puedes usar una configuración OOB porque falta o si necesitas cambiar valores predeterminados que no están en la configuración OOB, puedes compilar parámetros con DlTdoaRangingParams.Builder como se muestra en el siguiente fragmento. Puedes usar estos parámetros en lugar de DlTdoaRangingParams.createFromFiraConfigPacket():

Kotlin

val dlTdoaParams = DlTdoaRangingParams.Builder(1)
    .setComplexChannel(UwbComplexChannel.Builder()
            .setChannel(9).setPreambleIndex(10).build())
    .setDeviceAddress(deviceAddress)
    .setSessionKeyInfo(byteArrayOf(0x01, 0x02, 0x03, 0x04))
    .setRangingIntervalMillis(240)
    .setSlotDuration(UwbRangingParams.DURATION_2_MS)
    .setSlotsPerRangingRound(20)
    .setRangingRoundIndexes(byteArrayOf(0x01, 0x05))
    .build()

Java

DlTdoaRangingParams dlTdoaParams = new DlTdoaRangingParams.Builder(1)
    .setComplexChannel(new UwbComplexChannel.Builder()
            .setChannel(9).setPreambleIndex(10).build())
    .setDeviceAddress(deviceAddress)
    .setSessionKeyInfo(new byte[]{0x01, 0x02, 0x03, 0x04})
    .setRangingIntervalMillis(240)
    .setSlotDuration(UwbRangingParams.DURATION_2_MS)
    .setSlotsPerRangingRound(20)
    .setRangingRoundIndexes(new byte[]{0x01, 0x05})
    .build();

[ranging-manager-ref]: /reference/android/ranging/RangingManager