Fitur dan API

Android 17 memperkenalkan fitur dan API baru yang hebat untuk para developer. Bagian berikut merangkum fitur ini untuk membantu Anda mulai menggunakan API terkait.

Untuk melihat daftar mendetail tentang API yang baru, diubah, dan dihapus, baca laporan perbedaan API. Untuk mengetahui detail tentang API baru, buka referensi API Android — API baru ditandai agar lebih mudah dilihat.

Anda juga harus meninjau area tempat perubahan platform dapat memengaruhi aplikasi Anda. Untuk informasi selengkapnya, lihat halaman berikut:

Fungsi inti

Android 17 menambahkan fitur baru berikut yang terkait dengan fungsi Android inti.

Pemicu ProfilingManager baru

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

JobDebugInfo API

Android 17 memperkenalkan JobDebugInfo API baru untuk membantu developer men-debug tugas JobScheduler mereka—alasan tugas tidak berjalan, durasi tugas berjalan, dan informasi gabungan lainnya.

Metode pertama dari JobDebugInfo API yang diperluas adalah getPendingJobReasonStats(), yang menampilkan peta alasan mengapa tugas berada dalam status eksekusi yang tertunda dan durasi tertunda kumulatifnya. Metode ini bergabung dengan metode getPendingJobReasonsHistory() dan getPendingJobReasons() untuk memberi Anda insight tentang alasan tugas terjadwal tidak berjalan seperti yang diharapkan, tetapi menyederhanakan pengambilan informasi dengan menyediakan durasi dan alasan tugas dalam satu metode.

Misalnya, untuk jobId yang ditentukan, metode ini dapat menampilkan PENDING_JOB_REASON_CONSTRAINT_CHARGING dan durasi 60.000 md, yang menunjukkan bahwa tugas tertunda selama 60.000 md karena batasan pengisian daya tidak terpenuhi.

Mengurangi penguncian layar saat aktif dengan dukungan pemroses untuk alarm yang diizinkan saat perangkat dalam kondisi tidak aktif

Android 17 memperkenalkan varian baru AlarmManager.setExactAndAllowWhileIdle yang menerima OnAlarmListener, bukan PendingIntent. Mekanisme baru berbasis callback ini ideal untuk aplikasi yang saat ini mengandalkan wakelock berkelanjutan untuk melakukan tugas berkala, seperti aplikasi pesan yang mempertahankan koneksi soket.

Privasi

Android 17 menyertakan fitur baru berikut untuk meningkatkan privasi pengguna.

Dukungan platform Encrypted Client Hello (ECH)

Android 17 memperkenalkan dukungan platform untuk Encrypted Client Hello (ECH), sebuah peningkatan privasi yang signifikan untuk komunikasi jaringan. ECH adalah ekstensi TLS 1.3 yang mengenkripsi Server Name Indication (SNI) selama handshake TLS awal. Enkripsi ini membantu melindungi privasi pengguna dengan mempersulit perantara jaringan mengidentifikasi domain tertentu yang terhubung ke aplikasi.

Platform kini menyertakan API yang diperlukan agar library jaringan dapat menerapkan ECH. Hal ini mencakup kemampuan baru di DnsResolver untuk mengkueri data DNS HTTPS yang berisi konfigurasi ECH, dan metode baru di SSLEngines dan SSLSockets Conscrypt untuk mengaktifkan ECH dengan meneruskan konfigurasi ini saat terhubung ke domain. Developer dapat mengonfigurasi preferensi ECH, seperti mengaktifkannya secara oportunistik atau mewajibkan penggunaannya, melalui elemen <domainEncryption> baru dalam file Konfigurasi Keamanan Jaringan, yang berlaku secara global atau per domain.

Library jaringan populer seperti HttpEngine, WebView, dan OkHttp diharapkan mengintegrasikan API platform ini dalam update mendatang, sehingga mempermudah aplikasi mengadopsi ECH dan meningkatkan privasi pengguna.

Untuk mengetahui informasi selengkapnya, lihat dokumentasi Encrypted Client Hello.

Pemilih kontak Android

Android 联系人选择工具是一个标准化的可浏览界面,供用户与您的应用分享联系人。该选择工具适用于搭载 Android 17(API 级别 37)或更高版本的设备,可提供一种可保护隐私的替代方案,以取代广泛的 READ_CONTACTS 权限。您的应用无需请求访问用户的整个地址簿,而是指定所需的数据字段(例如电话号码或电子邮件地址),然后用户选择要分享的特定联系人。这样,您的应用便只能读取所选数据,从而确保精细控制,同时提供一致的用户体验,并具有内置搜索、个人资料切换和多选功能,而无需构建或维护界面。

如需了解详情,请参阅联系人选择工具文档

Keamanan

Android 17 menambahkan fitur baru berikut untuk meningkatkan keamanan perangkat dan aplikasi.

