Android 17에는 개발자를 위한 훌륭한 새로운 기능과 API가 도입되었습니다. 다음 섹션에서는 관련 API를 시작하는 데 도움이 되도록 이러한 기능을 요약합니다.
새로운 API, 수정된 API, 삭제된 API에 관한 자세한 목록은 API 차이점 보고서를 참고하세요. 새로운 API에 관한 자세한 내용은 Android API 참조를 방문하세요. 새로운 API가 강조 표시되어 쉽게 확인 가능합니다.
또한 플랫폼 변경사항이 앱에 영향을 미칠 수 있는 영역을 검토해야 합니다. 자세한 내용은 다음 페이지를 참조하세요.
핵심 기능
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: 과도한 바인더 호출 및 과도한 메모리 사용과 같은 시스템 성능 이상을 감지합니다.
시스템 트리거를 설정하는 방법을 이해하려면 트리거 기반 프로파일링에 관한 문서와 프로파일링 데이터를 검색하고 분석하는 방법에 관한 문서를 참고하세요.
앱 이상에 대한 프로파일링 트리거
Android 17에서는 리소스 집약적인 동작과 잠재적인 호환성 회귀를 모니터링하는 온디바이스 이상 감지 서비스를 도입합니다. ProfilingManager와 통합된 이 서비스를 사용하면 앱에서 특정 시스템 감지 이벤트에 의해 트리거된 프로파일링 아티팩트를 수신할 수 있습니다.
TRIGGER_TYPE_ANOMALY 트리거를 사용하여 과도한 바인더 호출 및 과도한 메모리 사용과 같은 시스템 성능 문제를 감지합니다. 앱이 OS 정의 메모리 한도를 위반하면 이상 트리거를 통해 개발자가 앱별 힙 덤프를 수신하여 메모리 문제를 식별하고 해결할 수 있습니다. 또한 과도한 바인더 스팸의 경우 이상 트리거는 바인더 트랜잭션에 관한 스택 샘플링 프로필을 제공합니다.
이 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가 도입되었습니다. 이 API를 사용하면 작업이 실행되지 않는 이유, 실행 시간, 기타 집계된 정보를 확인할 수 있습니다.
확장된 JobDebugInfo API의 첫 번째 메서드는 getPendingJobReasonStats()입니다. 이 메서드는 작업이 실행 대기 중 상태인 이유와 각 누적 대기 기간의 맵을 반환합니다. 이 메서드는 getPendingJobReasonsHistory() 및 getPendingJobReasons() 메서드를 조인하여 예약된 작업이 예상대로 실행되지 않는 이유에 대한 통계를 제공하지만, 단일 메서드에서 기간과 작업 이유를 모두 제공하여 정보 검색을 간소화합니다.
예를 들어 지정된 jobId의 경우 메서드는 PENDING_JOB_REASON_CONSTRAINT_CHARGING 및 60000ms의 기간을 반환할 수 있습니다. 이는 충전 제약 조건이 충족되지 않아 작업이 60000ms 동안 대기 중이었음을 나타냅니다.
유휴 상태에서 허용 알람에 대한 리스너 지원으로 wake lock 줄이기
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를 빌드하거나 유지관리할 필요 없이 내장된 검색, Google 검색 프로필 전환, 다중 선택 기능을 통해 일관된 사용자 환경을 제공할 수 있습니다.
자세한 내용은 연락처 선택 도구 문서를 참고하세요.
보안
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는 하이브리드 APK 서명 스키마를 지원하여 양자 컴퓨팅을 사용하는 잠재적인 공격 위협으로부터 앱의 서명 ID를 미래에도 안전하게 보호합니다. 이 기능은 기존 서명 키 (예: 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 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
onHandoffActivityDataRequested()
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 Downlink-TDoA API
DL-TDoA (Downlink Time Difference of Arrival) 측정은 기기가 신호의 상대적 도착 시간을 측정하여 여러 앵커를 기준으로 상대적 위치를 결정할 수 있도록 합니다.다음 스니펫은 측정 관리자를 초기화하고, 기기 기능을 확인하고, 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
}
}
자바
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 구성 데이터의 예를 보여줍니다.
자바
// 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();