Android O(API 26)부터 Background Service 실행이 제한되어 Foreground로 실행해야 합니다.
Activity가 실행 중이라면 문제 없겠지만, BroadcastReceiver 처럼 Foreground가 아닌 상태에서 Background Service를 실행시킬 수는 없게 되었습니다.
이럴 때는 서비스를 Foreground로 실행시켜야 합니다. Foreground로 실행시키게 되면 서비스가 실행 중이라는 내용의 Notificaiton이 등록되어 사용자가 인지할 수 있습니다.
이 글에서는 Foreground로 서비스를 실행시키는 방법에 대해서 알아보겠습니다.
이 글에서 소개된 Sample App은 GitHub - StartForegroundService에서 확인할 수 있습니다. 이 글의 예제는 Kotlin으로 작성되었습니다.
Service 정의
다음과 같이 MyService
라는 서비스 클래스를 구현하고, AndroidManifest에 정의하였다고 가정합니다.
class MyService : Service() {
override fun onCreate() {
super.onCreate()
Log.d("Test", "MyService is started")
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
<application>
...
<service android:name=".MyService" />
</application>
Foreground Service 실행
Android O(API 26) 미만의 버전에서는 startService()
로 서비스를 실행할 수 있었지만, Android O 이상의 디바이스에서는 서비스가 Background로 실행될 때는 실행이 되지 않습니다. 이 때는 startForegroundService()
로 서비스를 Foreground로 실행시켜야 합니다.
따라서 다음과 같이 서비스를 실행시킬 수 있습니다.
val intent = Intent(context, MyService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
FOREGROUND_SERVICE 퍼미션
Foreground로 서비스를 실행하려면 FOREGROUND_SERVICE
퍼미션을 AndroidManifest에 등록해야 합니다.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
startForegroundService()으로 Notification 등록
startForegroundService()
으로 서비스가 실행되면, 실행된 서비스는 5초 내에 startForeground()
를 호출하여 서비스가 실행 중이라는 Notificaiton을 등록해야 합니다. 만약 호출하지 않으면, 시스템은 서비스를 강제로 종료시킵니다.
Service.startForeground()
는 다음과 같이 Notificaiton ID
와 Notificaiton
객체를 인자로 받습니다.
주의할 점은 ID로 0을 전달하면 동작이 안된다는 것입니다. 0을 제외한 다른 숫자를 ID로 전달해야 합니다.
public final void startForeground(int id, Notification notification)
다음과 같이 Notificaiton Channel과 Notification 객체를 만들고, startForeground()
을 호출할 수 있습니다.
class MyService : Service() {
companion object {
const val NOTIFICATION_ID = 10
const val CHANNEL_ID = "primary_notification_channel"
}
override fun onCreate() {
super.onCreate()
Log.d("Test", "MyService is started")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel()
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("MyService is running")
.setContentText("MyService is running")
.build()
Log.d("Test", "start foreground")
startForeground(NOTIFICATION_ID, notification)
}
}
private fun createNotificationChannel() {
val notificationChannel = NotificationChannel(
CHANNEL_ID,
"MyApp notification",
NotificationManager.IMPORTANCE_HIGH
)
notificationChannel.enableLights(true)
notificationChannel.lightColor = Color.RED
notificationChannel.enableVibration(true)
notificationChannel.description = "AppApp Tests"
val notificationManager = applicationContext.getSystemService(
Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(
notificationChannel)
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
}
Android O(API 26) 이상의 버전에서 Notification을 등록하기 전에 Notificaiton Channel을 등록해야 합니다.
테스트
서비스를 실행해보면, 서비스가 실행되면서 다음과 같이 Notifciaton이 보이는 것을 확인 할 수 있습니다.
Troubleshooting
FOREGROUND_SERVICE
권한을 앱에 추가하지 않으면 다음과 같은 에러로 앱이 종료됩니다.
12-24 12:58:32.783 3688 3688 E AndroidRuntime: FATAL EXCEPTION: main
12-24 12:58:32.783 3688 3688 E AndroidRuntime: Process: com.example.app, PID: 3688
12-24 12:58:32.783 3688 3688 E AndroidRuntime: java.lang.RuntimeException: Unable to create service com.example.example.MyService: java.lang.SecurityException: Permission Denial: startForeground from pid=3688, uid=10153 requires android.permission.FOREGROUND_SERVICE
startForegroundService()
호출 후, 실행된 서비스에서 5초 내에 startForeground()
를 호출하지 않으면 시스템에 의해 서비스가 종료됩니다.
12-24 11:53:19.793 504 530 W ActivityManager: Bringing down service while still waiting for start foreground: ServiceRecord{21733d5 u0 com.example.app/.MyService}
12-24 11:53:19.912 504 3971 I ActivityManager: Crashing app skipping ANR: ProcessRecord{1bdf9c0 3660:com.example.app/u0a153} Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{21733d5 u0 com.example.app/.MyService}
참고
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력