5.8 KiB
5.8 KiB
id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
| id | title | category | status | source_trust_level | verification_status | created_at | updated_at | tags | tech_stack | applied_in | aliases | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| android-foreground-service-patterns | Android Foreground Service — 14+ 제약 + 타입 | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
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
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" /> <!-- 14+ -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<service
android:name=".PlayerService"
android:foregroundServiceType="mediaPlayback"
android:exported="false" />
Service 구현
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<NotificationManager>()?.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)
}
}
시작
val i = Intent(context, PlayerService::class.java).setAction("PLAY")
ContextCompat.startForegroundService(context, i)
⚠️ Android 12+: startForegroundService 후 5초 안에 startForeground() 호출 안 하면 ANR.
권한 요청 (notification, 13+)
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
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
Compose 시작 / 중지 controls
@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 가 가능하면 그게 우선.