본문 바로가기
2023년 이전/Android

Android - WorkManager (5) - Chaining Work

by JeongUPark 2019. 10. 17.
반응형

이전 글 : 2019/10/17 - [프로그래밍/Android] - Android - WorkManager (4) -Observing intermediate Worker progress

출처- Android developer chaining work

 

WorkManager를 사용하면 여러 종속 작업을 지정하고 작업 순서를 정의하는 일련의 작업을 작성하고 대기열에 넣을 수 있습니다. 이는 특정 작업에서 여러 작업을 실행해야 할 때 특히 유용합니다.

 

Chain of work을 만들려면 WorkContinuation 인스턴스를 반환하는 WorkManager.beginWith (OneTimeWorkRequest) 또는 WorkManager.beginWith (List<OneTimeWorkRequest>)를 사용할 수 있습니다.

그런 다음 WorkContinuation을 사용하여 WorkContinuation then(OneTimeWorkRequest) 또는 WorkContinuation.then(List<OneTimeWorkRequest>)을 사용하여 종속 OneTimeWorkRequests를 추가 할 수 있습니다.

 

WorkContinuation.then(...)을 호출 할 때마다 WorkContinuation의 새 인스턴스를 리턴합니다. OneTimeWorkRequests 목록을 추가하면 이러한 요청이 병렬로 실행될 수 있습니다.

마지막으로 WorkContinuation.enqueue () 메서드를 사용하여 WorkContinuations 체인을 enqueue () 할 수 있습니다.

 

그럼 예제 code를 확인 해 보겠습니다.

 

File->New->New Project 선택 후 Fragment+ViewModel로 project를 생성합니다.

 그리고 다음 worker들을 만들어 줍니다.

MyWorker_A.kt

class MyWorker_A_1 (appContext : Context, workerParams: WorkerParameters) : Worker(appContext,workerParams){
    override fun doWork(): Result {
        Log.d("TEST", "start MyWorker_A_1")
        var count = 0
        var doCounting = true
        while (doCounting){
            Log.d("TEST","MyWorker_A_1 now Count : ${++count}" )
            if(count == 3){
                doCounting = false
            }
            Thread.sleep(1000)
        }
        val outputData = workDataOf(Pair("outputData", "MyWorker_A_1 from doWork"))
        return Result.success(outputData)
    }
}
class MyWorker_A_2 (appContext : Context, workerParams: WorkerParameters) : Worker(appContext,workerParams){
    override fun doWork(): Result {

        Log.d("TEST", "start MyWorker_A_2")
        var count = 0
        var doCounting = true
        while (doCounting){
            Log.d("TEST","MyWorker_A_2 now Count : ${++count}" )
            if(count == 3){
                doCounting = false
            }
            Thread.sleep(1000)
        }
        val outputData = workDataOf(Pair("outputData", "MyWorker_A_2 from doWork"))
        return Result.success(outputData)
    }
}
class MyWorker_A_3 (appContext : Context, workerParams: WorkerParameters) : Worker(appContext,workerParams){
    override fun doWork(): Result {

        Log.d("TEST", "start MyWorker_A_3")
        var count = 0
        var doCounting = true
        while (doCounting){
            Log.d("TEST","MyWorker_A_3 now Count : ${++count}" )
            if(count == 3){
                doCounting = false
            }
            Thread.sleep(1000)
        }
        val outputData = workDataOf(Pair("outputData", "MyWorker_A_3 from doWork"))
        return Result.success(outputData)
    }
}

MyWorker_B.kt

class MyWorker_B (appContext : Context, workerParams: WorkerParameters) : Worker(appContext,workerParams){
    override fun doWork(): Result {
        Log.d("TEST", "start MyWorker_B")

        var count = 0
        var doCounting = true
        while (doCounting){
            Log.d("TEST"," MyWorker_B now Count : ${++count}" )
            if(count == 3){
                doCounting = false
            }
            Thread.sleep(1000)
        }
        val outputData = workDataOf(Pair("outputData", "outputData from doWork"))
        return Result.success(outputData)
    }
}

MyWorker_C.kt

class MyWorker_C (appContext : Context, workerParams: WorkerParameters) : Worker(appContext,workerParams){
    override fun doWork(): Result {
            Log.d("TEST", "start MyWorker_C")
        var count = 0
        var doCounting = true
        while (doCounting){
            Log.d("TEST","MyWorker_C now Count : ${++count}" )
            if(count == 3){
                doCounting = false
            }
            Thread.sleep(1000)
        }
        val outputData = workDataOf(Pair("outputData", "outputData from doWork"))
        return Result.success(outputData)
    }
}

 

그 다음 Fragment에 실행을 위한 Btn을 만들고 다음과 같이 WorkRequest들을 추가 합니다.

class MainFragment : Fragment() {

    companion object {
        fun newInstance() = MainFragment()
    }

