Tính năng và API

Android 17 mang đến cho nhà phát triển các tính năng và API mới tuyệt vời. Các phần sau đây tóm tắt những tính năng này để giúp bạn làm quen với các API liên quan.

Để biết danh sách chi tiết về các API mới, đã được sửa đổi, cũng như đã bị xoá, hãy đọc báo cáo điểm khác biệt về API. Để biết thông tin chi tiết về các API mới, hãy truy cập vào tài liệu tham khảo về API cho Android (các API mới được trình bày nổi bật).

Bạn cũng nên xem xét những khía cạnh mà các thay đổi của nền tảng có thể ảnh hưởng đến ứng dụng của bạn. Để biết thêm thông tin, hãy xem các trang sau:

Chức năng cốt lõi

Android 17 bổ sung các tính năng mới sau đây liên quan đến chức năng cốt lõi của Android.

Điều kiện kích hoạt ProfilingManager mới

Android 17 向 ProfilingManager 添加了多个新的系统触发器,可帮助您收集深入的数据来调试性能问题。

新增的触发条件包括:

如需了解如何设置系统触发器,请参阅有关基于触发器的分析以及如何检索和分析分析数据的文档。

应用异常的分析触发器

Android 17 引入了一项设备端异常检测服务,用于监控资源密集型行为和潜在的兼容性回归。此服务与 ProfilingManager 集成,可让您的应用接收由特定系统检测到的事件触发的分析工件。

使用 TRIGGER_TYPE_ANOMALY 触发器可检测系统性能问题,例如过多的 binder 调用和过多的内存用量。当应用超出操作系统定义的内存限制时,异常触发器可让开发者接收特定于应用的堆转储,以帮助他们识别和修复内存问题。此外,对于过多的 Binder 垃圾信息,异常触发器会在 Binder 事务上提供堆栈采样配置文件。

此 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)
}

API JobDebugInfo

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.

Giảm số lượng khoá đánh thức bằng tính năng hỗ trợ trình nghe cho báo thức cho phép ở chế độ chờ

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.

Quyền riêng tư

Android 17 có các tính năng mới sau đây để cải thiện quyền riêng tư của người dùng.

Nền tảng hỗ trợ 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.

Trình chọn người liên hệ của 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.

Bảo mật

Android 17 bổ sung các tính năng mới sau đây để cải thiện tính bảo mật của thiết bị và ứng dụng.

Chế độ Bảo vệ nâng cao trên Android (AAPM)

Chế độ Bảo vệ nâng cao của Android cung cấp cho người dùng Android một bộ tính năng bảo mật mới mạnh mẽ, đánh dấu một bước tiến quan trọng trong việc bảo vệ người dùng (đặc biệt là những người dùng có nguy cơ cao hơn) khỏi các cuộc tấn công tinh vi. Được thiết kế như một tính năng chọn sử dụng, AAPM được kích hoạt bằng một chế độ cài đặt cấu hình duy nhất mà người dùng có thể bật bất cứ lúc nào để áp dụng một bộ biện pháp bảo vệ bảo mật theo ý kiến riêng.

Các cấu hình cốt lõi này bao gồm việc chặn cài đặt ứng dụng từ các nguồn không xác định (tải ứng dụng bên ngoài), hạn chế tín hiệu dữ liệu USB và bắt buộc quét bằng Google Play Protect, giúp giảm đáng kể phạm vi tấn công của thiết bị. Nhà phát triển có thể tích hợp với tính năng này bằng cách sử dụng API AdvancedProtectionManager để phát hiện trạng thái của chế độ, cho phép các ứng dụng tự động áp dụng trạng thái bảo mật tăng cường hoặc hạn chế chức năng có rủi ro cao khi người dùng đã chọn sử dụng.

Ký APK bằng PQC

Android 现在支持混合 APK 签名方案,以保护应用的签名身份免受利用量子计算的攻击的潜在威胁。此功能引入了一种新的 APK 签名方案,可让您将经典签名密钥(例如 RSA 或 EC)与新的后量子加密 (PQC) 算法 (ML-DSA) 配对。

这种混合方法可确保您的应用在未来免受量子攻击,同时与依赖经典签名验证的旧版 Android 和设备保持完全的向后兼容性。

对开发者的影响

  • 使用 Play 应用签名的应用:如果您使用 Play 应用签名,可以等待 Google Play 为您提供使用 Google Play 生成的 PQC 密钥升级混合签名的选项,从而确保您的应用受到保护,而无需手动管理密钥。
  • 使用自行管理的密钥的应用:自行管理签名密钥的开发者可以利用更新后的 Android build 工具(例如 apksigner)轮换到混合身份,将 PQC 密钥与新的经典密钥相结合。(您必须创建新的经典密钥,无法重复使用旧密钥。)

