티스토리 뷰
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
'안드로이드' 카테고리의 다른 글
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 |
- Total
- Today
- Yesterday
- Android
- Compose Naver Map
- 안드로이드 구글 지도
- Compose ModalBottomSheetLayout
- Gradient
- Duplicate class fond 에러
- Android Compose
- Duplicate class found error
- Fast api
- 웹뷰
- Row
- Compose QRCode Scanner
- Compose ConstraintLayout
- 포켓몬 도감
- column
- 안드로이드
- Compose BottomSheetDialog
- WorkManager
- Pokedex
- WebView
- Compose MotionLayout
- Retrofit
- Kotlin
- Compose BottomSheetScaffold
- compose
- LazyColumn
- Worker
- Compose BottomSheet
- Compose 네이버 지도
- Compose 네이버 지도 api
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |