티스토리 뷰

안드로이드

WorkManger 사용해보기 2

알렌보이스 2022. 6. 7. 21:43
728x90

 

2022.06.05 - [안드로이드] - WorkManager 사용해보기 1

 

WorkerManager 사용해보기 1

이번에는 Jetpack Library 중 하나인 WorkManager를 사용해보려고 합니다. WorkMager도 백그라운드 작업을 해주는 라이브러리 중 하나입니다. 특징 관련해서는 코드를 보면서 확인하는게 이해하기 쉬울 것

alanboyce.tistory.com

 

지난 글에 이어서 WorkManager를 사용해봅시다

 

1. 고유한 작업 실행

1편에서 마지막에 작업 취소할 때 잠깐 나왔던 개념입니다

WorkManager
    .getInstance(this)
    .enqueueUniqueWork(
        "oneTimeUnique",
        ExistingWorkPolicy.KEEP,
        workRequest
    )

WorkManager
    .getInstance(this)
    .enqueueUniquePeriodicWork(
        "periodicUnique",
        ExistingPeriodicWorkPolicy.REPLACE,
        workRequest2
    )

우선 사용 방법은 기존에는 enqueue()를 이용해서 어떤 Request를 넣어도 작동을 하였지만

Unique는 Request 종류에따라 OneTimeWorkRequest일 경우 위에 방식을, PeriodicWorkRequest일 경우 아래 방식을 이용하면 됩니다.

받는 인자는 다음과 같습니다.

@NonNull String uniqueWorkName,
@NonNull ExistingWorkPolicy existingWorkPolicy,
@NonNull OneTimeWorkRequest work or @NonNull PeriodicWorkRequest periodicWork

세번째 인자는 위에 내용으로 알 수 있고, 첫 번째는 사용자가 관리하기 위한 아이디를 지정해 주면 됩니다.

두 번째 인자에 들어갈 수 있는 값은 다음과 같습니다

ExistingPeriodicWorkPolicy.REPLACE
ExistingPeriodicWorkPolicy.KEEP

ExistingWorkPolicy.REPLACE
ExistingWorkPolicy.KEEP
ExistingWorkPolicy.APPEND
ExistingWorkPolicy.APPEND_OR_REPLACE

이 값들은 고유한 작업을 큐에 넣을 때 충동 발생 시 처리방법을 의미합니다.

  - REPLACE : 기존 작업을 새 작업으로 대체, 기존의 작업을 취소합니다.
  - KEEP : 기존의 작업을 유지하고 새 작업을 무시합니다.
  - APPEND : 새 작업을 기존 작업의 끝에 추가합니다. 새 작업을 기존 작업에 체이닝 하여 기존 작업이 완료된 후에 새 작업을 실행합니다.
     다만, 기존 작업이 CANCELD 또는 FAILED가 되면 새 작업 역시 CANCELD, FAILED가 됩니다.
  - APPEND_OR_REPLACE : APPEND 방식과 동일하지만 기존 작업의 상태 값과 무관하게 새 작업을 진행합니다.

 

2. 상태 변화 감지

이전 글에서 Worker의 상태 값들에 대해 알아보았고, WorkManager의 실행 방법들도 다 알아보았으니
이제 Worker의 상태변화를 감지하여 원하는 작업을 할 수 있는 방법에 대하여 알아보겠습니다.

우선 테스트를 진행하기 위해 xml을 준비합니다.

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".workmanager.WorkManagerActivity">

        <Button
            android:id="@+id/button"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

        <RadioGroup
            android:id="@+id/radioGroup"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <RadioButton
                android:id="@+id/radioFailure"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:checked="true"
                android:text="failure" />

            <RadioButton
                android:id="@+id/radioSuccess"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="20dp"
                android:text="success" />

        </RadioGroup>

        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:visibility="visible" />

        <TextView
            android:id="@+id/textView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/radioGroup" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

테스트를 위한 것이기 때문에 UI 대략 이정도로 준비해 줍니다.

그다음 Worker를 수정해줍니다.

class TestWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {

    override fun doWork(): Result {

        // Worker 에게 전달한 데이터 받기
        val inputData = inputData.getString(WORKER_INPUT_DATA)

        // 추가 작업... 파일 업로드, 데이터 통신 등
        Thread.sleep(2000)

        return if (inputData == SUCCESS){
            val output = workDataOf(RESULT to "doWork end : Success")
            Result.success(output)
        } else {
            val output = workDataOf(RESULT to "doWork end : Failure")
            Result.failure(output)
        }
    }

    companion object {
        const val WORKER_INPUT_DATA = "worker_input_data"
        const val SUCCESS = "success"
        const val FAILURE = "failure"
        const val RESULT = "result"
    }

}

간단한 예제로 받은 InputData에서 받은 값에 따라 success 또는 failure를 리턴합니다.

실제로는 백그라운드 작업을 진행한다는 가정하에 Thread.sleep(2000)를 추가하였습니다.

private fun initViews() = with(binding) {
    radioGroup.setOnCheckedChangeListener { _, checkedId ->
        when (checkedId) {
            radioSuccess.id -> {
                data = TestWorker.SUCCESS
            }
            radioFailure.id -> {
                data = TestWorker.FAILURE
            }
        }
    }

    button.setOnClickListener {
        val request = createWorkerRequest()

        WorkManager
            .getInstance(this@WorkManagerActivity)
            .apply {
                enqueue(request)

                getWorkInfoByIdLiveData(request.id)
                    .observe(this@WorkManagerActivity) { workInfo ->
                        setWorkResult(workInfo)
                    }
            }
    }
}

private fun createWorkerRequest() =
    OneTimeWorkRequestBuilder<TestWorker>()
        .setInputData(
            workDataOf(
                TestWorker.WORKER_INPUT_DATA to data,
            )
        )
        .addTag("tag")
        .build()

