[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
---
|
||||
id: android-14-migration-notes
|
||||
title: Android 14+ 마이그레이션 — 주요 변화
|
||||
category: Coding
|
||||
status: draft
|
||||
source_trust_level: B
|
||||
verification_status: conceptual
|
||||
created_at: 2026-05-09
|
||||
updated_at: 2026-05-09
|
||||
tags: [android, migration, vibe-coding]
|
||||
tech_stack: { language: "Kotlin", applicable_to: ["Android"] }
|
||||
applied_in: []
|
||||
aliases: [Android 14, target SDK 34, photo picker, foreground service type, predictive back]
|
||||
---
|
||||
|
||||
# Android 14+ Migration
|
||||
|
||||
> targetSdk 34/35 의 주요 변화 체크리스트. **FGS type 의무화 / Photo picker 권장 / 6h 데이터 sync 한도 / Predictive back / Notification permission**.
|
||||
|
||||
## 📖 핵심 개념
|
||||
- targetSdk 변경 = 새 동작 적용.
|
||||
- Permission 변경: media, photo picker.
|
||||
- FGS type 의무.
|
||||
- 새 권장: predictive back, large screen.
|
||||
|
||||
## 💻 코드 패턴
|
||||
|
||||
### Photo Picker (권한 없이 사진 선택)
|
||||
```kotlin
|
||||
val launcher = registerForActivityResult(
|
||||
ActivityResultContracts.PickVisualMedia()
|
||||
) { uri -> /* uri 한 장 */ }
|
||||
|
||||
launcher.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))
|
||||
// 또는 ImageAndVideo / SingleMimeType("image/jpeg")
|
||||
```
|
||||
|
||||
다중:
|
||||
```kotlin
|
||||
val launcher = registerForActivityResult(
|
||||
ActivityResultContracts.PickMultipleVisualMedia(maxItems = 5)
|
||||
) { uris -> /* List<Uri> */ }
|
||||
```
|
||||
|
||||
→ READ_MEDIA_IMAGES 권한 불필요. 사용자 친화 + privacy.
|
||||
|
||||
### Selected Photos Access (READ_MEDIA_VISUAL_USER_SELECTED)
|
||||
사용자가 일부 사진만 access 허용 (모두 X).
|
||||
```kotlin
|
||||
val perms = arrayOf(
|
||||
Manifest.permission.READ_MEDIA_IMAGES,
|
||||
Manifest.permission.READ_MEDIA_VIDEO,
|
||||
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, // 14+
|
||||
)
|
||||
```
|
||||
|
||||
### Foreground Service type (Android 14+ 의무)
|
||||
```xml
|
||||
<service
|
||||
android:name=".PlayerService"
|
||||
android:foregroundServiceType="mediaPlayback" />
|
||||
```
|
||||
|
||||
[[Android_Foreground_Service_Patterns]] 참조.
|
||||
|
||||
### dataSync 6시간 한도 (14+)
|
||||
24시간 안 dataSync FGS 6시간 한도 — WorkManager 로 분할.
|
||||
|
||||
### Predictive back gesture (14+)
|
||||
```xml
|
||||
<application android:enableOnBackInvokedCallback="true">
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// Compose
|
||||
BackHandler(enabled = canGoBack) { goBack() }
|
||||
|
||||
// 또는 OnBackPressedDispatcher
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
onBackPressedDispatcher.addCallback(this) {
|
||||
// back 처리
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ 사용자가 swipe back 시 미리보기 애니메이션.
|
||||
|
||||
### Notification permission (13+)
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
```
|
||||
|
||||
```kotlin
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
requestPermission(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
```
|
||||
|
||||
### Background activity launch (14+)
|
||||
```kotlin
|
||||
// 14+ 더 엄격: background 에서 activity launch 어려워짐
|
||||
// 권장: 사용자 작업이 보이는 곳 (notification action, FGS) 으로 trigger
|
||||
```
|
||||
|
||||
### Pending intent — exported 명시 (12+)
|
||||
```kotlin
|
||||
PendingIntent.getActivity(ctx, 0, intent,
|
||||
PendingIntent.FLAG_IMMUTABLE) // 또는 FLAG_MUTABLE — RemoteInput 시
|
||||
```
|
||||
|
||||
### Exact alarm permission (12+)
|
||||
```xml
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||
```
|
||||
|
||||
```kotlin
|
||||
val am = getSystemService<AlarmManager>()
|
||||
if (am?.canScheduleExactAlarms() == true) {
|
||||
am.setExactAndAllowWhileIdle(...)
|
||||
} else {
|
||||
// 권한 요청 / fallback
|
||||
}
|
||||
```
|
||||
|
||||
### Implicit intent — package 명시 (15+ 권장)
|
||||
```kotlin
|
||||
// ❌ implicit
|
||||
Intent(Intent.ACTION_VIEW, uri)
|
||||
|
||||
// ✅ package 명시
|
||||
Intent(Intent.ACTION_VIEW, uri).setPackage("com.android.chrome")
|
||||
// 또는 setComponent
|
||||
```
|
||||
|
||||
### Edge-to-edge 의무 (15+)
|
||||
```kotlin
|
||||
// Activity
|
||||
enableEdgeToEdge()
|
||||
|
||||
// Compose
|
||||
Scaffold { padding ->
|
||||
// padding 사용해서 system bar 안 가리게
|
||||
Column(Modifier.padding(padding)) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Large screen / fold
|
||||
```kotlin
|
||||
val widthSizeClass = calculateWindowSizeClass(activity).widthSizeClass
|
||||
when (widthSizeClass) {
|
||||
WindowWidthSizeClass.Compact -> NavigationBar(...)
|
||||
WindowWidthSizeClass.Medium -> NavigationRail(...)
|
||||
WindowWidthSizeClass.Expanded -> PermanentNavigationDrawer(...)
|
||||
}
|
||||
```
|
||||
|
||||
### Resume / pause 정확히
|
||||
```kotlin
|
||||
DisposableEffect(Unit) {
|
||||
val obs = LifecycleEventObserver { _, ev ->
|
||||
when (ev) {
|
||||
Lifecycle.Event.ON_RESUME -> resume()
|
||||
Lifecycle.Event.ON_PAUSE -> pause()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
lifecycle.addObserver(obs)
|
||||
onDispose { lifecycle.removeObserver(obs) }
|
||||
}
|
||||
```
|
||||
|
||||
## 🤔 의사결정 기준
|
||||
| 변경 | 우선 |
|
||||
|---|---|
|
||||
| Notification 권한 | 즉시 (13+) |
|
||||
| FGS type | 즉시 (14+) |
|
||||
| Photo picker 전환 | 적극 권장 |
|
||||
| Predictive back | 14+ |
|
||||
| Edge-to-edge | 15+ 의무 |
|
||||
| Implicit intent | 15+ 권장 |
|
||||
|
||||
## ❌ 안티패턴
|
||||
- **targetSdk 안 올림**: Play Store 거부.
|
||||
- **Permission 옛 (READ_EXTERNAL_STORAGE)**: 13+ 동작 X. media-specific.
|
||||
- **FGS type 누락**: SecurityException.
|
||||
- **Notification 권한 안 받고 notify**: 무시.
|
||||
- **Edge-to-edge X + 15+**: 검은 bar.
|
||||
- **Predictive back 안 옵트인**: 새 UX 못 씀.
|
||||
- **Background activity launch 시도**: 차단.
|
||||
|
||||
## 🤖 LLM 활용 힌트
|
||||
- 매년 targetSdk + 1.
|
||||
- Photo picker / FGS type / notification permission 3종이 큰 변화.
|
||||
- WindowSizeClass 로 fold / tablet 자동.
|
||||
|
||||
## 🔗 관련 문서
|
||||
- [[Android_Foreground_Service_Patterns]]
|
||||
- [[Android_Notification_Patterns]]
|
||||
- [[Android_Lifecycle_Aware_Components]]
|
||||
Reference in New Issue
Block a user