일반적으로 부팅되면 대부분의 앱은 실행되지 않은 상태입니다.
안드로이드는 부팅이 완료되면 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}
참고
Recommended Posts:
- Android - 진동, Vibrator, VibrationEffect 예제
- Android - FusedLocationProviderClient으로 위치 정보 얻기
- Android - 시간, 날짜 변경 이벤트 받기
- Android - Foreground Service 실행
- Android - GPS, Network 위치 정보 얻기 (LocationManager)
- Android - Screen On/Off 이벤트 수신, 상태 확인
- Android - 파일 입출력 예제 (Read, Write, 내부, 외부 저장소)
- Android - currentTimeMillis(), elapsedRealtime(), uptimeMillis()
- Android - PowerManager WakeLock
- Android - 다른 앱의 Service에 바인딩
- Android - Handler vs Executor
- Android - Darkmode 활성화하는 방법
- Android - hasSystemFeature(), 지원되는 Feature 확인