Android - ACTION_BOOT_COMPLETED 이벤트 받기

JS · 24 Dec 2020

일반적으로 부팅되면 대부분의 앱은 실행되지 않은 상태입니다.

안드로이드는 부팅이 완료되면 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

foreground service

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}

참고

댓글을 보거나 쓰려면 이 버튼을 눌러주세요.
codechachaCopyright ©2019 codechacha