private fun setWorkResult(workInfo: WorkInfo) {
    when (workInfo.state) {
        WorkInfo.State.SUCCEEDED -> {
            val result = workInfo.outputData.getString(TestWorker.RESULT)
            binding.textView.text = result
            binding.progressBar.isVisible = false
        }
        WorkInfo.State.FAILED -> {
            val result = workInfo.outputData.getString(TestWorker.RESULT)
            binding.textView.text = result
            binding.progressBar.isVisible = false
        }
        WorkInfo.State.RUNNING -> {
            binding.progressBar.isVisible = true
        }
        else -> {}
    }
}

간단한 예제이므로 다른 코드 관련한 부분의 설명은 생략하겠습니다.

새로 추가된 부분은 이 부분입니다.

getWorkInfoByIdLiveData(request.id)
    .observe(this@WorkManagerActivity) { workInfo ->
        setWorkResult(workInfo)
    }

상태 값이 변경된 것을 getWorkInfoByIdLiveData(UUID)를 통해 LiveData를 가져오고 observe를 이용해서
Worker의 상태 변화를 감지하여 원하는 동작을 수행하면 됩니다.

when (workInfo.state) {
    WorkInfo.State.SUCCEEDED -> {
        val result = workInfo.outputData.getString(TestWorker.RESULT)
        binding.textView.text = result
        binding.progressBar.isVisible = false
    }
    WorkInfo.State.FAILED -> {
        val result = workInfo.outputData.getString(TestWorker.RESULT)
        binding.textView.text = result
        binding.progressBar.isVisible = false
    }
    WorkInfo.State.RUNNING -> {
        binding.progressBar.isVisible = true
    }
    else -> {}
}

이번 예제에서는 3가지 상태 값을 사용하였습니다.

이 외에도 다음과 같은 상태값들이 있습니다.

public enum State {
    ENQUEUED,
    RUNNING,
    SUCCEEDED,
    FAILED,
    BLOCKED,
    CANCELLED;

    public boolean isFinished() {
        return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
    }
}

 

LiveData를 얻는 방법은 저 방법 외에도 2가지 방법이 더 있습니다.

getWorkInfosByTagLiveData("tag")
getWorkInfosForUniqueWorkLiveData("uniqueWorkName")

 

차이점은 getWorkInfoByIdLiveData(UUID)의 경우 WorkInfo에 대하여 감지하게 되고

저 두개는 MutableList <WorkInfo>에 대하여 감지하게 됩니다.

 

3. 체이닝

체이닝은 여러 개의 Worker가 순차적으로 진행되어야 할 때 사용할 수 있는 방법입니다.

테스트를 위해 Worker를 복사하였습니다.

class Test2Worker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {

    override fun doWork(): Result {
        Log.e("Test2Worker", "Test2Worker")

        // Worker 에게 전달한 데이터 받기
        val inputData = inputData.getString(WORKER_INPUT_DATA)

        // 추가 작업... 파일 업로드, 데이터 통신 등
        Thread.sleep(2000)

        return if (inputData == SUCCESS){
            Log.e("Test2Worker", "Test2 doWork end : Success")
            val output = workDataOf(RESULT to "Test2 doWork end : Success")
            Result.success(output)
        } else {
            Log.e("Test2Worker", "Test2 doWork end : Failure")
            val output = workDataOf(RESULT to "Test2 doWork end : Failure")
            Result.failure(output)
        }
    }

    companion object {
        const val WORKER_INPUT_DATA = "worker_input_data"
        const val SUCCESS = "success"
        const val FAILURE = "failure"
        const val RESULT = "result"
    }

}
val request = createWorkerRequest()
val request2 = createWorkerRequest2()

WorkManager
    .getInstance(this@WorkManagerActivity)
    .beginWith(request)
    .then(request2)
    .enqueue()

WorkRequest 생성 함수는 이전 방식과 동일하게 생성하였습니다.

처음 시작하는 Worker는 beginWith()에 넣어주고, 그다음부터는 then()을 이용하여 여러 개를 넣어줄 수 있습니다.

성공으로 설정하였을 때

E/TestWorker: Test1Worker
E/TestWorker: Test1 doWork end : Success
E/Test2Worker: Test2Worker
E/Test2Worker: Test2 doWork end : Success

결괏값은 실패로 설정하였을 때

E/TestWorker: Test1Worker
E/TestWorker: Test1 doWork end : Failure

이것으로 알 수 있는 점은 체이닝 시 상위 결괏값에 따라 하위 결과 값에 영향을 준다는 걸 확인할 수 있습니다.

먼저 큐에 넣은 Worker의 상태가 Failure, Cancel, Block이 되었을 때 하위로 넘어가지 못하고 나머지도 같은 상태가 됩니다.

그리고 Success 했을 때 outputData를 넘겨주면 다음에 넣은 Worker에서 이전 Worker의 결과 값을 받아올 수 있습니다.

 

 

이렇게 해서 WorkManager의 사용 법을 알아보았습니다.

공식 문서 위주로 작성을 하였습니다.

단순 예제만 진행하였는데 조금 더 사용하는 예제는 다른 포스팅 진행하고 나서 해보려고 계획하고 있습니다.

진행 완료하면 하단 링크 추가해두겠습니다~

 

참고자료
- 공식 사이트 : https://developer.android.com/topic/libraries/architecture/workmanager?hl=ko
728x90

'안드로이드' 카테고리의 다른 글

1. CloudType - MjApp 제작기  (0) 2023.04.30
Android Duplicate class found 오류 해결  (2) 2022.10.28
Compose 기초 1 : Column, Row, Box  (0) 2022.06.29
WorkManager 사용해보기 1  (0) 2022.06.05
댓글