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. Para obtener más información, consulta las siguientes páginas:
- Cambios de comportamiento que afectan a las apps cuando se segmentan para Android 17
- Cambios de comportamiento que afectan a todas las apps, independientemente de
targetSdkVersion.
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 y depurar problemas de rendimiento.
Los nuevos activadores son los siguientes:
TRIGGER_TYPE_COLD_START: El activador se ejecuta durante el inicio en frío de la app. Proporciona una muestra de la pila de llamadas y un registro del sistema en la respuesta.TRIGGER_TYPE_OOM: El activador se activa cuando una app arroja unOutOfMemoryErrory proporciona un volcado de montón de Java en respuesta.TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE: El activador se produce cuando se cierra una app debido a un uso de CPU anormal y excesivo, y proporciona una muestra del registro de pila en la respuesta.
Para comprender cómo configurar el activador del sistema, consulta la documentación sobre el registro de perfil basado en activadores y cómo recuperar y analizar los datos de registro de perfil.
APIs de JobDebugInfo
Android 17 introduce 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 motivos por los que el trabajo estaba en un estado de ejecución pendiente y sus respectivas duraciones pendientes acumulativas. Este método une los métodos getPendingJobReasonsHistory() y getPendingJobReasons() para brindarte información sobre por qué un trabajo programado no se ejecuta según lo previsto, pero simplifica la recuperación de información al hacer 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 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
presenta una nueva variante de AlarmManager.setExactAndAllowWhileIdle que
acepta un OnAlarmListener en lugar de un PendingIntent. Este nuevo mecanismo basado en devolución 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 引入了对加密客户端 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 APK con PQC
Android 现在支持混合 APK 签名方案,以保护应用的签名身份免受利用量子计算的潜在攻击威胁。此功能引入了一种新的 APK 签名方案,可让您将经典签名密钥(例如 RSA 或 EC)与新的后量子密码 (PQC) 算法 (ML-DSA) 配对。
这种混合方法可确保您的应用在未来免受量子攻击,同时与依赖经典签名验证的旧版 Android 版本和设备保持完全向后兼容。
对开发者的影响
- 使用 Play 应用签名的应用 :如果您使用 Play 应用签名,可以等待 Google Play 为您提供使用 Google Play 生成的 PQC 密钥升级混合签名的选项,确保您的应用受到保护,而无需手动管理密钥。
- 使用自行管理的密钥的应用 :自行管理签名密钥的开发者可以利用更新后的 Android build 工具(例如 apksigner)轮替到混合身份,将 PQC 密钥与新的经典密钥相结合。(您必须创建新的经典密钥,无法重复使用旧密钥。)
Conectividad
Android 17 agrega las siguientes funciones para mejorar la conectividad de los dispositivos y las apps.
Redes satelitales restringidas
实现优化,使应用能够在低带宽卫星网络上有效运行。
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 presenta una transmisión de volumen del Asistente exclusiva para las apps del Asistente,
para la reproducción con USAGE_ASSISTANT. Este cambio desacopla el audio del Asistente de la transmisión de contenido multimedia estándar, lo que proporciona a los usuarios un control aislado sobre ambos volúmenes. Esto permite situaciones como silenciar la reproducción de contenido multimedia y, al mismo tiempo, mantener la audibilidad de las respuestas del Asistente, y viceversa.
Las apps del Asistente con acceso al nuevo MODE_ASSISTANT_CONVERSATION audio
mode 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 la transmisión del Asistente se pueda controlar fuera de la reproducción activa de USAGE_ASSISTANT o con periféricos Bluetooth conectados.
Handoff
Handoff es una nueva función y API que se incluirán en Android 17 y que los desarrolladores de apps pueden integrar para proporcionar continuidad entre dispositivos a sus usuarios. Permite al usuario iniciar una actividad de la app en un dispositivo Android y hacer la transición a otro dispositivo Android. Handoff se ejecuta en segundo plano en el dispositivo del 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 vincula directamente al usuario a la actividad designada. Como alternativa, el traspaso de la app a la Web se puede ofrecer como opción de resguardo o implementarse directamente con el traspaso de URL.
La compatibilidad con la transferencia se implementa por actividad. Para habilitar Handoff, llama al método setHandoffEnabled() para la actividad. Es posible que se deban pasar datos adicionales junto con la transferencia para que la actividad recreada en el dispositivo receptor pueda restablecer el estado adecuado. Implementa la devolución de llamada onHandoffActivityRequested() 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:
NotificationNotification.MetricNotification.ProgressStyle.PointNotification.ProgressStyle.Segment
Colorear
- Verde: Se asocia con la seguridad. Este color se debe usar en el caso en que se les informa a las personas que estás en una situación segura.
- Naranja: Para designar precaución y marcar peligros físicos Este color se debe usar en situaciones en las que los usuarios deben prestar atención para establecer una mejor configuración de protección.
- Rojo: Por lo general, indica peligro o detención. Se debe presentar en el caso en que se necesite la atención de las personas con urgencia.
- Azul: Color neutro para el contenido informativo que debe destacarse del resto.
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 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 Ranging Manager, 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.CapabilitiesCallback {
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 = intArrayOf(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.CapabilitiesCallback() {
@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());
int[] rangingRoundIndexes = new int[] {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 los 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();