功能與 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 推出新的 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 聯絡人選擇工具

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 建構工具 (例如 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 裝置上啟動應用程式活動,然後轉移到另一部 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();