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 introduces new JobDebugInfo APIs to help developers debug
their JobScheduler jobs--why they aren't running, how long they ran for, and
other aggregated information.
The first method of the expanded JobDebugInfo APIs is
getPendingJobReasonStats(), which returns a map of reasons why the job was in
a pending execution state and their respective cumulative pending
durations. This method joins the getPendingJobReasonsHistory() and
getPendingJobReasons() methods to give you insight into why a scheduled
job is not running as expected, but simplifies information retrieval by making
both duration and job reason available in a single method.
For example, for a specified jobId, the method might return
PENDING_JOB_REASON_CONSTRAINT_CHARGING and a duration of 60000 ms, indicating
the job was pending for 60000ms due to the charging constraint not being
satisfied.
支援允許閒置時的鬧鐘,減少喚醒鎖定
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 聯絡人挑選器
Android 聯絡人選擇工具是標準化的可瀏覽介面,使用者可透過這個介面與應用程式分享聯絡人。這項選擇工具適用於執行 Android 17 (API 級別 37) 以上版本的裝置,可做為廣泛 READ_CONTACTS 權限的替代方案,同時保護隱私權。應用程式不會要求存取使用者的完整通訊錄,而是指定需要的資料欄位 (例如電話號碼或電子郵件地址),並由使用者選取要分享的特定聯絡人。這項功能只會授予應用程式所選資料的讀取權限,確保您能精細控管資料,同時提供一致的使用者體驗,包括內建搜尋、切換設定檔和多選功能,不必自行建構或維護使用者介面。
詳情請參閱聯絡人挑選器說明文件。
安全性
Android 17 新增下列功能,可提升裝置和應用程式安全性。
Android 進階保護模式 (AAPM)
Android 進階保護模式為 Android 使用者提供一系列強大的全新安全防護功能,在保護使用者 (尤其是高風險使用者) 免於遭受複雜攻擊方面,邁出重要的一步。AAPM 是一項可選擇啟用的功能,只要設定一次即可啟用,使用者隨時都能開啟這項功能,套用一組預設的安全防護措施。
這些核心設定包括禁止從不明來源安裝應用程式 (側載)、限制 USB 資料訊號,以及強制執行 Google Play 安全防護掃描,大幅縮減裝置的攻擊面。開發人員可以透過 AdvancedProtectionManager API 整合這項功能,偵測模式狀態,讓應用程式在使用者啟用時自動採用強化安全措施,或限制高風險功能。
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 introduces a dedicated Assistant volume stream for Assistant apps,
for playback with USAGE_ASSISTANT. This change decouples Assistant audio
from the standard media stream, providing users with isolated control over both
volumes. This enables scenarios such as muting media playback while maintaining
audibility for Assistant responses, and the other way around.
Assistant apps with access to the new MODE_ASSISTANT_CONVERSATION audio
mode can further improve the volume control consistency. Assistant apps can use
this mode to provide a hint to the system about an active Assistant session,
ensuring the Assistant stream can be controlled outside of the active
USAGE_ASSISTANT playback or with connected Bluetooth peripherals.
接力
Handoff is a new feature and API coming to Android 17 that app developers can integrate with to provide cross-device continuity for their users. It allows the user to start an app activity on one Android device and transition it to another Android device. Handoff runs in the background of a user's device and surfaces available activities from the user's other nearby devices through various entry points, like the launcher and taskbar, on the receiving device.
Apps can designate Handoff to launch the same native Android app, if it is installed and available on the receiving device. In this app-to-app flow, the user is deep-linked to the designated activity. Alternatively, app-to-web Handoff can be offered as a fallback option or directly implemented with URL Handoff.
Handoff support is implemented on a per-activity basis. To enable Handoff, call
the setHandoffEnabled() method for the activity. Additional data may need to
be passed along with the handoff so the recreated activity on the receiving
device can restore appropriate state. Implement the
onHandoffActivityRequested() callback to return a HandoffActivityData object
which contains details that specify how Handoff should handle and recreate
the activity on the receiving device.
即時更新 - 語意色彩 API
With Android 17, Live Update launches the Semantic Coloring APIs to support colors with universal meaning.
The following classes support semantic coloring:
NotificationNotification.MetricNotification.ProgressStyle.PointNotification.ProgressStyle.Segment
Coloring
- Green: Associated with safety. This color should be used for the case where it lets people know you are in the safe situation.
- Orange: For designating caution and marking physical hazards. This color should be used in the situation where users need to pay attention to set better protection setting.
- Red: Generally indicates danger, stop. It should be presented for the case where need people's attention urgently.
- Blue: Neutral color for content that is informational and should stand out from other content.
The following example shows how to apply semantic styles to text in a notification:
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();