功能與 API

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

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

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

核心功能

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

新的 ProfilingManager 觸發條件

Android 17 新增了多個系統觸發條件,可協助您收集深入資料,以偵錯效能問題。ProfilingManager

新觸發條件包括:

如要瞭解如何設定系統觸發條件,請參閱以觸發條件為準的剖析說明文件,以及如何擷取及分析剖析資料說明文件。

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 包含下列新功能,可提升使用者隱私。

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 助理應用程式可以使用這個模式,向系統提供有關有效 Google 助理工作階段的提示,確保可以在有效USAGE_ASSISTANT播放作業以外或透過連線的藍牙周邊裝置控制 Google 助理串流。

接力

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

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

「接力」支援功能是以活動為單位實作。如要啟用交接功能,請呼叫活動的 setHandoffEnabled() 方法。您可能需要連同交接作業傳遞額外資料,以便接收裝置上重建的活動還原適當狀態。實作 onHandoffActivityRequested() 回呼,傳回 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.CapabilitiesCallback {
            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 = intArrayOf(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.CapabilitiesCallback() {
            @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());
        int[] rangingRoundIndexes = new int[] {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();