Android 17에서는 개발자를 위한 훌륭한 새 기능과 API가 도입됩니다. 다음 섹션에서는 이러한 기능을 요약하여 관련 API를 시작하는 데 도움을 드립니다.
새로운 API, 수정된 API, 삭제된 API에 관한 자세한 목록은 API diff 보고서를 참고하세요. 새로운 API에 관한 자세한 내용은 Android API 참조를 방문하세요. 새로운 API가 강조 표시되어 쉽게 확인 가능합니다.
또한 플랫폼 변경사항이 앱에 영향을 미칠 수 있는 영역을 검토해야 합니다. 자세한 내용은 다음 페이지를 참고하세요.
- Android 17을 타겟팅할 때 앱에 영향을 미치는 동작 변경사항
targetSdkVersion과 관계없이 모든 앱에 영향을 미치는 동작 변경사항
핵심 기능
Android 17에서는 핵심 Android 기능과 관련된 다음과 같은 새로운 기능이 추가되었습니다.
새로운 ProfilingManager 트리거
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)
}
JobDebugInfo API
Android 17에서는 개발자가 JobScheduler 작업을 디버그하는 데 도움이 되는 새로운 JobDebugInfo API를 도입합니다. 작업이 실행되지 않는 이유, 실행된 시간, 기타 집계된 정보 등을 확인할 수 있습니다.
확장된 JobDebugInfo API의 첫 번째 메서드는 getPendingJobReasonStats()로, 작업이 대기 중인 실행 상태에 있었던 이유와 각 누적 대기 기간의 맵을 반환합니다. 이 메서드는 getPendingJobReasonsHistory() 및 getPendingJobReasons() 메서드를 결합하여 예약된 작업이 예상대로 실행되지 않는 이유를 파악할 수 있도록 지원하지만, 단일 메서드에서 기간과 작업 이유를 모두 사용할 수 있도록 하여 정보 검색을 간소화합니다.
예를 들어 지정된 jobId의 경우 이 메서드는 PENDING_JOB_REASON_CONSTRAINT_CHARGING와 60000ms의 기간을 반환하여 충전 제약 조건이 충족되지 않아 작업이 60000ms 동안 대기 중이었음을 나타낼 수 있습니다.
유휴 상태에서 허용 알람의 리스너 지원으로 웨이크 잠금 감소
Android 17
에서는 AlarmManager.setExactAndAllowWhileIdle 대신 PendingIntent을 허용하는 새로운 OnAlarmListener 변형이 도입되었습니다. 이 새로운 콜백 기반 메커니즘은 현재 지속적인 웨이크 잠금에 의존하여 소켓 연결을 유지하는 메시지 앱과 같은 주기적인 작업을 실행하는 앱에 적합합니다.
개인 정보 보호
Android 17에는 사용자 개인 정보 보호를 개선하기 위한 다음과 같은 새로운 기능이 포함되어 있습니다.
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 文档。
Android 연락처 선택 도구
Android 연락처 선택 도구는 사용자가 앱과 연락처를 공유할 수 있는 표준화된 탐색 가능한 인터페이스입니다. Android 17 (API 수준 37) 이상을 실행하는 기기에서 사용할 수 있는 선택 도구는 광범위한 READ_CONTACTS 권한에 대한 개인 정보 보호 대안을 제공합니다. 사용자의 전체 주소록에 대한 액세스를 요청하는 대신 앱은 전화번호나 이메일 주소와 같이 필요한 데이터 필드를 지정하고 사용자는 공유할 특정 연락처를 선택합니다. 이렇게 하면 앱에 선택한 데이터에 대한 읽기 액세스 권한만 부여되므로 UI를 빌드하거나 유지관리하지 않고도 내장 검색, 프로필 전환, 다중 선택 기능을 통해 일관된 사용자 환경을 제공하면서 세부적인 제어가 가능합니다.
자세한 내용은 연락처 선택기 문서를 참고하세요.
보안
Android 17에서는 기기 및 앱 보안을 개선하기 위한 다음과 같은 새로운 기능이 추가되었습니다.
Android 고급 보호 모드 (AAPM)
Android 고급 보호 모드는 Android 사용자에게 강력한 새로운 보안 기능을 제공하여 정교한 공격으로부터 사용자를 보호하는 데 중요한 역할을 합니다. 특히 위험도가 높은 사용자를 보호하는 데 효과적입니다. 선택 기능으로 설계된 AAPM은 사용자가 언제든지 사용 설정하여 의견이 반영된 보안 보호 세트를 적용할 수 있는 단일 구성 설정으로 활성화됩니다.
이러한 핵심 구성에는 알 수 없는 소스에서 앱 설치 차단(사이드로드)과 USB 데이터 신호 제한, Google Play 프로텍트 검사 의무화가 포함되어 기기의 공격 표면적을 크게 줄입니다.
개발자는 AdvancedProtectionManager API를 사용하여 이 기능과 통합하여 모드의 상태를 감지할 수 있으므로 사용자가 선택한 경우 애플리케이션이 강화된 보안 자세를 자동으로 채택하거나 위험도가 높은 기능을 제한할 수 있습니다.
PQC APK 서명
이제 Android는 양자 컴퓨팅을 활용하는 공격의 잠재적 위협으로부터 앱의 서명 ID를 보호하기 위해 하이브리드 APK 서명 스키마를 지원합니다. 이 기능은 기존 서명 키 (예: RSA 또는 EC)를 새로운 양자 내성 암호 (PQC) 알고리즘 (ML-DSA)과 페어링할 수 있는 새로운 APK 서명 스키마를 도입합니다.
이 하이브리드 접근 방식을 사용하면 기존 서명 확인에 의존하는 이전 Android 버전 및 기기와의 완전한 이전 버전 호환성을 유지하면서 향후 양자 공격으로부터 앱을 안전하게 보호할 수 있습니다.
개발자에게 미치는 영향
- Play 앱 서명을 사용하는 앱: Play 앱 서명을 사용하는 경우 Google Play에서 생성한 PQC 키를 사용하여 하이브리드 서명을 업그레이드하는 옵션을 Google Play에서 제공할 때까지 기다릴 수 있습니다. 이렇게 하면 수동 키 관리 없이 앱을 보호할 수 있습니다.
- 자체 관리 키를 사용하는 앱: 자체 서명 키를 관리하는 개발자는 업데이트된 Android 빌드 도구 (예: apksigner)를 활용하여 PQC 키와 새 기존 키를 결합하는 하이브리드 ID로 순환할 수 있습니다. (새 기존 키를 만들어야 하며 이전 키는 재사용할 수 없습니다.)
연결
Android 17에서는 기기 및 앱 연결을 개선하기 위한 다음과 같은 기능이 추가되었습니다.
제약된 위성 네트워크
实现优化,使应用能够在低带宽卫星网络上有效运行。
사용자 환경 및 시스템 UI
Android 17에는 사용자 환경을 개선하기 위한 다음과 같은 변경사항이 포함되어 있습니다.
전용 어시스턴트 볼륨 스트림
Android 17에서는 USAGE_ASSISTANT로 재생하기 위해 어시스턴트 앱 전용 어시스턴트 볼륨 스트림을 도입합니다. 이 변경사항은 어시스턴트 오디오를 표준 미디어 스트림에서 분리하여 사용자가 두 볼륨을 모두 격리된 방식으로 제어할 수 있도록 합니다. 이를 통해 어시스턴트 응답의 가청성을 유지하면서 미디어 재생을 음소거하는 등의 시나리오가 가능합니다.
새로운 MODE_ASSISTANT_CONVERSATION 오디오 모드에 액세스할 수 있는 어시스턴트 앱은 볼륨 제어 일관성을 더욱 개선할 수 있습니다. 어시스턴트 앱은 이 모드를 사용하여 활성 어시스턴트 세션에 관한 힌트를 시스템에 제공하여 활성 USAGE_ASSISTANT 재생 외부에서 또는 연결된 블루투스 주변기기를 사용하여 어시스턴트 스트림을 제어할 수 있습니다.
Handoff
핸드오프는 앱 개발자가 통합하여 사용자에게 교차 기기 연속성을 제공할 수 있는 Android 17의 새로운 기능 및 API입니다. 사용자가 한 Android 기기에서 앱 활동을 시작하고 다른 Android 기기로 전환할 수 있습니다. 핸드오프는 사용자 기기의 백그라운드에서 실행되며 수신 기기의 런처 및 작업 표시줄과 같은 다양한 진입점을 통해 사용자의 다른 주변 기기에서 사용 가능한 활동을 표시합니다.
앱은 핸드오프를 지정하여 수신 기기에 설치되어 있고 사용 가능한 경우 동일한 기본 Android 앱을 실행할 수 있습니다. 이 앱 간 흐름에서 사용자는 지정된 활동으로 딥 링크됩니다. 또는 앱-웹 핸드오프를 대체 옵션으로 제공하거나 URL 핸드오프를 사용하여 직접 구현할 수 있습니다.
핸드오프 지원은 활동별로 구현됩니다. 핸드오프를 사용 설정하려면 활동의
메서드를
setHandoffEnabled()
호출합니다. 수신 기기에서 다시 생성된 활동이 적절한 상태를 복원할 수 있도록 핸드오프와 함께 추가 데이터를 전달해야 할 수 있습니다. onHandoffActivityDataRequested() 콜백을 구현하여 핸드오프가 수신 기기에서 활동을 처리하고 다시 만드는 방법을 지정하는 세부정보가 포함된 HandoffActivityData 객체를 반환합니다.
실시간 업데이트 - 시맨틱 색상 API
在 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)
Android 17용 UWB 다운링크-TDoA API
다운링크 도착 시간 차이 (DL-TDoA) 범위 지정을 사용하면 기기가 신호의 상대적 도착 시간을 측정하여 여러 앵커를 기준으로 기기의 위치를 확인할 수 있습니다.다음 스니펫은 범위 지정 관리자를 초기화하고, 기기 기능을 확인하고, 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()
자바
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();