Mode Perlindungan Lanjutan Android (AAPM)

Android 高级保护模式为 Android 用户提供了一套强大的新安全功能,标志着在保护用户(尤其是面临较高风险的用户)免遭复杂攻击方面迈出了重要一步。AAPM 是一项选择启用功能,只需进行一项配置设置即可激活。用户可以随时启用该功能,以应用一套主观的安全保护措施。

这些核心配置包括:禁止安装未知来源的应用(旁加载)、限制 USB 数据信号传输,以及强制执行 Google Play 保护机制扫描,从而显著减小设备的攻击面。 开发者可以使用 AdvancedProtectionManager API 与此功能集成,以检测模式的状态,从而使应用能够在用户选择启用此模式时自动采用强化型安全姿态或限制高风险功能。

Penandatanganan APK PQC

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.)

Konektivitas

Android 17 menambahkan fitur berikut untuk meningkatkan konektivitas perangkat dan aplikasi.

Jaringan satelit yang dibatasi

实现优化,使应用能够在低带宽卫星网络上有效运行。

Pengalaman pengguna dan UI sistem

Android 17 menyertakan perubahan berikut untuk meningkatkan pengalaman pengguna.

Aliran volume Asisten khusus

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

Penyerahan adalah fitur dan API baru yang akan hadir di Android 17 yang dapat diintegrasikan oleh developer aplikasi untuk memberikan kontinuitas lintas perangkat bagi pengguna mereka. Fitur ini memungkinkan pengguna memulai aktivitas aplikasi di satu perangkat Android dan mentransisikannya ke perangkat Android lain. Pengalihan berjalan di latar belakang perangkat pengguna dan menampilkan aktivitas yang tersedia dari perangkat terdekat pengguna lainnya melalui berbagai titik entri, seperti peluncur dan taskbar, di perangkat penerima.

Aplikasi dapat menetapkan Handoff untuk meluncurkan aplikasi Android native yang sama, jika aplikasi tersebut diinstal dan tersedia di perangkat penerima. Dalam alur aplikasi-ke-aplikasi ini, pengguna ditautkan secara mendalam ke aktivitas yang ditentukan. Atau, Penyerahan dari aplikasi ke web dapat ditawarkan sebagai opsi penggantian atau diterapkan langsung dengan Penyerahan URL.

Dukungan Handoff diimplementasikan per aktivitas. Untuk mengaktifkan Handoff, panggil metode setHandoffEnabled() untuk aktivitas. Data tambahan mungkin perlu diteruskan bersama dengan pengalihan sehingga aktivitas yang dibuat ulang di perangkat penerima dapat memulihkan status yang sesuai. Terapkan callback onHandoffActivityDataRequested() untuk menampilkan objek HandoffActivityData yang berisi detail yang menentukan cara Handoff harus menangani dan membuat ulang aktivitas di perangkat penerima.

Pembaruan Langsung - Semantic color API

Dengan Android 17, Update Langsung meluncurkan Semantic Coloring API untuk mendukung warna dengan makna universal.

Class berikut mendukung pewarnaan semantik:

Mewarnai

  • Hijau: Terkait dengan keselamatan. Warna ini harus digunakan untuk kasus yang memberi tahu orang lain bahwa Anda berada dalam situasi yang aman.
  • Oranye: Untuk menunjukkan kehati-hatian dan menandai bahaya fisik. Warna ini harus digunakan dalam situasi saat pengguna perlu memperhatikan untuk menyetel setelan perlindungan yang lebih baik.
  • Merah: Umumnya menunjukkan bahaya, berhenti. Harus ditampilkan untuk kasus yang memerlukan perhatian orang dengan segera.
  • Biru: Warna netral untuk konten yang bersifat informatif dan harus terlihat berbeda dari konten lainnya.

Contoh berikut menunjukkan cara menerapkan gaya semantik pada teks dalam notifikasi:

  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 untuk Android 17

Pengukuran Downlink Time Difference of Arrival (DL-TDoA) memungkinkan perangkat menentukan posisinya relatif terhadap beberapa anchor dengan mengukur waktu kedatangan sinyal relatif.

Cuplikan berikut menunjukkan cara menginisialisasi Ranging Manager, memverifikasi kemampuan perangkat, dan memulai sesi 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
        }
    }
}

Konfigurasi Out-of-Band (OOB)

Cuplikan berikut memberikan contoh data konfigurasi OOB DL-TDoA untuk Wi-Fi dan 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
};

Jika Anda tidak dapat menggunakan konfigurasi OOB karena tidak ada, atau jika Anda perlu mengubah nilai default yang tidak ada dalam konfigurasi OOB, Anda dapat membuat parameter dengan DlTdoaRangingParams.Builder seperti yang ditunjukkan dalam cuplikan berikut. Anda dapat menggunakan parameter ini sebagai pengganti 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();