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:偵測系統效能異常狀況,例如繫結器呼叫次數過多和記憶體用量過高。
如要瞭解如何設定系統觸發條件,請參閱以觸發條件為準的剖析說明文件,以及如何擷取及分析剖析資料說明文件。
應用程式異常狀況的剖析觸發條件
Android 17 推出裝置端異常偵測服務,可監控耗用大量資源的行為和潛在的相容性回歸。這項服務與 ProfilingManager 整合,可讓應用程式接收特定系統偵測到的事件所觸發的剖析構件。
使用 TRIGGER_TYPE_ANOMALY 觸發條件偵測系統效能問題,例如繫結器呼叫次數過多和記憶體用量過高。如果應用程式超出作業系統定義的記憶體限制,異常狀況觸發條件會允許開發人員接收應用程式專屬的記憶體快照資料,協助找出並修正記憶體問題。此外,如果活頁夾垃圾內容過多,異常觸發程序會提供活頁夾交易的堆疊取樣設定檔。
這個 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 推出新的 AlarmManager.setExactAndAllowWhileIdle 變體,可接受 OnAlarmListener,而非 PendingIntent。如果應用程式目前依賴持續喚醒鎖定來執行週期性工作 (例如即時通訊應用程式維護通訊端連線),這個以新回呼為基礎的機制就非常適合。
隱私權
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 聯絡人選擇工具
The Android Contact Picker is a standardized, browsable interface for users to
share contacts with your app. Available on devices running
Android 17 (API level 37) or higher, the picker offers a privacy-preserving
alternative to the broad READ_CONTACTS permission. Instead of requesting
access to the user's entire address book, your app specifies the data fields it
needs, such as phone numbers or email addresses, and the user selects specific
contacts to share. This grants your app read access to only the selected data,
ensuring granular control while providing a consistent user experience with
built-in search, profile switching, and multi-selection capabilities without
having to build or maintain the UI.
For more information, see the contact picker documentation.
安全性
Android 17 新增了下列功能,可提升裝置和應用程式安全性。
Android 進階保護模式 (AAPM)
Android Advanced Protection Mode offers Android users a powerful new set of security features, marking a significant step in safeguarding users—particularly those at higher risk—from sophisticated attacks. Designed as an opt-in feature, AAPM is activated with a single configuration setting that users can turn on at any time to apply an opinionated set of security protections.
These core configurations include blocking app installation from unknown sources
(sideloading), restricting USB data signaling, and mandating Google Play Protect
scanning, which significantly reduces the device's attack surface area.
Developers can integrate with this feature using the
AdvancedProtectionManager API to detect the mode's status, enabling
applications to automatically adopt a hardened security posture or restrict
high-risk functionality when a user has opted in.
PQC APK 簽署
Android now supports a hybrid APK signature scheme to future-proof your app's signing identity against the potential threat of attacks that make use of quantum computing. This feature introduces a new APK Signature Scheme, which lets you pair a classical signing key (such as RSA or EC) with a new post-quantum cryptography (PQC) algorithm (ML-DSA).
This hybrid approach ensures your app remains secure against future quantum attacks while maintaining full backward compatibility with older Android versions and devices that rely on classical signature verification.
Impact on developers
- Apps using Play App Signing: If you use Play App Signing, you can wait for Google Play to give you the option to upgrade a hybrid signature using a PQC key generated by Google Play, ensuring your app is protected without requiring manual key management.
- Apps using self-managed keys: Developers who manage their own signing keys can utilize updated Android build tools (like apksigner) to rotate to a hybrid identity, combining a PQC key with a new classical key. (You must create a new classical key, you cannot reuse the older one.)
連線能力
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 裝置上啟動應用程式活動,然後轉移到另一部 Android 裝置。接手功能會在使用者裝置的背景執行,並透過各種進入點 (例如啟動器和工作列),在接收裝置上顯示使用者其他鄰近裝置的可用活動。
如果接收裝置已安裝相同的原生 Android 應用程式,且該應用程式可供使用,應用程式可以指定 Handoff 啟動該應用程式。在這個應用程式對應用程式的流程中,系統會將使用者深層連結至指定活動。或者,應用程式到網站的交接功能可以做為備用選項,也可以直接透過網址交接功能導入。
「接力」支援功能是以活動為單位實作。如要啟用接續功能,請呼叫活動的 setHandoffEnabled() 方法。您可能需要連同交接作業傳遞額外資料,以便在接收裝置上重建活動時還原適當狀態。實作 onHandoffActivityDataRequested() 回呼,傳回 HandoffActivityData 物件,其中包含詳細資料,指定 Handoff 應如何處理及在接收裝置上重新建立活動。
即時更新 - 語意色彩 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
Downlink Time Difference of Arrival (DL-TDoA) ranging lets a device determine its position relative to multiple anchors by measuring the relative arrival times of signals.
The following snippet demonstrates how to initialize the Ranging Manager, verify device capabilities, and start a DL-TDoA session:
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
}
}
}
Out-of-Band (OOB) Configurations
The following snippet provides an example of DL-TDoA OOB configuration data for Wi-Fi and 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
};
If you can't use an OOB configuration because it is missing, or if you need to
change default values that aren't in the OOB config, you can build parameters
with DlTdoaRangingParams.Builder as shown in the following snippet. You can use
these parameters in place of 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();