일반적으로 부팅되면 대부분의 앱은 실행되지 않은 상태입니다.
안드로이드는 부팅이 완료되면 ACTION_BOOT_COMPLETED
인텐트를 전달하여 앱이 실행되도록 합니다.
앱은 이 인텐트를 받고, 어떤 작업을 처리할 수 있습니다.
이 글에서는 ACTION_BOOT_COMPLETED
인텐트를 수신하는 방법을 소개합니다.
이 글에서 소개된 Sample App은 GitHub - StartForegroundService에서 확인할 수 있습니다. 이 글의 예제는 Kotlin으로 작성되었습니다.
권한 설정
App이 ACTION_BOOT_COMPLETED
인텐트를 수신하려면 다음과 같이 RECEIVE_BOOT_COMPLETED
권한을 AndroidManifest에 선언해야 합니다.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application ...>
...
</application>
BroadcastReceiver 구현
이벤트를 반으려면 Receiver 클래스를 구현하고 그 클래스를 AndroidManifest에 정의해야 합니다.
부팅이 되면 앱이 실행되지 않았을 때, Context.registerReceiver()
API로 리시버를 등록할 수 없기 때문입니다.
다음과 같이 Receiver 클래스를 생성합니다.
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("Test", "Receive : ${intent.action}")
// Do something
}
}
그리고 다음과 같이 AndroidManifest에 리시버를 정의하면 됩니다.
인텐트를 받기 위해서는 리시버의 인텐트 필터에 BOOT_COMPLETED
Action을 추가해야 합니다.
<application ... >
...
<receiver android:name=".MyReceiver">
<intent-filter >
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
이제 앱을 설치하고 디바이스를 재시작하면 BOOT_COMPLETED
가 앱으로 전달되는 것을 확인할 수 있습니다.
12-24 11:35:00.574 15143 15143 D Test : Receive : android.intent.action.BOOT_COMPLETED
서비스 실행
BOOT_COMPLETED
인텐트를 받고 내 앱의 서비스를 실행시켜 어떤 작업을 처리하도록 만들 수 있습니다.
서비스 구현
먼저 다음과 같이 Service를 구현합니다.
class MyService : Service() {
companion object {
const val NOTIFICATION_ID = 10
const val CHANNEL_ID = "my_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()
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)부터 Background service 실행이 제한되어 Foreground로 실행해야 합니다.
startForegroundService()
으로 서비스를 Foreground로 실행시킬 수 있습니다. 하지만, 서비스는 5초 이내에 startForeground()
를 호출하여 Notification으로 서비스가 Foreground에 실행 중이라는 것을 사용자에게 알려야 합니다. 그렇지 않으면 시스템은 서비스를 종료시킵니다.
그렇기 때문에 위의 코드에서 Notificaiton Channel과 Notificaiton을 만들었습니다.
주의할 점은 위의 코드에서 다음과 같이 startForeground()
를 호출하면서 Channel ID를 인자로 넘기는데, Channel ID는 0이 아닌 숫자가 전달되어야 합니다. 0을 전달하면 동작이 되지 않습니다.
startForeground(NOTIFICATION_ID, notification)
Manifest에 퍼미션 및 서비스 등록
다음과 같이 AndroidManifest에 서비스를 정의하고 FOREGROUND_SERVICE
퍼미션을 추가해야 합니다.
FOREGROUND_SERVICE
는 Foreground로 서비스를 실행시키기 위해 필요한 권한입니다.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application>
...
<service android:name=".MyService" />
</application>
리시버에서 서비스 실행
다음으로 Receiver에서 인텐트를 받았을 때 서비스를 실행하도록 구현합니다.
class MyReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d("Test", "Receive : ${intent.action}")
val intent = Intent(context, MyService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
}
위에서 설명한 것처럼 Android O(API 26)부터 Background Service 실행을 제안하고 있습니다.
API 26 이상에서는 startForegroundService()
으로 서비스를 Foreground로 실행시켜야 합니다.
테스트
위와 같이 구현했으면 앱을 설치하고 디바이스를 재시작해보세요.
부팅 후 조금 기다리면 Receiver로 BOOT_COMPLETED
가 전달되고, Foreground로 서비스가 실행되는 것을 확인할 수 있습니다.
12-24 13:04:58.044 3793 3793 D Test : Receive : android.intent.action.BOOT_COMPLETED
12-24 13:04:58.054 3793 3793 D Test : MyService is started
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 명령어로 로그 출력