Android 17 представляет множество новых функций и API для разработчиков. В следующих разделах приведено краткое описание этих функций, которое поможет вам начать работу с соответствующими API.
Подробный список новых, измененных и удаленных API см. в отчете об изменениях API . Подробную информацию о новых API см. в справочнике Android API — новые API выделены для большей наглядности.
Также следует проанализировать области, где изменения платформы могут повлиять на ваши приложения. Для получения дополнительной информации см. следующие страницы:
- Изменения в поведении, влияющие на приложения при использовании Android 17.
- Изменения в поведении, затрагивающие все приложения независимо от
targetSdkVersion.
Основная функциональность
В Android 17 добавлены следующие новые функции, относящиеся к основным возможностям Android.
Новые триггеры ProfilingManager
В Android 17 в ProfilingManager добавлено несколько новых системных триггеров, которые помогут вам собрать подробные данные для отладки проблем с производительностью.
Новые триггеры:
-
TRIGGER_TYPE_COLD_START: Триггер срабатывает во время холодного запуска приложения. В ответе он предоставляет как пример стека вызовов, так и трассировку системы. -
TRIGGER_TYPE_OOM: Триггер срабатывает, когда приложение генерирует ошибкуOutOfMemoryErrorи в ответ предоставляет дамп кучи Java. -
TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE: Триггер срабатывает, когда приложение завершает работу из-за ненормального и чрезмерного использования ЦП, и в ответ предоставляет пример стека вызовов. -
TRIGGER_TYPE_ANOMALY: Обнаруживает аномалии производительности системы, такие как чрезмерное количество вызовов binder и чрезмерное использование памяти.
Чтобы понять, как настроить системный триггер, ознакомьтесь с документацией по профилированию на основе триггеров , а также с документацией по получению и анализу данных профилирования .
Триггер профилирования для выявления аномалий в приложении
В Android 17 появилась встроенная служба обнаружения аномалий на устройстве, которая отслеживает ресурсоемкое поведение и потенциальные проблемы совместимости. Интегрированная с ProfilingManager , эта служба позволяет вашему приложению получать артефакты профилирования, запускаемые определенными событиями, обнаруженными системой.
Используйте триггер TRIGGER_TYPE_ANOMALY для обнаружения проблем с производительностью системы, таких как чрезмерное количество вызовов binder и чрезмерное использование памяти. Когда приложение превышает установленные ОС ограничения памяти, триггер anomaly позволяет разработчикам получать дампы кучи, специфичные для приложения, что помогает выявлять и устранять проблемы с памятью. Кроме того, при чрезмерном использовании binder триггер anomaly предоставляет профиль выборки стека для транзакций binder.
Этот API-вызов происходит до применения каких-либо системных мер. Например, он может помочь разработчикам собрать отладочные данные до того, как приложение будет завершено системой за превышение лимитов памяти.
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)
}
API JobDebugInfo
Android 17 引入了新的 JobDebugInfo API,可帮助开发者调试其 JobScheduler 作业,了解作业未运行的原因、运行时长以及其他汇总信息。
扩展后的 JobDebugInfo API 的第一个方法是 getPendingJobReasonStats(),该方法会返回一个映射,其中包含作业处于待执行状态的原因及其各自的累计待执行时长。此方法将 getPendingJobReasonsHistory() 和 getPendingJobReasons() 方法联接在一起,可让您了解预定作业未按预期运行的原因,但通过在单个方法中同时提供时长和作业原因,简化了信息检索。
例如,对于指定的 jobId,该方法可能会返回 PENDING_JOB_REASON_CONSTRAINT_CHARGING 和 60000 毫秒的时长,表示作业因未满足充电约束而处于等待状态 60000 毫秒。
Уменьшите количество блокировок пробуждения благодаря поддержке слушателей для будильников, разрешающих работу в режиме ожидания.
В Android 17 представлен новый вариант метода AlarmManager.setExactAndAllowWhileIdle , который принимает OnAlarmListener вместо PendingIntent . Этот новый механизм на основе обратных вызовов идеально подходит для приложений, которые в настоящее время полагаются на непрерывную блокировку пробуждения для выполнения периодических задач, например, для мессенджеров, поддерживающих сокетные соединения.
Конфиденциальность
В Android 17 добавлены следующие новые функции для повышения конфиденциальности пользователей.
Поддержка платформы Encrypted Client Hello (ECH).
В Android 17 появилась поддержка технологии Encrypted Client Hello (ECH), которая значительно повышает конфиденциальность сетевых коммуникаций. ECH — это расширение TLS 1.3, которое шифрует Server Name Indication (SNI) во время первоначального установления TLS-соединения. Это шифрование помогает защитить конфиденциальность пользователей, затрудняя сетевым посредникам идентификацию конкретного домена, к которому подключается приложение.
Теперь платформа включает необходимые API для сетевых библиотек, позволяющие реализовать ECH. Это включает новые возможности в DnsResolver для запроса записей HTTPS DNS, содержащих конфигурации ECH, а также новые методы в SSLEngines и SSLSockets Conscrypt для включения ECH путем передачи этих конфигураций при подключении к домену. Разработчики могут настраивать параметры ECH, например, включать его по мере необходимости или предписывать его использование, с помощью нового элемента <domainEncryption> в файле конфигурации сетевой безопасности, применимого глобально или для каждого домена отдельно.
Ожидается, что популярные сетевые библиотеки, такие как HttpEngine, WebView и OkHttp, интегрируют эти API платформы в будущих обновлениях, что упростит внедрение ECH в приложения и повысит конфиденциальность пользователей.
Для получения более подробной информации см. документацию по протоколу Encrypted Client Hello .
средство выбора контактов для Android
Интерфейс выбора контактов Android Contact Picker — это стандартизированный, удобный для просмотра интерфейс, позволяющий пользователям делиться контактами с вашим приложением. Доступный на устройствах под управлением Android 17 (уровень API 37) или выше, этот интерфейс предлагает альтернативу широкому разрешению READ_CONTACTS , обеспечивающую конфиденциальность. Вместо запроса доступа ко всей адресной книге пользователя, ваше приложение указывает необходимые поля данных, такие как номера телефонов или адреса электронной почты, а пользователь выбирает конкретные контакты для обмена. Это предоставляет вашему приложению доступ на чтение только к выбранным данным, обеспечивая детальный контроль и предоставляя единообразный пользовательский опыт со встроенным поиском, переключением профилей и возможностью множественного выбора без необходимости разработки или поддержки пользовательского интерфейса.
Для получения более подробной информации см. документацию по средству выбора контактов .
Безопасность
В Android 17 добавлены следующие новые функции для повышения безопасности устройств и приложений.
Расширенный режим защиты Android (AAPM)
Android 高级保护模式为 Android 用户提供了一套强大的新安全功能,标志着在保护用户(尤其是面临较高风险的用户)免遭复杂攻击方面迈出了重要一步。AAPM 是一项选择启用功能,只需进行一项配置设置即可激活。用户可以随时启用该功能,以应用一套主观的安全保护措施。
这些核心配置包括:禁止安装未知来源的应用(旁加载)、限制 USB 数据信号传输,以及强制执行 Google Play 保护机制扫描,从而显著减小设备的攻击面。
开发者可以使用 AdvancedProtectionManager API 与此功能集成,以检测模式的状态,从而使应用能够在用户选择启用此模式时自动采用强化型安全姿态或限制高风险功能。
Подписание APK-файлов PQC
Android 现在支持混合 APK 签名方案,以保护应用的签名身份免受利用量子计算的攻击的潜在威胁。此功能引入了一种新的 APK 签名方案,可让您将经典签名密钥(例如 RSA 或 EC)与新的后量子加密 (PQC) 算法 (ML-DSA) 配对。
这种混合方法可确保您的应用在未来免受量子攻击,同时与依赖于经典签名验证的旧版 Android 和设备保持完全的向后兼容性。
对开发者的影响
- 使用 Play 应用签名的应用:如果您使用 Play 应用签名,可以等待 Google Play 为您提供使用 Google Play 生成的 PQC 密钥升级混合签名的选项,从而确保您的应用受到保护,而无需手动管理密钥。
- 使用自行管理的密钥的应用:自行管理签名密钥的开发者可以利用更新后的 Android build 工具(例如 apksigner)轮换到混合身份,将 PQC 密钥与新的经典密钥相结合。(您必须创建新的经典密钥,无法重复使用旧密钥。)
Подключение
В Android 17 добавлены следующие функции для улучшения взаимодействия устройств и приложений.
Ограниченные спутниковые сети
实现优化,使应用能够在低带宽卫星网络上有效运行。
Пользовательский опыт и пользовательский интерфейс системы
В Android 17 внесены следующие изменения для улучшения пользовательского опыта.
Поток выделенного аудиопомощника
Android 17 为 Google 助理应用引入了专用的 Google 助理音量流,
以便使用 USAGE_ASSISTANT 进行播放。此项更改将 Google 助理音频与标准媒体流分离,让用户可以单独控制这两个音量。这样便可实现以下场景:将媒体播放静音,同时保持 Google 助理响应的可听性,反之亦然。
有权访问新的 MODE_ASSISTANT_CONVERSATION 音频模式的 Google 助理应用可以进一步提高音量控制的一致性。Google 助理应用可以使用此模式向系统提供有关活跃 Google 助理会话的提示,确保可以在活跃 USAGE_ASSISTANT 播放之外或使用连接的蓝牙外设控制 Google 助理流。
Передавать
切换是 Android 17 中新增的一项功能和 API,应用开发者可以将其集成到应用中,以便为用户提供跨设备连续性。它允许用户在一个 Android 设备上启动应用 activity,然后将其转移到另一个 Android 设备。Handoff 在用户设备的后台运行,并通过各种入口点(例如接收设备上的启动器和任务栏)显示用户附近其他设备上的可用活动。
应用可以指定 Handoff 来启动相同的原生 Android 应用(如果该应用已安装在接收设备上且可供使用)。在此应用到应用流程中,用户通过深层链接跳转到指定 activity。或者,应用到网站切换功能可以作为后备选项提供,也可以通过网址切换功能直接实现。
切换支持是按 activity 实现的。如需启用 Handoff,请针对 activity 调用 setHandoffEnabled() 方法。可能需要随切换传递其他数据,以便接收设备上重新创建的 activity 可以恢复适当的状态。实现 onHandoffActivityDataRequested() 回调以返回 HandoffActivityData 对象,该对象包含用于指定 Handoff 应如何处理并在接收设备上重新创建 activity 的详细信息。
Обновление в реальном времени - API семантического цвета
在 Android 17 中,实时更新启动了语义着色 API,以支持具有通用含义的颜色。
以下类支持语义着色:
NotificationNotification.MetricNotification.ProgressStyle.PointNotification.ProgressStyle.Segment
填色游戏
- 绿色:与安全相关。此颜色应在以下情况下使用:让别人知道您处于安全状态。
- 橙色:用于表示警告和标记物理危险。在用户需要注意以设置更好的保护设置的情况下,应使用此颜色。
- 红色:通常表示危险、停止。它应在需要人们紧急关注的情况下显示。
- 蓝色:中性颜色,适用于信息性内容,应与其他内容区分开来。
以下示例展示了如何将语义样式应用于通知中的文本:
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 UWB Downlink-TDoA для Android 17
下行链路到达时间差 (DL-TDoA) 测距技术可让设备通过测量信号的相对到达时间来确定其相对于多个锚点的位置。
以下代码段演示了如何初始化 Ranging Manager、验证设备功能并启动 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();