
안드로이드 앱개발을 하고 있다면 지금 당장 확인해야 할 소식이 있다. 2026년 6월 16일, Google이 Android 17(코드명 Cinnamon Bun, API Level 37)을 공식 출시했다. targetSdk 37 마이그레이션은 아직 의무가 아니지만, 2027년 8월 Google Play가 신규 앱과 업데이트에 targetSdk 37을 요구할 가능성이 높다. 앱이 크래시 없이 작동하는지, 어떤 코드를 먼저 손봐야 하는지, 이 가이드에서 단계별로 정리한다.
targetSdk 37이란? 안드로이드 개발에 필수인 Android 17 (Cinnamon Bun) 대응
Android 17 = API Level 37 = 코드명 Cinnamon Bun. 이 세 가지는 동일한 버전을 가리킨다. 2026년 2월 13일 Beta 1이 공개되었고, 3월에 Platform Stability(최종 SDK/NDK API 확정)를 달성한 뒤 6월 16일 Pixel 기기를 시작으로 Honor, iQOO, Lenovo, OnePlus, OPPO 등 파트너사 기기에 동시 배포되었다.
Google Play의 targetSdk 정책은 매년 한 단계씩 요구 수준을 높이는 구조다. 신규 앱과 업데이트는 최신 Android 출시 1년 이내 API 레벨을 타겟팅해야 Play Store에 업로드가 가능하고, 업데이트 없이 유지되는 기존 앱도 최신 Android 출시 2년 이내 API 레벨을 충족해야 새 사용자에게 노출된다.
현재 기준(2026년 6월)으로는 신규 앱에 targetSdk 36(Android 16) 의무화가 이미 진행 중이다. targetSdk 37은 2027년 8월 의무화가 예상되는 만큼, 지금 미리 대응해두면 마감 직전의 혼란을 피할 수 있다.
| 연도 | 신규 앱/업데이트 최소 targetSdk | 기존 앱 최소 (새 사용자 노출) |
|---|---|---|
| 2023 | API 33 (Android 13) | API 31 |
| 2024 | API 34 (Android 14) | API 33 |
| 2025 | API 35 (Android 15) | API 34 |
| 2026 (현재) | API 36 (Android 16), 2026년 8월~ | API 35 |
| 2027 (예상) | API 37 (Android 17), 2027년 8월 예상 | API 36 예상 |
데드라인 연장 신청도 가능하다. 통상 2개월 내외 유예를 받을 수 있지만, 대형 앱일수록 마이그레이션 검증에 시간이 걸리기 때문에 지금부터 준비하는 것이 낫다.

환경 설정: 업그레이드 전 필수 준비사항
targetSdk 37로 바로 올리기 전에 확인해야 할 전제 조건이 있다. 많은 가이드가 이 부분을 생략하는데, 이것이 오히려 마이그레이션을 더 어렵게 만드는 원인이 된다.
targetSdk 36(Android 16) 선행 요구사항 먼저 확인
targetSdk 36을 건너뛰고 바로 37로 올리면 두 버전의 breaking change를 동시에 맞닥뜨린다. 아래 항목이 이미 적용되어 있는지 먼저 점검해야 한다.
| Android 16 (targetSdk 36) 변경사항 | 비고 |
|---|---|
| Edge-to-Edge 강제 | windowOptOutEdgeToEdgeEnforcement 옵션 무시됨. Android 15 opt-out 완전 제거 |
| 16KB 페이지 정렬 | 2026년 5월 31일 이후 Play 제출 거부. 네이티브 앱 필수 대응 |
| Predictive Back 강제 | android:enableOnBackInvokedCallback="true" 설정 필요 |
| 대형 화면 리사이저빌리티 | 36에서는 opt-out 가능. Android 17에서 opt-out 완전 제거되므로 36에서 테스트 권장 |
필수 개발 환경
안드로이드스튜디오는 Meerkat (2024.3.1) 이상, Android Gradle Plugin(AGP)은 8.9.0-rc01 이상이어야 한다. AGP 버전을 올리기 부담스럽다면 AGP Upgrade Assistant(Tools > AGP Upgrade Assistant)를 활용하면 단계적 업그레이드가 가능하다.
build.gradle 변경
Kotlin DSL을 사용하는 프로젝트라면 다음과 같이 수정한다.
// build.gradle.kts (Kotlin DSL)
android {
compileSdk = 37
defaultConfig {
targetSdk = 37
minSdk = 21 // 기존 minSdk 유지 가능
}
}
Groovy DSL을 사용하는 경우에는 아래와 같다.
// build.gradle (Groovy DSL)
android {
compileSdk = 37
defaultConfig {
targetSdkVersion 37
}
}
안드로이드스튜디오에서 Android SDK Platform 37 설치
- Android Studio 메뉴에서 Tools > SDK Manager 열기
- SDK Platforms 탭에서 Android Cinnamon Bun Preview 항목 확장 후 Android SDK Platform Cinnamon Bun 체크
- SDK Tools 탭에서 Android SDK Build-Tools 37 최신 버전(
37.x.x) 선택 - OK를 눌러 설치 완료
2단계 마이그레이션 접근법 (권장)
공식 Android 마이그레이션 가이드에서 권장하는 방식은 한 번에 올리는 것이 아닌 2단계 접근이다. Phase 1에서는 targetSdk를 건드리지 않은 채 all-apps 동작 변경사항(targetSdk와 무관하게 Android 17 기기에서 모든 앱에 적용되는 변경)을 먼저 테스트하고 배포한다. 앱 안정성을 최소 1주 이상 모니터링한 뒤 Phase 2에서 compileSdk = 37, targetSdk = 37로 올리고 targetSdk 전용 변경사항을 대응한다.
비공개 API 사용 여부를 확인하려면 StrictMode를 활성화해 logcat 경고를 확인한다.
// Application.onCreate() 또는 debug 빌드 한정으로 설정
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder()
.detectNonSdkApiUsage()
.penaltyLog()
.build()
)
targetSdk 37 핵심 변경사항 완전 분석

