[kotlin] workmanager와 백그라운드 작업

Boknami·2023년 8월 19일
0

코틀린

목록 보기
8/19

😕When?

  1. 주기적으로 무언가를 계속 실행해야한다.
  2. 앱을 키고 있지 않아도 작동한다
  3. 앱이 종료되더라도 작동한다.
  4. 폰의 화면이 off되어 있어도 작동한다.

위와 같은 상황에서 작업을 해야만 할 때 workmanager를 사용하는 것이 가장 좋은 것 같다.

백그라운드에서도 돌아가게 하려는 것에 가장 큰 문제는 안드로이드의 버전이 상승하면서 컨트롤 할 수 있는 부분이 점점 제약이 생기거나 변했다. 백그라운드에서 계속 무언가 서비스를 한다는 것은 당연하게도 지속적으로 클라이언트의 배터리 사용량을 증가시키는 또 클라이언트의 자원을 사용해야하는 경우이다. 이에 대해 안드로이드에서는 workmanager를 통해서 이러한 것들을 관리하기를 원하는 것 같았다.

✅ 알아둬야할 점

workmanager를 사용하면서 정말 고통을 받았다.
구현을 하면서 workmanager에 대해 알아야 할 점 혹은 오류가 나는 것에 대한 해결책을 적는다.

🔴 큐 형태의 작업과 코드의 위치

큐는 선입선출이다. 먼저 들어간 녀석이 먼저 나오게 되어있다.
이에 따라 만약 이 전에 우리가 큐에다 집어 넣은 녀석이 있다면 지금 막 집어 넣어도 당장 그게 실행되지 않는다. 이 점이 상당히 중요하다.

우리가 워크매니저가 잘 되는 지 테스트할 때 앱의 첫 진입점에 코드를 작성하고,
실수로 20분 주기로 맞췄다고 가정하자.

00:00 첫 실행
00:16 우린 주기가 15분인 줄 알고 에러가 났다 싶어 코드를 확인하니 주기가 20분으로 되있음을 확인하고 15분으로 다시 수정하고 다시 앱을 실행시킨다.
00:20 워크매니저가 동작을 했다. 우린 주기를 00:16에 수정하고 그 15분 후인 31분에 다시 워크매니저가 동작을 해야할텐데? 뭔가 이상함을 느낀다.

이러한 일이 계속 발생했기에 내가 워크매니저가 계속 오작동을 일으킨다고 생각했던 것 같기도하다..아무튼 이러한 일을 방지하기 위해선

val workManager = WorkManager.getInstance(this) // WorkManager 인스턴스를 가져옵니다.
        val uniqueWorkName = "my_unique_recurring_work_name" // 작업의 고유 이름을 정의합니다.

        // 이미 큐에 동일한 이름의 작업이 있는지 확인합니다.
        val workQuery = workManager.getWorkInfosForUniqueWork(uniqueWorkName)//특정 이름의 작업들가져오기
        val workInfoList = workQuery.get()//리스트 형태로 가져오기

        // 큐가 비어있다 => 워크 매니저 실행
        if (workInfoList.isEmpty()) {

처럼 워크매니저의 큐가 지금 비어있나..안 비어있나.. 확인을 하고 그에 따라서 큐를 비우고 실행하거나 하는 방식을 도입했다.
이것보다 훨씬 좋은 방식이 있을 지 모르지만 나는 이렇게 구현을 했다.

🔴 반드시 주기를 15분 이상으로 사용할 것

workmanager는 주기적으로 백그라운드에서 작업을 수행함으로 너무 짧은 주기로 실행하면 안된다. 3일동안 이걸 몰라서 테스트가 왜 정상적으로 안되나 삽질을 했다. 무조건 주기는 15분 이상을 지킬 것. 아니면 오류가 난다

💡 오류 해결법

사실 이 방법들은 내가 막 넣어보다가 해결이 되었기에 전혀 필요없는 것 일 수도 있다.

설정한 주기대로 움직이지 않고 멋대로 움직임

아마 이 부분을 한 10일은 고쳤다.
아무튼 추천하는 방법들은
1.앱을 꾹 눌러 설정 - 저장공간 - 데이터 삭제 | 혹은 앱 삭제 후 재설치
=> 앱의 워크매니저의 주기가 꼬였을 때!

2.앱을 꾹 눌러 설정 - 백그라운드 작업에 대한 제약 없음으로 바꾸기(코드로도 화이트리스트에 등록하는 방법이 있는 걸로 안다.)

3.권한들을 다 넣어보자.

<uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

분명 쓸데없는 권한들이 많을 것이다.
일단 나의 앱에서 사용하기 위한 권한들이 대부분인데 나는 이 상태에서 정상적으로 워크매니저가 동작했다.

알림 + workmanager를 사용한다면?

알림이 정상적으로 오지 않는 순간이 발생한다면
테스트기기가 안드로이드13이상이면 권한을 쥐어줘야한다.

앱 설치 시 12이하는 기본적으로 알림을 허용하면서 앱이 설치된다. 그러나 13부터는 그게 아니라 알림에 대한 코드를 작성하거나 사용자에게 직접 설정창에 들어가 알림을 허용하라고 지시해야한다.(이 자체가 앱이 유도리가 없겠지만..)

🤔How?

1.implementation

implementation "androidx.work:work-runtime-ktx:2.7.1" // kotlin

현재 23.08.19 기준 최신 버전은 2.8.1이다.
해당 부분으로 코드를 작성했어도 잘 돌아갔기에 이 버전을 사용했다.

2. workmanager작업 생성

본인 같은 경우 Application()을 상속 받아서 그 곳에 워크매니저가 작동하도록 했다.
첫 화면에다 사용해도 정상 작동은 되었지만 앱이 액티비티를 방문할 때마다 작동이 되었기에 application을 상속 받는 위치에 코드를 작성했다.

1.workmanager를 사용할 변수를 만들고 WorkManager 인스턴스를 가져온다.

val workManager = WorkManager.getInstance(this)

2.workmanager에 대한 제약을 설정한다.

 val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build()

서버에 지속적으로 데이터를 보내야 하기에 네트워크 제약을 걸었다.

3.처리할 클래스와 작업할 주기를 설정한다. 이 때 위에 제시한 제약 조건을 함께 걸 수 있다.

val workRequest = PeriodicWorkRequest.Builder(
                LocationNotificationWorker::class.java, 15, TimeUnit.MINUTES
            )
                .setConstraints(constraints)
                .build()

4.작업을 큐에 넣어 실행할 수 있도록 해준다.

workManager.enqueueUniquePeriodicWork(workRequest)

3.작업될 클래스를 정의한다.

class LocationNotificationWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
    override fun doWork(): Result {
        Log.d("테스트", "==================================================")
        Log.d(TAG, "doWork() started")
        
        return try {
            val location = fetchCurrentLocation()
            
            if (location != null) {
                Result.success()
            }
            else {
                Result.failure()
            }
        } catch (e: Exception) {
            
        }
    }

class 클래스명(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): Result{

}

}

상속 받아야할 부분들을 상속받고 이 부분에서 내가 무엇을 할 지 정의하면 된다..

결과


정상적으로 15분마다 작동을 하는 중이다!

0개의 댓글