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 사용으로 인해 앱이 종료될 때 트리거가 발생하고 이에 대한 응답으로 호출 스택 샘플을 제공합니다.
시스템 트리거를 설정하는 방법을 알아보려면 트리거 기반 프로파일링 및 프로파일링 데이터 검색 및 분석 방법에 관한 문서를 참고하세요.
JobDebugInfo API
Android 17에서는 개발자가 JobScheduler 작업을 디버그하는 데 도움이 되는 새로운 JobDebugInfo API를 도입합니다. 작업이 실행되지 않는 이유, 실행된 시간, 기타 집계된 정보 등을 확인할 수 있습니다.
확장된 JobDebugInfo API의 첫 번째 메서드는 getPendingJobReasonStats()이며, 이는 작업이 실행 대기 상태에 있었던 이유와 각 누적 대기 기간의 지도를 반환합니다. 이 메서드는 getPendingJobReasonsHistory() 및 getPendingJobReasons() 메서드를 결합하여 예약된 작업이 예상대로 실행되지 않는 이유를 파악할 수 있도록 지원하지만, 단일 메서드에서 기간과 작업 이유를 모두 제공하여 정보 검색을 간소화합니다.
예를 들어 지정된 jobId의 경우 이 메서드는 PENDING_JOB_REASON_CONSTRAINT_CHARGING와 60000ms의 기간을 반환하여 충전 제약 조건이 충족되지 않아 작업이 60000ms 동안 대기 중임을 나타낼 수 있습니다.
개인 정보 보호
Android 17에는 사용자 개인 정보 보호를 개선하는 다음과 같은 새로운 기능이 포함되어 있습니다.
Android 연락처 선택기
Android 연락처 선택 도구는 사용자가 앱과 연락처를 공유할 수 있는 표준화된 탐색 가능한 인터페이스입니다. Android 17 이상을 실행하는 기기에서 사용할 수 있는 선택 도구는 광범위한 READ_CONTACTS 권한을 대체하는 개인 정보 보호 대안을 제공합니다. 사용자의 전체 주소록에 대한 액세스를 요청하는 대신 앱은 전화번호나 이메일 주소와 같이 필요한 데이터 필드를 지정하고 사용자는 공유할 특정 연락처를 선택합니다. 이렇게 하면 앱에 선택한 데이터에 대한 읽기 액세스 권한만 부여되므로 UI를 빌드하거나 유지관리하지 않고도 내장 검색, 프로필 전환, 다중 선택 기능을 통해 일관된 사용자 환경을 제공하면서 세부적인 제어가 가능합니다.
자세한 내용은 연락처 선택기 문서를 참고하세요.
보안
Android 17에서는 기기 및 앱 보안을 개선하기 위해 다음과 같은 새로운 기능을 추가합니다.
Android 고급 보호 모드 (AAPM)
Android 고급 보호 모드는 Android 사용자에게 강력한 새로운 보안 기능을 제공하여 정교한 공격으로부터 사용자를 보호하는 데 중요한 역할을 합니다. 특히 위험도가 높은 사용자를 보호하는 데 효과적입니다. 선택 기능으로 설계된 AAPM은 사용자가 언제든지 사용 설정하여 의견이 반영된 보안 보호 세트를 적용할 수 있는 단일 구성 설정으로 활성화됩니다.
이러한 핵심 구성에는 알 수 없는 소스에서 앱 설치 차단(사이드로드)과 USB 데이터 신호 제한, Google Play 프로텍트 검사 의무화가 포함되어 기기의 공격 표면적을 크게 줄입니다.
개발자는 AdvancedProtectionManager API를 사용하여 이 기능과 통합하여 모드의 상태를 감지할 수 있으므로 사용자가 선택한 경우 애플리케이션이 강화된 보안 자세를 자동으로 채택하거나 위험도가 높은 기능을 제한할 수 있습니다.
연결
Android 17에서는 기기 및 앱 연결을 개선하기 위해 다음 기능을 추가합니다.
제약이 있는 위성 네트워크
앱이 낮은 대역폭 위성 네트워크에서 효과적으로 작동할 수 있도록 최적화를 구현합니다.
사용자 환경 및 시스템 UI
Android 17에는 사용자 환경을 개선하기 위한 다음 변경사항이 포함되어 있습니다.
핸드오프
핸드오프는 앱 개발자가 통합하여 사용자에게 교차 기기 연속성을 제공할 수 있는 Android 17의 새로운 기능이자 API입니다. 이를 통해 사용자는 한 Android 기기에서 앱 활동을 시작하고 다른 Android 기기로 전환할 수 있습니다. 핸드오프는 사용자의 기기 백그라운드에서 실행되며 수신 기기의 런처 및 작업 표시줄과 같은 다양한 진입점을 통해 사용자의 다른 근처 기기에서 사용 가능한 활동을 표시합니다.
앱은 수신 기기에 설치되어 있고 사용할 수 있는 경우 Handoff를 지정하여 동일한 네이티브 Android 앱을 실행할 수 있습니다. 이 앱 간 흐름에서 사용자는 지정된 활동으로 딥 링크됩니다. 또는 앱-웹 핸드오프를 대체 옵션으로 제공하거나 URL 핸드오프를 사용하여 직접 구현할 수 있습니다.
핸드오프 지원은 활동별로 구현됩니다. 핸드오프를 사용 설정하려면 활동의 setHandoffEnabled() 메서드를 호출합니다. 수신 기기에서 다시 생성된 활동이 적절한 상태를 복원할 수 있도록 핸드오프와 함께 추가 데이터를 전달해야 할 수 있습니다. onHandoffActivityRequested() 콜백을 구현하여 핸드오프가 수신 기기에서 활동을 처리하고 다시 만드는 방법을 지정하는 세부정보가 포함된 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) 범위 지정은 기기가 신호의 상대적 도착 시간을 측정하여 여러 앵커에 대한 위치를 결정할 수 있도록 합니다.
다음 스니펫은 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();