Android 17에는 개발자를 위한 훌륭한 새로운 기능과 API가 도입되었습니다. 다음 섹션에서는 관련 API를 시작하는 데 도움이 되도록 이러한 기능을 요약합니다.
새로운 API, 수정된 API, 삭제된 API에 관한 자세한 목록은 API 차이점 보고서를 참고하세요. 새로운 API에 관한 자세한 내용은 Android API 참조를 방문하세요. 새로운 API가 강조 표시되어 쉽게 확인 가능합니다.
플랫폼 변경사항이 앱에 영향을 미칠 수 있는 영역도 검토해야 합니다. 자세한 내용은 다음 페이지를 참조하세요.
핵심 기능
Android 17에서는 핵심 Android 기능과 관련된 다음과 같은 새로운 기능을 추가합니다.
새 ProfilingManager 트리거
Android 17 adds several new system triggers to ProfilingManager to
help you collect in-depth data to debug performance issues.
The new triggers are:
TRIGGER_TYPE_COLD_START: Trigger occurs during app cold start. It provides both a call stack sample and a system trace in the response.TRIGGER_TYPE_OOM: Trigger occurs when an app throws anOutOfMemoryErrorand provides a Java Heap Dump in response.TRIGGER_TYPE_KILL_EXCESSIVE_CPU_USAGE: Trigger occurs when an app is killed due to abnormal and excessive CPU usage and provides a call stack sample in response.TRIGGER_TYPE_ANOMALY: Detect system performance anomalies such as excessive binder calls and excessive memory usage.
To understand how to set up the system trigger, see the documentation on trigger-based profiling and how to retrieve and analyze profiling data documentation.
Profiling trigger for app anomalies
Android 17
introduces an on-device anomaly detection service that monitors for
resource-intensive behaviors and potential compatibility regressions. Integrated
with ProfilingManager, this service allows your app to receive profiling
artifacts triggered by specific system-detected events.
Use the TRIGGER_TYPE_ANOMALY trigger to detect system performance issues
such as excessive binder calls and excessive memory usage. When an app breaches
OS-defined memory limits, the anomaly trigger allows developers to receive
app-specific heap dumps to help identify and fix memory issues. Additionally,
for excessive binder spam, the anomaly trigger provides a stack sampling profile
on binder transactions.
This API callback occurs prior to any system imposed enforcements. For example, it can help developers collect debug data before the app is terminated by the system for exceeding memory limits.
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에서는 PendingIntent 대신 OnAlarmListener를 허용하는 새로운 AlarmManager.setExactAndAllowWhileIdle 변형이 도입되었습니다. 이 새로운 콜백 기반 메커니즘은 현재 연속 절전 모드를 사용하여 소켓 연결을 유지하는 메시지 앱과 같은 주기적 작업을 실행하는 앱에 적합합니다.
개인 정보 보호
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 연락처 선택 도구
The Android Contact Picker is a standardized, browsable interface for users to
share contacts with your app. Available on devices running
Android 17 (API level 37) or higher, the picker offers a privacy-preserving
alternative to the broad READ_CONTACTS permission. Instead of requesting
access to the user's entire address book, your app specifies the data fields it
needs, such as phone numbers or email addresses, and the user selects specific
contacts to share. This grants your app read access to only the selected data,
ensuring granular control while providing a consistent user experience with
built-in search, profile switching, and multi-selection capabilities without
having to build or maintain the UI.
For more information, see the contact picker documentation.
보안
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에서 Google Play에서 생성된 PQC 키를 사용하여 하이브리드 서명을 업그레이드하는 옵션을 제공할 때까지 기다릴 수 있습니다. 이렇게 하면 수동 키 관리 없이 앱을 보호할 수 있습니다.
- 자체 관리 키를 사용하는 앱: 자체 서명 키를 관리하는 개발자는 업데이트된 Android 빌드 도구 (예: apksigner)를 활용하여 하이브리드 ID로 순환하고 PQC 키를 새 기존 키와 결합할 수 있습니다. (새로운 클래식 키를 만들어야 하며 이전 키는 재사용할 수 없습니다.)
연결
Android 17에서는 기기 및 앱 연결을 개선하기 위해 다음 기능을 추가합니다.
제약이 있는 위성 네트워크
앱이 낮은 대역폭 위성 네트워크에서 효과적으로 작동할 수 있도록 최적화를 구현합니다.
사용자 환경 및 시스템 UI
Android 17에는 사용자 환경을 개선하기 위한 다음 변경사항이 포함되어 있습니다.
전용 어시스턴트 볼륨 스트림
Android 17에서는 USAGE_ASSISTANT로 재생하기 위해 어시스턴트 앱을 위한 전용 어시스턴트 볼륨 스트림을 도입합니다. 이 변경사항은 어시스턴트 오디오를 표준 미디어 스트림에서 분리하여 사용자가 두 볼륨을 모두 격리된 상태로 제어할 수 있도록 합니다. 이를 통해 어시스턴트 응답의 가청성을 유지하면서 미디어 재생을 음소거하는 등의 시나리오가 가능합니다.
새로운 MODE_ASSISTANT_CONVERSATION 오디오 모드에 액세스할 수 있는 어시스턴트 앱은 볼륨 제어 일관성을 더욱 개선할 수 있습니다. 어시스턴트 앱은 이 모드를 사용하여 활성 어시스턴트 세션에 관한 힌트를 시스템에 제공하여 활성 USAGE_ASSISTANT 재생 외부에서 또는 연결된 블루투스 주변기기를 사용하여 어시스턴트 스트림을 제어할 수 있습니다.
핸드오프
핸드오프는 앱 개발자가 통합하여 사용자에게 교차 기기 연속성을 제공할 수 있는 Android 17의 새로운 기능이자 API입니다. 이를 통해 사용자는 한 Android 기기에서 앱 활동을 시작하고 다른 Android 기기로 전환할 수 있습니다. 핸드오프는 사용자 기기의 백그라운드에서 실행되며 수신 기기의 런처 및 작업 표시줄과 같은 다양한 진입점을 통해 사용자의 다른 근처 기기에서 사용 가능한 활동을 표시합니다.
앱은 수신 기기에 설치되어 있고 사용할 수 있는 경우 Handoff를 지정하여 동일한 네이티브 Android 앱을 실행할 수 있습니다. 이 앱 간 흐름에서 사용자는 지정된 활동으로 딥 링크됩니다. 또는 앱-웹 핸드오프를 대체 옵션으로 제공하거나 URL 핸드오프를 사용하여 직접 구현할 수 있습니다.
핸드오프 지원은 활동별로 구현됩니다. 핸드오프를 사용 설정하려면 활동의 setHandoffEnabled() 메서드를 호출합니다. 수신 기기에서 다시 생성된 활동이 적절한 상태를 복원할 수 있도록 핸드오프와 함께 추가 데이터를 전달해야 할 수 있습니다. onHandoffActivityDataRequested() 콜백을 구현하여 핸드오프가 수신 기기에서 활동을 처리하고 다시 만드는 방법을 지정하는 세부정보가 포함된 HandoffActivityData 객체를 반환합니다.
실시간 업데이트 - 시맨틱 색상 API
With Android 17, Live Update launches the Semantic Coloring APIs to support colors with universal meaning.
The following classes support semantic coloring:
NotificationNotification.MetricNotification.ProgressStyle.PointNotification.ProgressStyle.Segment
Coloring
- Green: Associated with safety. This color should be used for the case where it lets people know you are in the safe situation.
- Orange: For designating caution and marking physical hazards. This color should be used in the situation where users need to pay attention to set better protection setting.
- Red: Generally indicates danger, stop. It should be presented for the case where need people's attention urgently.
- Blue: Neutral color for content that is informational and should stand out from other content.
The following example shows how to apply semantic styles to text in a notification:
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
Downlink Time Difference of Arrival (DL-TDoA) ranging lets a device determine its position relative to multiple anchors by measuring the relative arrival times of signals.The following snippet demonstrates how to initialize the Ranging Manager, verify device capabilities, and start a DL-TDoA session:
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
}
}
}
Out-of-Band (OOB) Configurations
The following snippet provides an example of DL-TDoA OOB configuration data for Wi-Fi and 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
};
If you can't use an OOB configuration because it is missing, or if you need to
change default values that aren't in the OOB config, you can build parameters
with DlTdoaRangingParams.Builder as shown in the following snippet. You can
use these parameters in place of
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();