변경사항이 많다. 어디부터 시작해야 할지 모르겠다면 우선순위 기준으로 접근하는 것이 현실적이다. 앱 크래시를 일으킬 수 있는 것부터 먼저 처리하고, 기능 누락 위험이 있는 것, 사용자 경험 저하 가능성이 있는 것 순으로 내려간다.
[긴급] 앱 크래시 / 기능 완전 중단 위험
이 범주는 대응하지 않으면 앱이 즉시 크래시 나거나 핵심 기능이 완전히 중단된다. targetSdk 37로 올리기 전에 반드시 해결해야 한다.
| 변경사항 | 영향 | 대응 방법 |
|---|---|---|
| Static final 리플렉션 금지 | IllegalAccessException / JNI 크래시 |
리플렉션 제거, 의존성 주입 패턴 적용 |
| Native DCL 읽기 전용 강제 | UnsatisfiedLinkError |
.so 파일 권한을 build 단계에서 읽기 전용(444)으로 자동 설정. 커스텀 로드는 runtime에 chmod 444 검증 |
| Lock-Free MessageQueue | private 필드 리플렉션 앱 깨짐 | 공개 API 사용, TestLooperManager 활용 |
| 배경 오디오 WIU 요구 | 오디오 무음 / AudioFocus 거부 | Media3 MediaSessionService 마이그레이션 |
| BAL 강화 | 백그라운드 Activity 시작 불가 | MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE |
Static final 리플렉션 금지는 테스트 프레임워크에도 영향을 준다. Mockito 등 리플렉션에 의존하는 테스트 도구가 취약하다. 아래 패턴이 코드베이스에 있다면 즉시 제거해야 한다.
// Android 17에서 IllegalAccessException 발생
Field field = MyClass.class.getDeclaredField("STATIC_FINAL_FIELD");
field.setAccessible(true);
field.set(null, newValue); // 예외 발생
// 대신 의존성 주입 패턴 또는 @VisibleForTesting + package-private 활용
배경 오디오 강화는 API 35~36과 달리 API 37에서는 While-In-Use(WIU) 포어그라운드 서비스가 반드시 필요하다. AudioTrack 재생, AudioFocus 요청이 모두 차단된다. Media3 MediaSessionService로 전환하는 코드는 다음과 같다.
class BackgroundAudioService : MediaSessionService() {
private var mediaSession: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = ExoPlayer.Builder(this).build()
mediaSession = MediaSession.Builder(this, player).build()
}
override fun onGetSession(
controllerInfo: MediaSession.ControllerInfo
): MediaSession? = mediaSession
override fun onDestroy() {
mediaSession?.run {
player.release()
release()
}
super.onDestroy()
}
}
AndroidManifest.xml에도 서비스 선언이 필요하다.
<service
android:name=".BackgroundAudioService"
android:foregroundServiceType="mediaPlayback"
android:exported="true"
android:permission="android.permission.BIND_MEDIA_SESSION_SERVICE">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
</intent-filter>
</service>
[중요] 권한 및 보안 강화
즉시 크래시는 아니지만 기능이 조용히 중단될 수 있다. 로컬 네트워크 기능이나 SMS OTP를 사용하는 앱은 특히 주의해야 한다.
| 변경사항 | 영향 | 대응 방법 |
|---|---|---|
ACCESS_LOCAL_NETWORK 권한 필수 |
mDNS/소켓 차단, UDP EPERM |
매니페스트 선언 + 런타임 요청 또는 시스템 피커 사용 |
| OTP SMS 3시간 지연 | 일반 SMS OTP 자동 감지 불가 | SMS Retriever API / SMS User Consent API 전환 |
| Certificate Transparency 기본 활성 | 비준수 인증서 연결 실패 | 내부 서버 인증서 CT 준수 여부 확인 |
| ContactsProvider2 PII 필드 제거 | ACCOUNT_NAME, ACCOUNT_TYPE 컬럼 조회 실패 |
ContactsContract.RawContacts로 쿼리 이전 |
로컬 네트워크 권한은 mDNS를 통한 기기 검색, TCP/UDP 로컬 소켓, IoT 기기 통신 등을 사용하는 앱에 직접 영향을 준다. AndroidManifest.xml에 다음 권한을 추가하고 런타임에도 요청해야 한다.
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
시스템 미디에이션 피커(DiscoveryRequest.FLAG_SHOW_PICKER)를 활용하면 런타임 권한 없이도 사용자가 직접 기기를 선택하는 방식으로 구현할 수 있다. 권한 요청 화면을 보여주는 것이 부담스러운 경우 고려해볼 만한 대안이다.
[모니터링] UX/UI 변경
| 변경사항 | 영향 | 대응 방법 |
|---|---|---|
| 대형 화면 방향 opt-out 불가 | 태블릿(600dp+)에서 세로 고정 무시 | Compose WindowSizeClass 기반 반응형 레이아웃 |
| IME 가시성 미복원 | 회전 후 소프트 키보드 안 뜸 | stateAlwaysVisible 또는 수동 표시 요청 |
600dp 이상 기기(태블릿, 폴더블)에서는 screenOrientation, setRequestedOrientation(), resizeableActivity가 모두 무시된다. 게임 앱(android:appCategory="game")만 예외다. 레이아웃 대응 예시는 다음과 같다.
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
.widthIn(max = 300.dp)
.fillMaxWidth()
.verticalScroll(rememberScrollState())
.padding(16.dp)
) {
// 콘텐츠
}
}
[연결성] Bluetooth
BluetoothSocket RFCOMM에서 InputStream.read()의 반환 동작이 바뀌었다. 연결 끊김 시 이전에는 IOException이 발생했지만, 이제는 -1을 반환한다. 루프 패턴을 다음처럼 수정해야 한다.
// 연결 끊김을 -1로 감지하는 패턴
int bytes;
while ((bytes = inputStream.read(buffer)) != -1) {
// 수신 데이터 처리
}
// -1 반환 시 연결 종료 처리
단계별 마이그레이션 체크리스트
실제 프로젝트에서 사용할 수 있도록 두 단계로 나눠 정리한다. Phase 1을 먼저 배포하고 앱 안정성을 검증한 뒤 Phase 2로 넘어가는 것이 권장 방식이다.
Phase 1: targetSdk 변경 없이 호환성 확보 (먼저 배포)
- Android Studio Meerkat (2024.3.1+) 및 AGP 8.9.0-rc01+ 업그레이드
- Android 17 에뮬레이터 또는 실기기에서 앱 실행 테스트
- StrictMode
detectNonSdkApiUsage()활성화 후 logcat 경고 점검 adb shell cmd compat enable|disable로 개별 변경사항 독립 테스트- IME 가시성 확인: 회전 후 소프트 키보드 상태 테스트
- 배경 오디오 테스트:
adb shell cmd audio set-enable-hardening enable - 메모리 제한 테스트: RAM별 한도 초과 시 프로세스 종료 여부 확인
- Phase 1 배포 후 앱 안정성 최소 1주 모니터링
Phase 2: compileSdk/targetSdk 37 업데이트
- build.gradle에서
compileSdk = 37,targetSdk = 37변경 - 리플렉션으로
static final필드 수정하는 코드 전수 검색:
grep -r "setAccessible" --include="*.java" --include="*.kt" .
ACCESS_LOCAL_NETWORK권한 필요 여부 확인 (mDNS, 소켓, 기기 검색 기능 보유 앱)- OTP SMS 처리 로직: SMS Retriever API 사용 여부 확인
- Certificate Transparency: 내부/파트너 서버 인증서 CT 준수 여부 확인
- 오디오 관련 코드: WIU FGS 적용 여부 확인 (ExoPlayer 2 사용 중이면 Media3 마이그레이션 필수)
- 대형 화면/폴더블: 실기기 또는 에뮬레이터에서 회전 및 리사이즈 테스트
- Bluetooth 사용 앱: RFCOMM
InputStream.read()반환값 -1 처리 로직 추가 - BAL 관련:
MODE_BACKGROUND_ACTIVITY_START_ALLOWEDdeprecated 상수 사용 여부 확인 ContactsContract.Data에서ACCOUNT_NAME/ACCOUNT_TYPE조회 코드 점검- CameraX 버전: 1.5.2 또는 1.6.0+ 사용 여부 확인 (targetSdk 37에서 HDR 동적 범위 처리로 인한
IllegalArgumentException크래시 방지) - Native .so 파일: 읽기 전용 권한(444) 확인
- 내부 QA: targetSdk 37 빌드로 전체 smoke test
- Google Play internal test track 배포 후 24~48시간 모니터링
- 프로덕션 점진적 출시 (10% 시작 후 50%, 100% 단계적 확대)

