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 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 produce durante el inicio en frío de la app. Proporciona una muestra de pila de llamadas y un seguimiento del sistema en la respuesta.
  • TRIGGER_TYPE_OOM: El activador se produce cuando una app arroja una OutOfMemoryError y proporciona un volcado de montón de Java como respuesta.
  • TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE: Se activa cuando se cierra una app debido a un uso anormal y excesivo de la CPU, y proporciona una instantánea de un registro del sistema en segundo plano en ejecución como respuesta.
  • TRIGGER_TYPE_ANOMALY: Detecta anomalías en el rendimiento del sistema, como llamadas excesivas a Binder 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 subir perfiles y analizar los 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 en 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 excesivas de Binder y uso excesivo de memoria. Cuando una app incumple 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 corregir problemas de memoria. Además, en el caso de 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 que se apliquen las medidas por incumplimiento que impone 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.

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 引入了对加密客户端 Hello (ECH) 的平台支持,这是对网络通信的一项重大隐私增强功能。ECH 是一项 TLS 1.3 扩展,可在初始 TLS 握手期间加密服务器名称指示 (SNI)。这种加密有助于保护用户隐私,因为它可以让网络中介更难识别应用连接到的特定网域。

该平台现在包含网络库实现 ECH 所需的 API。这包括 DnsResolver 中的新功能,用于查询包含 ECH 配置的 HTTPS DNS 记录;以及 Conscrypt 的 SSLEngine 和 SSLSocket 中的新方法,用于在连接到网域时传入这些配置来启用 ECH。开发者可以通过网络安全配置文件中的新 <domainEncryption> 元素来配置 ECH 偏好设置,例如机会性地启用 ECH 或强制使用 ECH,这些设置可全局应用,也可按网域应用。

预计 HttpEngine、WebView 和 OkHttp 等热门联网库将在未来的更新中集成这些平台 API,从而使应用能够更轻松地采用 ECH 并增强用户隐私保护。

如需了解详情,请参阅加密的客户端 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 APKs con PQC

Android 现在支持混合 APK 签名方案,可保护应用的签名身份免受利用量子计算的潜在攻击威胁。此功能引入了一种新的 APK 签名方案,可让您将传统签名密钥(例如 RSA 或 EC)与新的后量子加密 (PQC) 算法 (ML-DSA) 搭配使用。

这种混合方法可确保您的应用在未来免受量子攻击,同时与依赖经典签名验证的旧版 Android 和设备保持完全向后兼容。

对开发者的影响

  • 使用 Play 应用签名的应用:如果您使用 Play 应用签名,则可以等待 Google Play 为您提供使用 Google Play 生成的 PQC 密钥升级混合签名的选项,从而确保您的应用受到保护,而无需手动管理密钥。
  • 使用自行管理密钥的应用:自行管理签名密钥的开发者可以利用更新后的 Android 构建工具(例如 apksigner)轮替到混合身份,将 PQC 密钥与新的经典密钥相结合。(您必须创建新的传统密钥,不能重复使用旧密钥。)

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

如果接收设备上已安装并可使用相同的原生 Android 应用,应用可以指定 Handoff 启动该应用。在此应用到应用流程中,用户会通过深层链接进入指定 activity。或者,应用到网页 Handoff 可作为后备选项提供,也可直接通过网址 Handoff 实现。

接力支持是按 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 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();