본문 바로가기
Android/Open Source

[Open Source] WorkManager 및 Kotlin

by 준그래머 2023. 7. 19.
반응형

본 게시물은 Medium에 2019년 6월 13일에 게시된 내용을 번역한 글입니다. 오역이 있을 수 있으니 원문을 읽고 싶으신 분들은 아래 링크를 이용해 주세요.

WorkManager meets Kotlin

 

WorkManager meets Kotlin

A look at WorkManager’s KTX and CoroutineWorker.

medium.com

 

시작

WorkManager 시리즈의 세 번째 게시물에 온 것을 환영합니다. WorkManager는 나중에 처리될 수 있으면서 무조건 실행되어야 하는 비동기 작업을 쉽게 처리할 수 있는 Android Jetpack 라이브러리입니다. 현재 이것은 안드로이드에서 백그라운드 작업 처리를 위한 가장 좋은 방법입니다.

만약 여러분들이 지금까지 잘 따라 왔다면 다음 내용을 기억하고 있을 겁니다.

  • WorkManager가 무엇인지, 언제 사용되어야 하는지
  • WorkManager API를 사용해 작업을 스케줄 하는 방법

이 게시물에서는 아래에 대해 알아볼 겁니다.

  • Kotlin에서 WorkManager를 사용하는 법
  • CoroutineWorker 클래스
  • TestListenableWorkerBuilder 이용해 어떻게 CoroutineWorker 클래스들을 테스트하는지

 

Kotlin에서 WorkManager

이 게시물의 코드들은 KTX library를 이용한 것 들입니다. WorkManager의 KTX 버전은 보다 간결하고 Kotlin에 적합한 확장된 기능들을 제공하고 있습니다. 여러분은 build.gradle 파일에 androidx.work:work-runtime-ktx 종속성을 추가함으로써 WorkManager의 KTX 버전을 사용할 수 있습니다.(WorkManager의 release notes에 설명된 내용) 이 아티팩트는 CoroutineWorker와 다른 WorkManager에 도움을 줄 수 있는 확장 함수를 포함하고 있습니다.

 

보다 간결하고 Kotlin에 친화적

WorkManager의 KTX는 Worker 클래스를 전달하거나 전달하기 위해 Data 객체 생성해야 할 때, 편한 구문을 제공합니다. Java로 작성된 구문을 확인해 보죠.

Data myData = new Data.Builder()
                      .putInt(KEY_ONE_INT, aInt)
                      .putIntArray(KEY_ONE_INT_ARRAY, aIntArray)
                      .putString(KEY_ONE_STRING, aString)
                      .build();

Kotlin의 경우 유용한 함수인 workDataOf를 사용해 보다 쉽게 이용할 수 있습니

inline fun workDataOf(vararg pairs: Pair<String, Any?>): Data

이걸 통해 이전 Java 구문을 다음과 같이 사용할 수 있습니다.

val data = workDataOf(
        KEY_MY_INT to myIntVar,
        KEY_MY_INT_ARRAY to myIntArray,
        KEY_MY_STRING to myString
    )

 

CoroutineWorker

자바에서 사용할 수 있는(Worker, ListenableWorkerRxWorker) Worker 클래스 외에도 Kotlin에서만 사용할 수 있는 Kotlin의 Coroutines을 이용해서 처리할 수 있습니다.

Worker와 CoroutineWorker의 가장 주요한 차이는 CoroutineWorker의 doWork() 함수는 중단할 수 있고 비동기 처리된다는 것입니다. 반면 Worker의 doWork() 함수는 오직 동기 처리로 수행됩니다. 다른 CoroutineWorker의 특징으로 자동으로 중단과 취소를 자동으로 처리하는 반면 Worker 클래스는 onStopped() 함수를 직접 구현해야 한다는 것이 있습니다.

다양한 옵션에 대한 전체 Context는 WorkerManager의 쓰레딩 가이드를 참조하세요.

이 게시물에선 CoroutineWorker에 집중해서 설명할 것입니다. 작지만 중요한 차이에 대해 다루고 WorkManager v2.1에 도입된 새로운 테스트 기능을 이용해 CoroutineWorker 클래스들을 어떻게 테스트하는지 알아봅시다.

전에 언급한 것처럼, CoroutineWorker의 doWork() 함수는 중단될 수 있는 기능입니다. 이것은 기본적으로 Dispatchers.Default에서 실행됩니다.

class MyWork(context: Context, params: WorkerParameters) :
        CoroutineWorker(context, params) {override suspend fun doWork(): Result {return try {
            // Do something
            Result.success()
        } catch (error: Throwable) {
            Result.failure()
        }
    }
}

Worker 또는 ListenableWorker 대신 CoroutineWorker를 사용하면서 기본적인 차이에 대해 이해하는 것이 중요합니다.

Worker와 다르게 이 코드는 WorkerManager의 설정이 명시된 Executor를 실행하지 않습니다.

방금 말했듯이, CoroutineWorker의 doWork() 함수는 Dispatchers.Default로 설정되며 withContext()를 사용해 커스터마이징 할 수 있습니다.

class MyWork(context: Context, params: WorkerParameters) :
        CoroutineWorker(context, params) {override suspend fun doWork(): Result = withContext(Dispatchers.IO) {return try {
            // Do something
            Result.success()
        } catch (error: Throwable) {
            Result.failure()
        }
    }
}

