功能與 API

Android 17 為開發人員推出了強大的新功能和 API。以下各節會簡要說明這些功能,協助您開始使用相關 API。

如需新增、修改及移除 API 的詳細清單,請參閱 API 差異比較表。如要進一步瞭解新的 API,請參閱 Android API 參考資料 - 新的 API 會醒目顯示,以利於查看。

此外,也請查看平台變更可能對應用程式造成的影響。詳情請參閱下列頁面:

核心功能

Android 17 新增了下列與 Android 核心功能相關的功能。

新的 ProfilingManager 觸發條件

Android 17 adds several new system triggers to ProfilingManager to help you collect in-depth data to debug performance issues.

The new triggers are:

To understand how to set up the system trigger, see the documentation on trigger-based profiling and how to retrieve and analyze profiling data documentation.

Profiling trigger for app anomalies

Android 17 introduces an on-device anomaly detection service that monitors for resource-intensive behaviors and potential compatibility regressions. Integrated with ProfilingManager, this service allows your app to receive profiling artifacts triggered by specific system-detected events.

Use the TRIGGER_TYPE_ANOMALY trigger to detect system performance issues such as excessive binder calls and excessive memory usage. When an app breaches OS-defined memory limits, the anomaly trigger allows developers to receive app-specific heap dumps to help identify and fix memory issues. Additionally, for excessive binder spam, the anomaly trigger provides a stack sampling profile on binder transactions.

This API callback occurs prior to any system imposed enforcements. For example, it can help developers collect debug data before the app is terminated by the system for exceeding memory limits.

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 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 introduces platform support for Encrypted Client Hello (ECH), a significant privacy enhancement for network communications. ECH is a TLS 1.3 extension that encrypts the Server Name Indication (SNI) during the initial TLS handshake. This encryption helps protect user privacy by making it more difficult for network intermediaries to identify the specific domain an app is connecting to.

The platform now includes the necessary APIs for networking libraries to implement ECH. This includes new capabilities in DnsResolver to query for HTTPS DNS records containing ECH configurations, and new methods in Conscrypt's SSLEngines and SSLSockets to enable ECH by passing in these configurations when connecting to a domain. Developers can configure ECH preferences, such as enabling it opportunistically or mandating its use, through the new <domainEncryption> element within the Network Security Configuration file, applicable globally or on a per-domain basis.

Popular networking libraries such as HttpEngine, WebView, and OkHttp are expected to integrate these platform APIs in future updates, making it easier for apps to adopt ECH and enhance user privacy.

For more information, see the Encrypted Client Hello documentation.

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 現在支援混合式 APK 簽署配置,可保護應用程式的簽署身分,防範未來可能出現的量子運算攻擊。這項功能會推出新的 APK 簽署方式,讓您將傳統簽署金鑰 (例如 RSA 或 EC) 與新的後量子密碼編譯 (PQC) 演算法 (ML-DSA) 配對。

這種混合式做法可確保應用程式免於日後量子攻擊的威脅,同時與舊版 Android 和依賴傳統簽章驗證的裝置完全回溯相容。

對開發人員的影響

  • 使用 Play 應用程式簽署功能的應用程式:如果您使用 Play 應用程式簽署功能,可以等待 Google Play 提供選項,使用 Google Play 產生的 PQC 金鑰升級混合式簽章,確保應用程式受到保護,且不需要手動管理金鑰。
  • 使用自行管理金鑰的應用程式:管理自有簽署金鑰的開發人員可以運用更新的 Android 建構工具 (例如 apksigner) 輪替至混合式身分,結合 PQC 金鑰和新的傳統金鑰。(您必須建立新的傳統金鑰,無法重複使用舊金鑰)。

連線能力

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

接續功能是 Android 17 的新功能和 API,應用程式開發人員可整合這項功能,為使用者提供跨裝置連續性。使用者可以在一部 Android 裝置上啟動應用程式活動,然後轉移到另一部 Android 裝置。接手功能會在使用者裝置的背景執行,並透過各種進入點 (例如啟動器和工作列),在接收裝置上顯示使用者其他鄰近裝置的可用活動。

如果接收裝置已安裝相同的原生 Android 應用程式,且該應用程式可供使用,應用程式可以指定 Handoff 啟動該應用程式。在這個應用程式對應用程式的流程中,系統會將使用者深層連結至指定活動。或者,應用程式到網站的交接功能可以做為備用選項,也可以直接透過網址交接功能導入。

「接力」支援功能是以活動為單位實作。如要啟用接續功能,請呼叫活動的 setHandoffEnabled() 方法。您可能需要連同交接作業傳遞額外資料,以便在接收裝置上重建活動時還原適當狀態。實作 onHandoffActivityDataRequested() 回呼,傳回 HandoffActivityData 物件,其中包含詳細資料,指定 Handoff 應如何處理及在接收裝置上重新建立活動。

即時更新 - 語意色彩 API

在 Android 17 中,即時更新會推出語意著色 API,支援具有通用意義的顏色。

下列類別支援語意著色:

著色

  • 綠色:與安全相關。 這個顏色應在您處於安全情況時使用,讓其他人知道您安全無虞。
  • 橘色:用於標示注意事項和實體危害。如果使用者需要注意設定,才能獲得更完善的保護措施,就應使用這個顏色。
  • 紅色:通常表示危險,請停止。如果需要緊急引起使用者注意,就應顯示這類訊息。
  • 藍色:中性色,適用於資訊內容,且應與其他內容有所區別。

以下範例說明如何將語意樣式套用至通知中的文字:

  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();