흔한 오류와 해결 방법
targetSdk 37로 올린 뒤 실제로 자주 발생하는 오류 패턴과 해결 방법을 정리한다. 에러 메시지로 검색해 여기까지 왔다면 바로 해당 항목을 찾아보면 된다.
오류 1: IllegalAccessException (static final 필드 접근 거부)
증상: 앱 시작 시 즉시 크래시. logcat에 java.lang.IllegalAccessException
원인: 리플렉션으로 static final 필드 수정 시도. Mockito 등 테스트 프레임워크에서도 발생 가능
해결: 의존성 주입(DI) 패턴으로 전환. 테스트 코드라면 @VisibleForTesting + package-private 필드로 대체
오류 2: UnsatisfiedLinkError (.so 파일 로드 실패)
증상: JNI/NDK 사용 앱에서 네이티브 라이브러리 로드 시 크래시
원인: System.load()로 로드하는 .so 파일에 쓰기 권한이 남아 있음. APK 빌드 시 파일 권한이 보존되지 않는 경우 발생
해결: build.gradle에서 packagingOptions로 명시하거나, 런타임에 파일 권한을 검증
// build.gradle에서 .so 파일 권한 설정
android {
packagingOptions {
pickFirst 'lib/arm64-v8a/libmylibrary.so'
}
}
// 또는 runtime 검증
File soFile = new File(context.getApplicationInfo().nativeLibraryDir, "libmylibrary.so");
// chmod 444로 확인되지 않으면 경고 로깅
오류 3: 오디오 무음 / AudioFocus 거부
증상: 백그라운드에서 음악/오디오 재생이 무음이거나 즉시 중단. AUDIOFOCUS_REQUEST_FAILED 반환
원인: WIU(While-In-Use) 포어그라운드 서비스 없이 백그라운드 오디오 시도. API 35~36까지는 일반 FGS만 있으면 됐지만 API 37에서는 WIU FGS가 필수
해결: Media3 MediaSessionService + foregroundServiceType="mediaPlayback" 적용. 위 코드 예시 참고.
오류 4: 로컬 네트워크 연결 타임아웃
증상: mDNS 서비스 검색 실패, TCP 연결이 조용히 타임아웃, UDP EPERM 오류
원인: ACCESS_LOCAL_NETWORK 권한 미선언
해결: 매니페스트에 권한 선언 후 런타임에도 요청. 아래 adb 명령으로 먼저 재현 테스트 가능
adb shell am compat enable RESTRICT_LOCAL_NETWORK <package_name>
오류 5: OTP SMS 수신 실패 (3시간 지연)
증상: 사용자가 OTP를 수신했다고 하는데 앱에서 자동 감지가 안 됨
원인: targetSdk 37 앱이 일반 SMS OTP를 수신 후 3시간 동안 읽기 차단. 기본 SMS 앱과 어시스턴트 앱은 예외
해결: SMS Retriever API 또는 SMS User Consent API로 전환. 서버에서 발송하는 OTP 메시지를 Retriever 형식으로 수정 필요
오류 6: 태블릿/폴더블에서 레이아웃 깨짐
증상: 세로 고정 앱이 태블릿에서 가로로 전환되면서 UI 배치가 깨짐
원인: 600dp 이상 기기에서 screenOrientation 강제 무시. opt-out 불가
해결: Compose WindowSizeClass 기반 반응형 레이아웃 구현. 카메라 프리뷰는 CameraX PreviewView 활용 권장
Android 17의 새로운 기회: targetSdk 37 신규 기능
breaking change 대응에만 집중하다 보면 Android 17이 개발자에게 제공하는 새로운 도구들을 놓치기 쉽다. 마이그레이션을 마친 뒤 바로 활용할 수 있는 실용성 높은 기능 다섯 가지를 소개한다.
AppFunctions & Android MCP
@AppFunction 어노테이션을 통해 앱의 특정 기능을 AI 어시스턴트(Gemini 등)가 도구로 호출할 수 있게 노출할 수 있다. AI 에이전트 통합이 점점 보편화되는 시점에 미리 준비해두면 경쟁 우위를 가질 수 있다.
Jetpack Compose-First 이니셔티브
Android 17부터 android.widget.*, Fragments, RecyclerView, ViewPager가 공식적으로 유지보수 모드로 전환됐다. 버그 수정만 이루어지고 신규 기능은 Compose 전용으로만 추가된다. 당장 기존 코드가 동작 안 하는 건 아니지만, 중장기적으로 Compose 마이그레이션 계획을 수립하지 않으면 점점 뒤처지는 구조다.
Android Contact Picker
READ_CONTACTS 권한 없이 사용자가 직접 선택한 연락처 데이터만 접근할 수 있다. 연락처 권한 요청으로 인한 사용자 이탈을 줄일 수 있다는 점에서 실무적 가치가 크다.
Post-Quantum 암호화 (ML-DSA)
양자 컴퓨팅 시대를 대비한 ML-DSA-65 알고리즘을 지원한다. APK 서명 v3.2에도 적용 가능하다. 서명 크기가 약 3,293바이트(ECDSA의 64바이트 대비 대폭 증가)인 점은 감안해야 한다.
val generator = KeyPairGenerator.getInstance("ML-DSA-65", "AndroidKeyStore")
generator.initialize(
KeyGenParameterSpec.Builder(
"my-key-alias",
KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).build()
)
val keyPair = generator.generateKeyPair()
ProfilingManager 자동 트리거
프로덕션 환경에서 Cold Start, OOM, Anomaly 등 이벤트 발생 시 자동으로 프로파일 데이터를 수집한다. 개발 환경에서 재현하기 어려운 실제 사용자 환경의 성능 이슈를 잡는 데 직접 활용할 수 있다.

