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 APIs. 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 deberías revisar las áreas en las que los cambios de la plataforma podrían afectar a 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 向 ProfilingManager 添加了多个新的系统触发器,可帮助您收集深入的数据来调试性能问题。

新触发条件包括:

如需了解如何设置系统触发器,请参阅有关基于触发器的性能分析以及如何检索和上传配置文件和分析性能分析数据的文档。

应用异常性能分析触发器

Android 17 引入了设备端异常值检测服务,用于监控资源密集型行为和潜在的兼容性回归。此服务与 ProfilingManager 集成,可让您的应用接收由系统检测到的特定事件触发的性能分析工件。

使用 TRIGGER_TYPE_ANOMALY 触发器检测系统性能问题,例如 Binder 调用过多和内存用量过多。当应用超出操作系统定义的内存限制时,异常触发器可让开发者接收应用专属堆转储,以帮助识别和修复内存问题。此外,对于过多的 binder 垃圾内容,异常触发器会提供 binder 事务的堆栈采样分析。

此 API 回调会在系统强制执行任何操作之前发生。例如,它可以帮助开发者在应用因超出内存限制而被系统终止之前收集调试数据。

fun registerAnomalyTrigger(context: Context) {
    val profilingManager =
        context.getSystemService(ProfilingManager::class.java) ?: return
    val triggers = listOf(
        ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_ANOMALY).build()
    )
    val callbackExecutor: Executor = Executors.newSingleThreadExecutor()
    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode == ProfilingResult.ERROR_NONE) {
            // Upload profile result to server for further analysis
            profilingResult.resultFilePath?.let { setupProfileUploadWorker(it) }
        }
    }
    profilingManager.registerForAllProfilingResults(
        callbackExecutor,
        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 devuelve un mapa de los motivos por los que el trabajo estaba en un estado de ejecución pendiente y sus respectivas duraciones pendientes acumuladas. Este método une los métodos getPendingJobReasonsHistory() y getPendingJobReasons() para darte 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 devolver 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 porque no se cumplió la restricción de carga.

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

Android 17 presenta 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 bloqueos de activación continuos para realizar tareas periódicas, como las apps de mensajería que mantienen conexiones de socket.

Privacidad

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

Compatibilidad de la plataforma con 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)

El modo de Protección avanzada de Android ofrece a los usuarios de Android un nuevo y potente conjunto de funciones de seguridad, lo que marca un paso significativo en la protección de los usuarios, en especial aquellos que corren un mayor riesgo, contra ataques sofisticados. Diseñada como una función opcional, la AAPM se activa con un solo parámetro de configuración que los usuarios pueden activar en cualquier momento para aplicar un conjunto de protecciones de seguridad basadas en opiniones.

Estas configuraciones principales incluyen el bloqueo de la instalación de apps de fuentes desconocidas (transferencia local), la restricción de la señalización de datos por USB y la obligatoriedad del análisis de Google Play Protect, lo que reduce significativamente la superficie de ataque del dispositivo. Los desarrolladores pueden integrar esta función con la API de AdvancedProtectionManager para detectar el estado del modo, lo que permite que las aplicaciones adopten automáticamente una postura de seguridad reforzada o restrinjan la funcionalidad de alto riesgo cuando un usuario habilita el modo.

Firma de APKs con PQC

Android ahora admite un esquema de firma de APK híbrido para preparar la identidad de firma de tu app para el futuro contra la posible amenaza de ataques que usan 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 contra futuros ataques cuánticos y, al mismo tiempo, mantenga la retrocompatibilidad total con las versiones y los dispositivos de Android más antiguos 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 a 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 las herramientas de compilación de Android actualizadas (como apksigner) para rotar a una identidad híbrida, que combina una clave de PQC con una clave clásica nueva. (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.

Reproducción independiente del volumen del asistente

Android 17 为辅助应用引入了专用的 Google 助理音量流,以便使用 USAGE_ASSISTANT 进行播放。此项变更将 Google 助理音频与标准媒体流分离开来,让用户可以分别控制这两种音量。这样一来,您就可以在静音媒体播放的同时保持 Google 助理回答的可听性,反之亦然。

能够访问新 MODE_ASSISTANT_CONVERSATION 音频模式的辅助应用可以进一步提高音量控制的一致性。助理应用可以使用此模式向系统提供有关活跃助理会话的提示,确保可以在活跃的 USAGE_ASSISTANT 播放之外或通过连接的蓝牙外围设备控制助理音频流。

Handoff

Handoff es una nueva función y API que llegará a Android 17 y con la que los desarrolladores de apps pueden integrarse para proporcionar continuidad multidispositivo a sus usuarios. Permite que el usuario inicie una actividad de la app en un dispositivo Android y la transfiera a otro. Handoff se ejecuta en segundo plano en el dispositivo de un usuario y muestra las actividades disponibles de los otros dispositivos cercanos del usuario a través de varios puntos de entrada, como el selector y la barra de tareas, en el dispositivo receptor.

Las apps pueden designar Handoff para iniciar la misma app nativa para Android, si está instalada y disponible en el dispositivo receptor. En este flujo de app a app, se dirige al usuario a la actividad designada a través de un vínculo directo. Como alternativa, la transferencia de app a Web se puede ofrecer como una opción de resguardo o se puede implementar directamente con la transferencia de URL.

La compatibilidad con Handoff se implementa por actividad. Para habilitar Handoff, llama al método setHandoffEnabled() de la actividad. Es posible que se deban pasar datos adicionales junto con la transferencia para que la actividad que se vuelve a crear en el dispositivo receptor pueda restablecer el estado adecuado. Implementa la devolución de llamada onHandoffActivityDataRequested() para devolver un objeto HandoffActivityData que contenga detalles que especifiquen cómo Handoff debe controlar y recrear la actividad en el dispositivo receptor.

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

Con Android 17, Live Update lanza las APIs de Semantic Coloring para admitir colores con significado universal.

Las siguientes clases admiten el color semántico:

Colorear

  • Verde: Se asocia con la seguridad. Este color se debe usar para que las personas sepan que estás en una situación segura.
  • Naranja: Para indicar precaución y marcar peligros físicos. Este color se debe usar en la situación en la que los usuarios deben prestar atención para establecer un mejor parámetro de configuración de protección.
  • Rojo: Por lo general, indica peligro, detenerse. Se debe presentar en el caso de que se necesite la atención de las personas con urgencia.
  • Azul: Es un color neutro para el contenido informativo que debe destacarse de otro contenido.

En el siguiente ejemplo, se muestra cómo aplicar estilos semánticos al texto de una notificación:

  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 Downlink-TDoA de UWB para Android 17

下行链路到达时间差 (DL-TDoA) 测距功能可让设备通过测量信号的相对到达时间来确定其相对于多个锚点的位置。

以下代码段演示了如何初始化测距管理器、验证设备功能以及启动 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
        }
    }
}

带外 (OOB) 配置

以下代码段提供了 Wi-Fi 和 BLE 的 DL-TDoA OOB 配置数据示例:

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
};

如果您因缺少 OOB 配置而无法使用该配置,或者需要更改 OOB 配置中没有的默认值,则可以使用 DlTdoaRangingParams.Builder 构建参数,如以下代码段所示。您可以使用以下参数代替 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();