Dispatcher의 변화는 거의 필요 없는데, CoroutineWorker의 대다수의 예에서 Dispatcher.Default가 가장 좋은 선택입니다.

Kotlin에서 WorkManager를 사용하는 방법에 대해 더 알고 싶으면 codelab의 예제를 도전해 보세요.

 

Worker 클래스들 테스트하기

WorkManager는 몇 가지의 추가적인 아티펙트들을 이용해 여러분의 작업을 쉽게 테스트할 수 있도록 합니다. WorkManager의 의 테스트 방법 문서WorkerManager 2.1.0를 이용한 테스트 읽으면 보다 많은 정보를 얻을 수 있습니다. 테스트 도우미의 기존 구현은 WorkManger를 커스터마이징이 가능하도록 했습니다. 동기 처리에 정기적이고 지연이 필요한 작업을 시뮬레이션할 수 있도록 하는 WorkManagerTestInitHelper의 getTestDriver() 함수를 사용할 수 있습니다.

여기서 중요한 점은 여러분이 Worker 클래스들이 작업들을 테스트할 수 있도록 동작이나 WorkManager수정하는 것입니다.

WorkManager v2.1는 Worker 클래스를 테스트하는 새로운 방법이 도입되어 있는 TestListenableWorkerBuilder 기능이 추가되었습니다.

실제로 TestListenableWorkerBuilder를 이용해 여러분은 직접 worker 클래스 실행해 로직 테스트를 해볼 수 있기 때문에 이건 CoroutineWorker에서 매우 중요한 업데이트라고 할 수 있습니다.

@RunWith(JUnit4::class)
class MyWorkTest {
    private lateinit var context: Context
    @Before
    fun setup() {
        context = ApplicationProvider.getApplicationContext()
    }
    @Test
    fun testMyWork() {
        // Get the ListenableWorker
        val worker = 
            TestListenableWorkerBuilder<MyWork>(context).build()

        // Run the worker synchronously
        val result = worker.startWork().get()

        assertThat(result, `is`(Result.success()))
    }
}

여기서 중요한 건 CoroutineWorker 실행 결과들에 동기화되어 획득되고 있으며 여러분은 Worker 클래스의 로직이 올바르게 동작하는지 확인할 수 있다는 것입니다.

 

TestListenableWorkerBuilder 사용해서 입력 데이터를 Worker로 넘겨줄 수 있고 runAttemptCount를 설정할 수 있습니다. 여러분의 작업 안에서 재시도 로직 테스트에 유용합니다.

예를 들면, 여러분이 만약 몇몇 데이터를 서버에 업로드하려고 한다면 연결 문제를 고려해 재시도 로직을 추가할 수 있습니다.

class MyWork(context: Context, params: WorkerParameters) :
    CoroutineWorker(context, params) {    override suspend fun doWork(): Result {
    val serverUrl = inputData.getString("SERVER_URL")        return try {
        // Do something with the URL
        Result.success()
    } catch (error: TitleRefreshError) {
        if (runAttemptCount <3) {
            Result.retry()
        } else {
            Result.failure()
        }
    }
}

그런 다음 TestListenableWorkerBuilder를 사용하여 테스트에서 이 재시도 로직을 테스트할 수 있습니다:

@Test
fun testMyWorkRetry() {
    val data = workDataOf("SERVER_URL" to "<http://fake.url>")    // Get the ListenableWorker with a RunAttemptCount of 2
    val worker = TestListenableWorkerBuilder(context)   
                     .setInputData(data)
                     .setRunAttemptCount(2)
                     .build()    // Start the work synchronously
    val result = worker.startWork().get()    assertThat(result, `is`(Result.retry()))
}@Test
fun testMyWorkFailure() {
    val data = workDataOf("SERVER_URL" to "<http://fake.url>")    // Get the ListenableWorker with a RunAttemptCount of 3
    val worker = TestListenableWorkerBuilder(context)
                     .setInputData(data)
                     .setRunAttemptCount(3)
                     .build()    // Start the work synchronously
    val result = worker.startWork().get()    assertThat(result, `is`(Result.failure()))
}

 

결론

WorkManager v2.1 릴리즈와 WorkManager-Testing의 새로운 기능을 통해 CoroutineWorker는 간편하고 좋은 기능성으로 빛이 난다고 할 수 있습니다. 이제 테스트는 정말 쉬워졌고 Kotlin에서의 전반적인 경험은 훌륭할 겁니다.

만약 여러분이 아직 CoroutineWorker와 workmanager-runtime-ktx이 포함된 다른 확장된 기능들을 시도해보지 않았다면 저는 진심으로 여러분들의 프로젝트에 적용해보기를 바랍니다. 제가 요즘에 Kotlin으로 작업할 때, WorkManager를 사용하는 방법을 매우 선호하고 있습니다.

저는 여러분들이 이 게시물에 유용한 것을 얻었길 바라며 여러분들이 어떻게 WorkManager를 사용하고 있는지, 또는 WorkManager의 기능에 대해 더 잘 설명하거나 문서화할 수 있는 방법이 있는지에 대해 듣고 싶습니다.

 

출처

반응형