Android 17 為開發人員推出了強大的新功能和 API。以下各節會簡要說明這些功能,協助您開始使用相關 API。
如需新增、修改及移除 API 的詳細清單,請參閱 API 差異比較表。如要進一步瞭解新的 API,請參閱 Android API 參考資料 - 新的 API 會醒目顯示,以利於查看。
此外,也請查看平台變更可能對應用程式造成的影響。詳情請參閱下列頁面:
核心功能
Android 17 新增了下列與 Android 核心功能相關的功能。
新的 ProfilingManager 觸發條件
Android 17 向 ProfilingManager 添加了多个新的系统触发器,以
帮助您收集深入数据来调试性能问题。
新触发器包括:
TRIGGER_TYPE_COLD_START:在应用冷启动期间触发。它在响应中同时提供调用堆栈样本和系统跟踪记录。TRIGGER_TYPE_OOM:当应用抛出OutOfMemoryError时触发,并在响应中提供 Java 堆转储。TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE:当应用因 CPU 使用异常且过高而被终止时触发,并在响应中提供调用堆栈样本。TRIGGER_TYPE_ANOMALY:检测系统性能异常,例如 binder 调用过多和内存用量过高。
如需了解如何设置系统触发器,请参阅有关 基于触发器的性能分析的文档以及有关如何检索和分析性能分析数据 的文档。
应用异常的性能分析触发器
Android 17 引入了一项设备端异常检测服务,用于监控资源密集型行为和潜在的兼容性回归。此服务与ProfilingManager集成,可让您的应用接收由特定系统检测到的事件触发的性能分析工件。
使用 TRIGGER_TYPE_ANOMALY 触发器检测系统性能问题
例如 binder 调用过多和内存用量过高。当应用违反操作系统定义的内存限制时,异常触发器允许开发者接收特定于应用的堆转储,以帮助识别和修复内存问题。此外,对于 binder 垃圾内容过多,异常触发器会提供有关 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)
}
JobDebugInfo API
Android 17 引入了新的 JobDebugInfo API,可帮助开发者调试其 JobScheduler 作业,了解作业未运行的原因、运行时长以及其他汇总信息。
扩展后的 JobDebugInfo API 的第一个方法是 getPendingJobReasonStats(),该方法会返回一个映射,其中包含作业处于待执行状态的原因及其各自的累计待执行时长。此方法将 getPendingJobReasonsHistory() 和 getPendingJobReasons() 方法联接在一起,可让您了解预定作业未按预期运行的原因,但通过在单个方法中同时提供时长和作业原因,简化了信息检索。
例如,对于指定的 jobId,该方法可能会返回 PENDING_JOB_REASON_CONSTRAINT_CHARGING 和 60000 毫秒的时长,表示作业因未满足充电约束而处于等待状态 60000 毫秒。
支援允許閒置時的鬧鐘,減少喚醒鎖定
Android 17
introduces a new variant of AlarmManager.setExactAndAllowWhileIdle that
accepts an OnAlarmListener instead of a PendingIntent. This new
callback-based mechanism is ideal for apps that currently rely on continuous
wakelocks to perform periodic tasks, such as messaging apps maintaining socket
connections.
隱私權
Android 17 包含下列新功能,可提升使用者隱私。
支援 Encrypted Client Hello (ECH) 的平台
Android 17 推出平台支援的 Encrypted Client Hello (ECH),可大幅提升網路通訊的隱私權。ECH 是 TLS 1.3 擴充功能,可在初始 TLS 握手期間加密伺服器名稱指標 (SNI)。這項加密功能可讓網路中介者更難以識別應用程式連線的特定網域,有助於保護使用者隱私。
平台現在包含網路程式庫實作 ECH 所需的 API。這包括 DnsResolver 中的新功能,可查詢含有 ECH 設定的 HTTPS DNS 記錄,以及 Conscrypt 的 SSLEngines 和 SSLSockets 中的新方法,可在連線至網域時傳遞這些設定來啟用 ECH。開發人員可以透過網路安全性設定檔中的新 <domainEncryption> 元素,設定 ECH 偏好設定,例如視情況啟用或強制使用 ECH,適用於全域或個別網域。
HttpEngine、WebView 和 OkHttp 等熱門網路程式庫預計會在日後的更新中整合這些平台 API,讓應用程式更容易採用 ECH 並提升使用者隱私權。
詳情請參閱「加密用戶端問候訊息」說明文件。
Android 聯絡人選擇工具
Android 联系人选择工具是一个标准化的可浏览界面,供用户与您的应用分享联系人。该选择工具适用于搭载 Android 17(API 级别 37)或更高版本的设备,可提供一种可保护隐私的替代方案,以取代广泛的 READ_CONTACTS 权限。您的应用无需请求访问用户的整个地址簿,而是指定所需的数据字段(例如电话号码或电子邮件地址),然后用户选择要分享的特定联系人。这样,您的应用便只能读取所选数据,从而确保精细控制,同时提供一致的用户体验,并具有内置搜索、个人资料切换和多选功能,而无需构建或维护界面。
如需了解详情,请参阅联系人选择工具文档。
安全性
Android 17 新增了下列功能,可提升裝置和應用程式安全性。
Android 進階保護模式 (AAPM)
Android 進階保護模式為 Android 使用者提供一系列強大的全新安全防護功能,在保護使用者 (尤其是高風險使用者) 免於遭受複雜攻擊方面,邁出重要的一步。AAPM 是一項可選擇啟用的功能,只要設定一次即可啟用,使用者隨時都能開啟這項功能,套用一組預設的安全防護措施。
這些核心設定包括禁止從不明來源安裝應用程式 (側載)、限制 USB 資料訊號,以及強制執行 Google Play 安全防護掃描,大幅縮減裝置的攻擊面。開發人員可以透過 AdvancedProtectionManager API 整合這項功能,偵測模式狀態,讓應用程式在使用者啟用時自動採用強化安全措施,或限制高風險功能。
PQC APK 簽署
Android 现在支持混合 APK 签名方案,以保护应用的签名身份免受利用量子计算的攻击的潜在威胁。此功能引入了一种新的 APK 签名方案,可让您将经典签名密钥(例如 RSA 或 EC)与新的后量子加密 (PQC) 算法 (ML-DSA) 配对。
这种混合方法可确保您的应用在未来免受量子攻击,同时与依赖于经典签名验证的旧版 Android 和设备保持完全的向后兼容性。
对开发者的影响
- 使用 Play 应用签名的应用:如果您使用 Play 应用签名,可以等待 Google Play 为您提供使用 Google Play 生成的 PQC 密钥升级混合签名的选项,从而确保您的应用受到保护,而无需手动管理密钥。
- 使用自行管理的密钥的应用:自行管理签名密钥的开发者可以利用更新后的 Android build 工具(例如 apksigner)轮换到混合身份,将 PQC 密钥与新的经典密钥相结合。(您必须创建新的经典密钥,无法重复使用旧密钥。)
連線能力
Android 17 新增下列功能,可提升裝置和應用程式的連線能力。
受限的衛星網路
實作最佳化功能,讓應用程式在低頻寬的衛星網路上也能有效運作。
使用者體驗和系統 UI
Android 17 包含下列異動項目,可提升使用者體驗。
專屬的 Google 助理音量串流
Android 17 推出專屬的 Google 助理音量串流,供 Google 助理應用程式使用,以便透過 USAGE_ASSISTANT 播放音訊。這項變更會將 Google 助理音訊與標準媒體串流分離,讓使用者能分別控制音量。這項功能可讓您在媒體播放期間將音量調到靜音,但仍可聽到 Google 助理的回覆,反之亦然。
如果 Google 助理應用程式可存取新的 MODE_ASSISTANT_CONVERSATION 音訊模式,就能進一步提升音量控制一致性。助理應用程式可以使用這個模式,向系統提供有效 Google 助理工作階段的提示,確保可以在有效USAGE_ASSISTANT播放作業以外或透過連線的藍牙周邊裝置控制 Google 助理串流。
Handoff
切换是 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)
Android 17 適用的 UWB 下行鏈路 TDoA API
裝置可透過下行鏈路到達時間差 (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();