--- id: android-foreground-service-patterns title: Android Foreground Service — 14+ 제약 + 타입 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [android, foreground-service, vibe-coding] tech_stack: { language: "Kotlin / Android", applicable_to: ["Android"] } applied_in: [] aliases: [foreground service, FGS, service type, partial wake lock, notification channel] --- # Foreground Service > 백그라운드에서 사용자가 인지하는 작업 (재생, 운동, 위치). **Android 14+ = service type 명시 필수**. Notification 항상 + 적절한 lifetime 만. ## 📖 핵심 개념 - FGS: 사용자가 알고 있는 background 작업. notification 표시. - Type: dataSync / mediaPlayback / location / phoneCall / health 등 (Android 14+). - WorkManager: 단순 정기 작업 — FGS 보다 우선 고려. - 권한 (Android 13+): POST_NOTIFICATIONS. ## 💻 코드 패턴 ### Manifest ```xml ``` ### Service 구현 ```kotlin class PlayerService : Service() { override fun onCreate() { super.onCreate() createChannel() startForeground(NOTIF_ID, buildNotification("Loading..."), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { "PLAY" -> player.play() "PAUSE" -> player.pause() "STOP" -> stopSelf() } return START_STICKY } override fun onBind(intent: Intent?): IBinder? = null override fun onDestroy() { player.release() super.onDestroy() } private fun createChannel() { val ch = NotificationChannel("player", "Player", NotificationManager.IMPORTANCE_LOW) getSystemService()?.createNotificationChannel(ch) } private fun buildNotification(text: String): Notification { val pi = PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE) return NotificationCompat.Builder(this, "player") .setContentTitle("Now playing") .setContentText(text) .setSmallIcon(R.drawable.ic_play) .setContentIntent(pi) .addAction(R.drawable.ic_pause, "Pause", pendingActionIntent("PAUSE")) .setOngoing(true) .build() } private fun pendingActionIntent(action: String): PendingIntent { val i = Intent(this, PlayerService::class.java).setAction(action) return PendingIntent.getService(this, action.hashCode(), i, PendingIntent.FLAG_IMMUTABLE) } } ``` ### 시작 ```kotlin val i = Intent(context, PlayerService::class.java).setAction("PLAY") ContextCompat.startForegroundService(context, i) ``` ⚠️ Android 12+: `startForegroundService` 후 5초 안에 `startForeground()` 호출 안 하면 ANR. ### 권한 요청 (notification, 13+) ```kotlin if (Build.VERSION.SDK_INT >= 33) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1) } } ``` ### Type 별 권한 (14+) ``` mediaPlayback → FOREGROUND_SERVICE_MEDIA_PLAYBACK location → FOREGROUND_SERVICE_LOCATION + ACCESS_FINE_LOCATION camera → FOREGROUND_SERVICE_CAMERA + CAMERA microphone → FOREGROUND_SERVICE_MICROPHONE + RECORD_AUDIO dataSync → FOREGROUND_SERVICE_DATA_SYNC (제약 강 — 6h/day 한도) mediaProjection → FOREGROUND_SERVICE_MEDIA_PROJECTION phoneCall → FOREGROUND_SERVICE_PHONE_CALL health → FOREGROUND_SERVICE_HEALTH (운동 등) ``` ### dataSync 6시간 제한 ``` 14+ : dataSync FGS 는 24시간 동안 6시간 한도. 초과 시 자동 stop. 대안: WorkManager + JobScheduler 짧은 작업으로 분할. ``` ### 동기 stop ```kotlin stopForeground(STOP_FOREGROUND_REMOVE) stopSelf() ``` ### Compose 시작 / 중지 controls ```kotlin @Composable fun PlayerControls() { val ctx = LocalContext.current Row { Button(onClick = { ContextCompat.startForegroundService(ctx, Intent(ctx, PlayerService::class.java).setAction("PLAY")) }) { Text("Play") } Button(onClick = { ctx.startService(Intent(ctx, PlayerService::class.java).setAction("STOP")) }) { Text("Stop") } } } ``` ## 🤔 의사결정 기준 | 작업 | 도구 | |---|---| | 미디어 재생 | FGS mediaPlayback + MediaSession | | 운동 추적 | FGS health | | 짧은 sync (<10분) | WorkManager | | 정기 sync (15분+) | PeriodicWorkRequest | | 즉시 + 짧음 | OneTimeWorkRequest | | 위치 추적 | FGS location | | 화면 녹화 | FGS mediaProjection | ## ❌ 안티패턴 - **FGS 로 데이터 sync 6시간 초과**: stop. WorkManager 으로. - **5초 안 startForeground 누락**: ANR + crash. - **Type 명시 누락 (14+)**: SecurityException. - **Notification importance 높음**: 알람음 매번. LOW 디폴트. - **사용자가 모르는 작업**: FGS 가 아닌 Worker. - **Permission 없이 mic / camera FGS**: SecurityException. - **stopSelf 안 함**: 영원 — 배터리 / 메모리. ## 🤖 LLM 활용 힌트 - Type + 권한 (14+) 필수. - 5초 안 startForeground. - WorkManager 가 가능하면 그게 우선. ## 🔗 관련 문서 - [[Android_WorkManager_Patterns]] - [[Android_Lifecycle_Aware_Components]] - [[Native_Battery_Network_Profiling]]