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, vui lòng truy cập Tài liệu tham khảo API cho Android (các API mới được làm nổi bật).
Bạn cũng nên xem xét những khía cạnh mà các thay đổi về nền tảng có thể ảnh hưởng đến ứng dụng của mình. Để biết thêm thông tin, hãy xem các trang sau:
- Các thay đổi về hành vi ảnh hưởng đến ứng dụng khi ứng dụng nhắm đến Android 17
- Các thay đổi về hành vi ảnh hưởng đến tất cả ứng dụng bất kể
targetSdkVersion.
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 添加了多个新的系统触发器,以
帮助您收集深入数据来调试性能问题。
新触发器包括:
TRIGGER_TYPE_COLD_START:在应用冷启动期间触发。它在响应中同时提供调用堆栈样本和系统跟踪记录。TRIGGER_TYPE_OOM:当应用抛出OutOfMemoryError时触发,并在响应中提供 Java 堆转储。TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE:当应用因 CPU 使用异常且过高而被终止时触发,并在响应中提供调用堆栈样本。TRIGGER_TYPE_ANOMALY:检测系统性能异常,例如 binder 调用过多和内存用量过高。
如需了解如何设置系统触发器,请参阅有关 基于触发器的性能分析的文档以及有关如何检索和分析性能分析数据 的文档。
应用异常的性能分析触发器
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 引入了新的 JobDebugInfo API,可帮助开发者调试其 JobScheduler 作业,了解作业未运行的原因、运行时长以及其他汇总信息。
扩展后的 JobDebugInfo API 的第一个方法是 getPendingJobReasonStats(),该方法会返回一个映射,其中包含作业处于待执行状态的原因及其各自的累计待执行时长。此方法将 getPendingJobReasonsHistory() 和 getPendingJobReasons() 方法联接在一起,可让您了解预定作业未按预期运行的原因,但通过在单个方法中同时提供时长和作业原因,简化了信息检索。
例如,对于指定的 jobId,该方法可能会返回 PENDING_JOB_REASON_CONSTRAINT_CHARGING 和 60000 毫秒的时长,表示作业因未满足充电约束而处于等待状态 60000 毫秒。
Giảm khoá đánh thức bằng tính năng hỗ trợ trình nghe cho báo thức cho phép trong khi thiết bị ở trạng thái rảnh
Android 17
引入了 AlarmManager.setExactAndAllowWhileIdle 的新变体,该变体
接受 OnAlarmListener 而不是 PendingIntent。这种基于回调的新机制非常适合目前依赖于连续唤醒锁来执行定期任务的应用,例如维护套接字连接的消息传递应用。
Quyền riêng tư
Android 17 bao gồm 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.
Hỗ trợ nền tảng Encrypted Client Hello (ECH)
Android 17 引入了对加密客户端 Hello (ECH) 的平台支持,这是对网络通信的一项重大隐私增强功能。ECH 是一项 TLS 1.3 扩展,可在初始 TLS 握手期间加密服务器名称指示 (SNI)。这种加密有助于保护用户隐私,因为它可以让网络中介更难识别应用连接到的特定网域。
该平台现在包含网络库实现 ECH 所需的 API。这包括 DnsResolver 中的新功能,用于查询包含 ECH 配置的 HTTPS DNS 记录;以及 Conscrypt 的 SSLEngine 和 SSLSocket 中的新方法,用于在连接到网域时传入这些配置来启用 ECH。开发者可以通过网络安全配置文件中的新 <domainEncryption> 元素来配置 ECH 偏好设置,例如机会性地启用 ECH 或强制使用 ECH,这些设置可全局应用,也可按网域应用。
预计 HttpEngine、WebView 和 OkHttp 等热门联网库将在未来的更新中集成这些平台 API,从而使应用能够更轻松地采用 ECH 并增强用户隐私保护。
如需了解详情,请参阅加密的客户端 Hello 文档。
Trình chọn người liên hệ cho Android
Android 联系人选择工具是一个标准化的可浏览界面,供用户与您的应用分享联系人。该选择工具适用于搭载 Android 17(API 级别 37)或更高版本的设备,可提供一种可保护隐私的替代方案,以取代广泛的 READ_CONTACTS 权限。您的应用无需请求访问用户的整个地址簿,而是指定所需的数据字段(例如电话号码或电子邮件地址),然后用户选择要分享的特定联系人。这样,您的应用便只能读取所选数据,从而确保精细控制,同时提供一致的用户体验,并具有内置搜索、个人资料切换和多选功能,而无需构建或维护界面。
如需了解详情,请参阅联系人选择工具文档。
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)
Android 高级保护模式为 Android 用户提供了一套强大的新安全功能,标志着在保护用户(尤其是面临较高风险的用户)免遭复杂攻击方面迈出了重要一步。AAPM 是一项选择启用功能,只需进行一项配置设置即可激活。用户可以随时启用该功能,以应用一套主观的安全保护措施。
这些核心配置包括:禁止安装未知来源的应用(旁加载)、限制 USB 数据信号传输,以及强制执行 Google Play 保护机制扫描,从而显著减小设备的攻击面。
开发者可以使用 AdvancedProtectionManager API 与此功能集成,以检测模式的状态,从而使应用能够在用户选择启用此模式时自动采用强化型安全姿态或限制高风险功能。
Ký APK PQC
Android hiện hỗ trợ lược đồ chữ ký APK kết hợp để bảo vệ danh tính ký của ứng dụng trước mối đe doạ tiềm ẩn từ các cuộc tấn công sử dụng điện toán lượng tử. Tính năng này giới thiệu một Lược đồ chữ ký APK mới, cho phép bạn ghép nối khoá ký cổ điển (chẳng hạn như RSA hoặc EC) với một thuật toán mật mã hậu lượng tử (PQC) mới (ML-DSA).
Phương pháp kết hợp này đảm bảo ứng dụng của bạn vẫn an toàn trước các cuộc tấn công lượng tử trong tương lai, đồng thời duy trì khả năng tương thích ngược hoàn toàn với các phiên bản và thiết bị Android cũ dựa vào quy trình xác minh chữ ký cổ điển.
Ảnh hưởng đối với nhà phát triển
- Ứng dụng sử dụng Tính năng ký ứng dụng của Play: Nếu sử dụng Tính năng ký ứng dụng của Play, bạn có thể đợi Google Play cung cấp cho bạn lựa chọn nâng cấp chữ ký kết hợp bằng khoá PQC do Google Play tạo, đảm bảo ứng dụng của bạn được bảo vệ mà không cần quản lý khoá theo cách thủ công.
- Ứng dụng sử dụng khoá do nhà phát triển tự quản lý: Những nhà phát triển tự quản lý khoá ký có thể sử dụng các công cụ xây dựng Android đã cập nhật (như apksigner) để chuyển sang danh tính kết hợp, kết hợp khoá PQC với một khoá cổ điển mới. (Bạn phải tạo một khoá cổ điển mới, không thể sử dụng lại khoá cũ.)
Khả năng kết nối
Android 17 bổ sung các tính năng sau đây để 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ế
实现优化,使应用能够在低带宽卫星网络上有效运行。
Trải nghiệm người dùng và giao diện người dùng hệ thống
Android 17 bao gồm các 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 có thể kiểm soát riêng cả hai âm lượng. Điều này cho phép các trường hợp như tắt tiếng phát nội dung nghe nhìn trong khi 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 việc 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 rằng luồng Trợ lý có thể được kiểm soát bên ngoài quá trình 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
切换是 Android 17 中新增的一项功能和 API,应用开发者可以将其集成到应用中,以便为用户提供跨设备连续性。它允许用户在一个 Android 设备上启动应用 activity,然后将其转移到另一个 Android 设备。Handoff 在用户设备的后台运行,并通过各种入口点(例如接收设备上的启动器和任务栏)显示用户附近其他设备上的可用活动。
应用可以指定 Handoff 来启动相同的原生 Android 应用(如果该应用已安装在接收设备上且可供使用)。在此应用到应用流程中,用户通过深层链接跳转到指定 activity。或者,应用到网站切换功能可以作为后备选项提供,也可以通过网址切换功能直接实现。
切换支持是按 activity 实现的。如需启用 Handoff,请针对 activity 调用 setHandoffEnabled() 方法。可能需要随切换传递其他数据,以便接收设备上重新创建的 activity 可以恢复适当的状态。实现 onHandoffActivityDataRequested() 回调以返回 HandoffActivityData 对象,该对象包含用于指定 Handoff 应如何处理并在接收设备上重新创建 activity 的详细信息。
Cập nhật trực tiếp – API màu ngữ nghĩa
在 Android 17 中,实时更新启动了语义着色 API,以支持具有通用含义的颜色。
以下类支持语义着色:
NotificationNotification.MetricNotification.ProgressStyle.PointNotification.ProgressStyle.Segment
填色游戏
- 绿色:与安全相关。此颜色应在以下情况下使用:让别人知道您处于安全状态。
- 橙色:用于表示警告和标记物理危险。在用户需要注意以设置更好的保护设置的情况下,应使用此颜色。
- 红色:通常表示危险、停止。它应在需要人们紧急关注的情况下显示。
- 蓝色:中性颜色,适用于信息性内容,应与其他内容区分开来。
以下示例展示了如何将语义样式应用于通知中的文本:
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)
API UWB Downlink-TDoA cho Android 17
下行链路到达时间差 (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();