Android 17에서는 개발자를 위한 훌륭한 새 기능과 API가 도입됩니다. 다음 섹션에서는 이러한 기능을 요약하여 관련 API를 시작하는 데 도움을 줍니다.
새로운 API, 수정된 API, 삭제된 API에 관한 자세한 목록은 API 차이점 보고서를 참고하세요. 새로운 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 사용으로 인해 앱이 종료될 때 트리거가 발생하고 이에 대한 응답으로 호출 스택 샘플을 제공합니다.
시스템 트리거를 설정하는 방법을 알아보려면 트리거 기반 프로파일링 및 프로파일링 데이터 검색 및 분석 방법에 관한 문서를 참고하세요.
JobDebugInfo API
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.
유휴 상태에서 허용 알람의 리스너 지원으로 절전 모드 해제 잠금 감소
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.
개인 정보 보호
Android 17에는 사용자 개인 정보 보호를 개선하기 위한 다음과 같은 새로운 기능이 포함되어 있습니다.
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.
Android 연락처 선택 도구
Android 연락처 선택 도구는 사용자가 앱과 연락처를 공유할 수 있는 표준화된 탐색 가능한 인터페이스입니다. Android 17 (API 수준 37) 이상을 실행하는 기기에서 사용할 수 있는 선택 도구는 광범위한 READ_CONTACTS 권한을 대체하는 개인 정보 보호 대안을 제공합니다. 사용자의 전체 주소록에 대한 액세스를 요청하는 대신 앱은 전화번호나 이메일 주소와 같이 필요한 데이터 필드를 지정하고 사용자는 공유할 특정 연락처를 선택합니다. 이렇게 하면 앱에 선택한 데이터에 대한 읽기 액세스 권한만 부여되므로 UI를 빌드하거나 유지관리하지 않고도 내장 검색, 프로필 전환, 다중 선택 기능을 통해 일관된 사용자 환경을 제공하면서 세부적인 제어가 가능합니다.
자세한 내용은 연락처 선택기 문서를 참고하세요.
보안
Android 17에서는 기기 및 앱 보안을 개선하기 위한 다음과 같은 새로운 기능이 추가되었습니다.
Android 고급 보호 모드 (AAPM)
Android Advanced Protection Mode offers Android users a powerful new set of security features, marking a significant step in safeguarding users—particularly those at higher risk—from sophisticated attacks. Designed as an opt-in feature, AAPM is activated with a single configuration setting that users can turn on at any time to apply an opinionated set of security protections.
These core configurations include blocking app installation from unknown sources
(sideloading), restricting USB data signaling, and mandating Google Play Protect
scanning, which significantly reduces the device's attack surface area.
Developers can integrate with this feature using the
AdvancedProtectionManager API to detect the mode's status, enabling
applications to automatically adopt a hardened security posture or restrict
high-risk functionality when a user has opted in.
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 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 is a new feature and API coming to Android 17 that app developers can integrate with to provide cross-device continuity for their users. It allows the user to start an app activity on one Android device and transition it to another Android device. Handoff runs in the background of a user's device and surfaces available activities from the user's other nearby devices through various entry points, like the launcher and taskbar, on the receiving device.
Apps can designate Handoff to launch the same native Android app, if it is installed and available on the receiving device. In this app-to-app flow, the user is deep-linked to the designated activity. Alternatively, app-to-web Handoff can be offered as a fallback option or directly implemented with URL Handoff.
Handoff support is implemented on a per-activity basis. To enable Handoff, call
the setHandoffEnabled() method for the activity. Additional data may need to
be passed along with the handoff so the recreated activity on the receiving
device can restore appropriate state. Implement the
onHandoffActivityRequested() callback to return a HandoffActivityData object
which contains details that specify how Handoff should handle and recreate
the activity on the receiving device.
실시간 업데이트 - 시맨틱 색상 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) 범위 지정은 기기가 신호의 상대적 도착 시간을 측정하여 여러 앵커에 대한 위치를 결정할 수 있도록 합니다.
다음 스니펫은 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
}
}
자바
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 구성 데이터의 예를 제공합니다.
자바
// 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();