    private lateinit var viewModel: MainViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return inflater.inflate(R.layout.main_fragment, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        // TODO: Use the ViewModel

      val A_1 =   OneTimeWorkRequestBuilder<MyWorker_A_1>().build()
      val A_2 =  OneTimeWorkRequestBuilder<MyWorker_A_2>().build()
      val A_3 =  OneTimeWorkRequestBuilder<MyWorker_A_3>().build()

      val B =  OneTimeWorkRequestBuilder<MyWorker_B>().build()
      val C =  OneTimeWorkRequestBuilder<MyWorker_C>().build()

        start_btn.setOnClickListener {
            WorkManager.getInstance(context!!).beginWith(listOf(A_1,A_2,A_3)).then(B).then(C).enqueue()
        }

    }

}

 이렇게 추가후 실행 버튼을 누르면 A_1, A_2, A_3 WorkRequest들이 병렬로 실행 된 후 B WorkRequest가 실행되고, 마지막으로 C WorkRequest가 실행 됩니다.

Input Mergers

OneTimeWorkRequests 체인을 사용하는 경우 상위 OneTimeWorkRequests의 출력이 하위 항목에 입력으로 전달됩니다. 따라서 위 예에서 A_1, A_2 및 A_3의 출력은 압축 요청에 대한 입력으로 전달됩니다.

 

여러 상위 OneTimeWorkRequests의 입력을 관리하기 위해 WorkManager는 InputMergers를 사용합니다.

WorkManager에서 제공하는 두 가지 유형의 InputMergers가 있습니다.

 

  • OverwritingInputMerger는 모든 입력의 모든 키를 출력에 추가하려고 시도합니다. 충돌이 발생하면 이전에 설정 한 키를 덮어 씁니다.
  • ArrayCreatingInputMerger는 입력 병합을 시도하여 필요한 경우 배열을 만듭니다.

그럼 simple 예제 code를 보겠습니다.

 

우선 MainFragment에 B workRequest를 만들때 inputMerger를 추가 합니다.

 override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        // TODO: Use the ViewModel

      val A_1 =   OneTimeWorkRequestBuilder<MyWorker_A_1>().build()
      val A_2 =  OneTimeWorkRequestBuilder<MyWorker_A_2>().build()
      val A_3 =  OneTimeWorkRequestBuilder<MyWorker_A_3>().build()

      val B =  OneTimeWorkRequestBuilder<MyWorker_B>().setInputMerger(ArrayCreatingInputMerger::class).build()
      val C =  OneTimeWorkRequestBuilder<MyWorker_C>().build()

        start_btn.setOnClickListener {
            WorkManager.getInstance(context!!).beginWith(listOf(A_1,A_2,A_3)).then(B).then(C).enqueue()
        }

    }

그리고 MyWork_B에 결과를 노출할 Log를 추가 합니다.

class MyWorker_B (appContext : Context, workerParams: WorkerParameters) : Worker(appContext,workerParams){
    override fun doWork(): Result {
        Log.d("TEST", "start MyWorker_B")

        val list = inputData.getStringArray("outputData")
        list?.let {
            Log.d("TEST","MyWorker_B  outputData list : ${list[0]}, ${list[1]}, ${list[2]}")
        }
        var count = 0
        var doCounting = true
        while (doCounting){
            Log.d("TEST"," MyWorker_B now Count : ${++count}" )
            if(count == 3){
                doCounting = false
            }
            Thread.sleep(1000)
        }
        val outputData = workDataOf(Pair("outputData", "outputData from doWork"))
        return Result.success(outputData)
    }
}

MyWorker_A_1 ~ 3모두 Result.success에 key를 outputData로 하는 data를 입력해서 return 하고 이습니다. 그래서 MyWorker_B에서 inputData중에 outputData를 key하는 StringArray를 get 합니다. (ArrayCreatingInputMerger을 선택했으므로 위의 설명처럼 배열을 만들어서 MyWorker_B에서 획득 합니다.)

 

그래서 Log를 확인해보면

MyWorker_A_1~3 이 병렬로 실행되고, MyWorker_B에서 그 값을 획득하고 노출하고 있는것을 확인 할 수 있습니다.

Chaining and Work Statuses

OneTimeWorkRequests 체인을 만들 때 명심해야 할 것이 몇 가지 있습니다.

  • 모든 상위 OneTimeWorkRequest가 성공하면 (즉, Result.success ()를 반환) 종속 OneTimeWorkRequests는 차단 해제됩니다 (ENQUEUED로 전환).
  • 상위 OneTimeWorkRequest가 실패하면 (Result.failure ()를 반환하면 모든 종속 OneTimeWorkRequests도 FAILED로 표시됩니다.
  • 상위 OneTimeWorkRequest가 취소되면 모든 종속 OneTimeWorkRequest도 CANCELLED로 표시됩니다.

 

반응형