핵심 요약
- targetSdk 37 = Android 17 = API Level 37 (코드명 Cinnamon Bun). 2026년 6월 16일 출시, 2027년 8월 Google Play 의무화 예상
- 가장 위험한 변경사항: static final 리플렉션 금지, 배경 오디오 WIU 요구, 로컬 네트워크 권한. 이 세 가지를 먼저 점검
- 2단계 접근(Phase 1 배포 후 Phase 2)이 가장 안전한 마이그레이션 경로
안드로이드 앱개발자라면 2027년 8월 데드라인까지는 아직 시간이 있지만, 앱이 크면 클수록 검증과 배포에 걸리는 시간도 길어진다. 지금 Phase 1(환경 업그레이드 + all-apps 변경사항 테스트)부터 시작하는 것이 현실적이다. 공식 Android 17 마이그레이션 가이드와 targetSdk 37 동작 변경사항 문서를 함께 참고하면 이 가이드에서 다루지 못한 세부 사항을 보완할 수 있다.
'코딩 > Android' 카테고리의 다른 글
| 안드로이드 면접 질문 50선 2부: Kotlin·Coroutine·Compose 심화 (0) | 2026.07.01 |
|---|---|
| 안드로이드 면접 질문 50선 (기본편) (0) | 2026.07.01 |
| Android Baseline Profile 적용 가이드 (0) | 2026.06.14 |
| XML에서 Compose로 완벽 마이그레이션 - 순서부터 전략까지 (0) | 2026.03.24 |
| Android TargetSdk 36 마이그레이션 완벽 가이드 (0) | 2026.03.21 |