Khả năng kết nối

Android 17 bổ sung các tính năng sau để cải thiện khả năng kết nối của thiết bị và ứng dụng.

Mạng vệ tinh bị hạn chế

Implements optimizations to enable apps to function effectively over low-bandwidth satellite networks.

Trải nghiệm người dùng và giao diện người dùng hệ thống

Android 17 có những thay đổi sau đây để cải thiện trải nghiệm người dùng.

Luồng âm lượng riêng cho Trợ lý

Android 17 giới thiệu một luồng âm lượng riêng cho Trợ lý đối với các ứng dụng Trợ lý, để phát bằng USAGE_ASSISTANT. Thay đổi này tách âm thanh của Trợ lý khỏi luồng nội dung nghe nhìn tiêu chuẩn, giúp người dùng kiểm soát độc lập cả hai âm lượng. Điều này cho phép các trường hợp như tắt tiếng khi phát nội dung nghe nhìn nhưng vẫn duy trì khả năng nghe được các câu trả lời của Trợ lý và ngược lại.

Các ứng dụng Trợ lý có quyền truy cập vào chế độ âm thanh MODE_ASSISTANT_CONVERSATION mới có thể cải thiện hơn nữa tính nhất quán của chế độ kiểm soát âm lượng. Các ứng dụng Trợ lý có thể sử dụng chế độ này để cung cấp gợi ý cho hệ thống về một phiên Trợ lý đang hoạt động, đảm bảo có thể kiểm soát luồng Trợ lý bên ngoài chế độ phát USAGE_ASSISTANT đang hoạt động hoặc bằng các thiết bị ngoại vi Bluetooth được kết nối.

Handoff

Handoff là một tính năng và API mới sắp có trên Android 17. Các nhà phát triển ứng dụng có thể tích hợp tính năng này để mang đến trải nghiệm liền mạch trên nhiều thiết bị cho người dùng. Tính năng này cho phép người dùng bắt đầu một hoạt động của ứng dụng trên một thiết bị Android và chuyển hoạt động đó sang một thiết bị Android khác. Tính năng Handoff chạy ở chế độ nền trên thiết bị của người dùng và hiển thị các hoạt động có sẵn từ các thiết bị khác ở gần của người dùng thông qua nhiều điểm truy cập, chẳng hạn như trình chạy và thanh tác vụ, trên thiết bị nhận.

Các ứng dụng có thể chỉ định tính năng Bàn giao để khởi chạy cùng một ứng dụng Android gốc, nếu ứng dụng đó được cài đặt và có trên thiết bị nhận. Trong quy trình từ ứng dụng đến ứng dụng này, người dùng được liên kết sâu đến hoạt động được chỉ định. Ngoài ra, bạn có thể cung cấp tính năng Bàn giao từ ứng dụng sang web dưới dạng lựa chọn dự phòng hoặc triển khai trực tiếp bằng tính năng Bàn giao URL.

Tính năng hỗ trợ chuyển giao được triển khai dựa trên từng hoạt động. Để bật tính năng Bàn giao, hãy gọi phương thức setHandoffEnabled() cho hoạt động. Bạn có thể cần chuyển thêm dữ liệu cùng với quá trình bàn giao để hoạt động được tạo lại trên thiết bị nhận có thể khôi phục trạng thái thích hợp. Triển khai lệnh gọi lại onHandoffActivityDataRequested() để trả về một đối tượng HandoffActivityData chứa thông tin chi tiết chỉ định cách Handoff nên xử lý và tạo lại hoạt động trên thiết bị nhận.

Bản cập nhật trực tiếp – API màu sắc theo ngữ nghĩa

With Android 17, Live Update launches the Semantic Coloring APIs to support colors with universal meaning.

The following classes support semantic coloring:

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)

UWB Downlink-TDoA API cho Android 17

Phạm vi Chênh lệch thời gian đến của đường xuống (DL-TDoA) cho phép thiết bị xác định vị trí của thiết bị so với nhiều điểm truy cập bằng cách đo thời gian đến tương đối của các tín hiệu.

Đoạn mã sau đây minh hoạ cách khởi tạo Trình quản lý khoảng cách, xác minh các chức năng của thiết bị và bắt đầu một phiên 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
        }
    }
}

Cấu hình ngoài băng tần (OOB)

Đoạn mã sau đây cung cấp ví dụ về dữ liệu cấu hình DL-TDoA OOB cho Wi-Fi và 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
};

Nếu không dùng được cấu hình OOB vì cấu hình này bị thiếu hoặc nếu cần thay đổi các giá trị mặc định không có trong cấu hình OOB, bạn có thể tạo các tham số bằng DlTdoaRangingParams.Builder như trong đoạn mã sau. Bạn có thể dùng các tham số này thay cho 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();