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

Android - WorkManager (2) WorkRequest

by JeongUPark 2019. 10. 16.
반응형

이전 글을 보고 오시면 더 좋습니다.( 2019/10/16 - [프로그래밍/Android] - Android - WorkManager(1) )

출처- Android developer WorkReqeust 정의

이전 글에서 WorkManager를 수행하기 위해서는 WorkRequest가 필요했습니다. 그럼 WorkReqeust에 대해 자세히 알아보도록 하겠습니다.

 

Work constraints

작업에 제약 조건을 추가하여 언제 실행할 수 있는지 표시 할 수 있습니다. 예를 들어, 장치가 idle 상태이고 전원에 연결된 경우에만 작업을 실행하도록 지정할 수 있습니다. 

Code로는 다음과 같습니다. (저번 글에서 만들었던 Project의 MainFragment에 작성하였습니다.)

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)

        val constraints = Constraints.Builder()
                .setRequiresDeviceIdle(true)
                .setRequiresCharging(false).build()
        val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
            .setConstraints(constraints)
            .build()
        workmanager_start_btn.setOnClickListener {
           WorkManager.getInstance(context!!).enqueue(myWorkRequest)
        }
    }
}

위의 code를 보면 constraints를 만들고 그 constrain을 OneTimeWorkRequest에 추가할 수 있습니다. (제약 조건의 자세한 내용은 Android Developer Constrains.Builder 페이지에서 확인 할 수 있습니다.) 그리고 위의 constrain은 device가 Idle 상태이고 충전중이 아닐 때 동작하는 제약 조건입니다. 위의 조건을 만족할 때 workManager가 동작합니다. (Log를 보기 위해 C타입 케이블을 연결해 놓으면 충전중인 상태이기 때문에 WorkManager가 동작 하지 않습니다.setRequiresCharging(false)를 true로 바꾸면 동작합니다.)

 

즉, 여러 제약 조건을 지정하면 모든 제약 조건이 충족 될 때만 작업이 실행고,  작업이 실행되는 동안 구속 조건이 실패하면 WorkManager가 작업자를 중지시킵니다. 그런 다음 제약 조건이 충족되면 작업이 재 시도됩니다.

 

Initial Delays

constrain가 있든 없든 workRequest가 enqueued가 되면, system은 작업을 바로 실행 시킬 것 입니다. 하지만 작업을 바로 실행 시키길 원치 않을 경우에는 InitilDelay를 주면 시작 시간을 조절 할 수 있습니다. 그럼 code로 확인해 보겠습니다. (Code는 위의 MainFragment에 추가 하도록 하겠습니다.)

override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
            .setInitialDelay(5,TimeUnit.SECONDS)
            .build()
        workmanager_start_btn.setOnClickListener {
           WorkManager.getInstance(context!!).enqueue(myWorkRequest)
        }
    }

위에 code를 보면 workRequest를 만들 때 setInitialDelay가 추가 되어 있습니다. setInitialDelay는 두가지가 있습니다.

public @NonNull B setInitialDelay(long duration, @NonNull TimeUnit timeUnit) {
    mWorkSpec.initialDelay = timeUnit.toMillis(duration);
    return getThis();
}

@RequiresApi(26)
public @NonNull B setInitialDelay(@NonNull Duration duration) {
    mWorkSpec.initialDelay = duration.toMillis();
    return getThis();
}

첫 번째 setinitalDelay를 duration 값과 뒤에 timeUnit을 조합하여 그만큼 딜레이를 주고, 두번째 setinitialDelay는 duration Millisecond 만큼 딜레이가 발생합니다. (timeUnit에는 NANOSECONDS, MICROSECONDS, MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS가 있습니다)

 

이렇게 딜레이를 주면 딜레이 이후 작업이 실행 됩니다. 결과를 보면

 시간을 보면 click 후 약 5초 후 동작함을 확인 할 수 있습니다.

 

Retries and Backoff Policy

작업을 다시 동작하고 싶으면 Work class에 onWork() Method 안에 retrun 결과를 Result.retry()를 반환하면 됩니다.

그런 다음 기본 백 오프 지연 및 정책으로 작업 일정이 조정하면 됩니다. 백 오프 지연은 작업을 재 동작하기 전에 대기 할 최소 시간을 지정합니다. 백 오프 정책은 다음 재 동작을 위해 백 오프 지연이 시간이 지남에 따라 어떻게 증가하는지 정의합니다. 기본적으로 EXPONENTIAL입니다. (EXPONENTIAL 이외 LINEAR이 있습니다.)

 

EXPONENTIAL : WorkManager가 백 오프 시간을 기하 급수적으로 늘려야 함을 나타내는 데 사용됩니다.

LINEAR : WorkManager가 백 오프 시간을 선형으로 늘려야 함을 나타내는 데 사용됩니다.

 

그럼 사용 방법을 확인해 보겠습니다.

 

    override fun doWork(): Result {
     		.....
        return Result.retry()
    }

우선 doWork에서 return을 Result.retry()로 변경 하고 

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

        val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
            .setBackoffCriteria(
                BackoffPolicy.LINEAR,
                OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
                TimeUnit.MICROSECONDS)
            .build()
        workmanager_start_btn.setOnClickListener {
            Log.d("TEST", "Click")
           WorkManager.getInstance(context!!).enqueue(myWorkRequest)
        }
    }

위와 같이 설정하여 build 합니다. 위의 setBackoffCriteria의 code를 살펴보면

/**
 * The maximum backoff time (in milliseconds) for work that has to be retried.
*/
public static final long MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000; // 5 hours.
/**
 * The minimum backoff time for work (in milliseconds) that has to be retried.
 */
public static final long MIN_BACKOFF_MILLIS = 10 * 1000; // 10 seconds.
    
    ....
    
public final @NonNull B setBackoffCriteria(
                @NonNull BackoffPolicy backoffPolicy,
                long backoffDelay,
                @NonNull TimeUnit timeUnit) {
    mBackoffCriteriaSet = true;
    mWorkSpec.backoffPolicy = backoffPolicy;
    mWorkSpec.setBackoffDelayDuration(timeUnit.toMillis(backoffDelay));
    return getThis();
}
        
        ....
        
/**
  * @param backoffDelayDuration The backoff delay duration in milliseconds
*/
public void setBackoffDelayDuration(long backoffDelayDuration) {
    if (backoffDelayDuration > MAX_BACKOFF_MILLIS) {
        Logger.get().warning(TAG, "Backoff delay duration exceeds maximum value");
        backoffDelayDuration = MAX_BACKOFF_MILLIS;
    }
    if (backoffDelayDuration < MIN_BACKOFF_MILLIS) {
        Logger.get().warning(TAG, "Backoff delay duration less than minimum value");
        backoffDelayDuration = MIN_BACKOFF_MILLIS;
    }
    this.backoffDelayDuration = backoffDelayDuration;
}

설정 시간의 최대 값과 최소값이 정해져 있음을 확인 할 수 있습니다.

 

Defining input/output for your task

수행하는 동작에서 입력 매개 변수로 전달되거나 결과로 리턴해야 하는 데이터가 있을 수 있습니다. 예를 들어 이미지 업로드를 처리하는 작업에서는 이미지의 URI를 입력으로 업로드해야하며 업로드 된 이미지의 URL을 출력으로 요구할 수 있습니다.

입력 및 출력 값은 Data 객체에 키-값 쌍으로 저장됩니다. 아래 코드는 WorkRequest에서 입력 데이터를 설정하는 방법을 보여줍니다.

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

        val sendData = workDataOf(Pair("strData", "Worker get SendData"))
        val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
            .setInputData(sendData)
            .build()
        workmanager_start_btn.setOnClickListener {
            Log.d("TEST", "Click")
            WorkManager.getInstance(context!!).enqueue(myWorkRequest)
        }
        WorkManager.getInstance(context!!).getWorkInfoByIdLiveData(myWorkRequest.id)
            .observe(viewLifecycleOwner,
                Observer { t ->
                    t?.let {
                        val outputData = it.outputData.getString("outputData")
                        if (outputData != null) {
                            Log.d("TEST", "outpudata : $outputData")
                        }
                    }
                })
    }

workDataof의 code를 보면 Pair 데이터를 받는 것을 볼 수 있습니다.

inline fun workDataOf(vararg pairs: Pair<String, Any?>): Data {
    val dataBuilder = Data.Builder()
    for (pair in pairs) {
        dataBuilder.put(pair.first, pair.second)
    }
    return dataBuilder.build()
}

 그리고 이렇게 전달 받은 데이터는 Worker class에서 확인 할 수 있는데 그 확인 방법은 다음에서 보여줍니다.

class MyWorker (appContext : Context, workerParams: WorkerParameters) : Worker(appContext,workerParams){
    override fun doWork(): Result {

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

Fragment에서 전달한 workDataOf로 만든 데이터를 inputData를 통하여 받을 수 있습니다. 또 workData를 만들어서 Result.success를 통하여 return 하면 위의 Fragment의  WorkManager.getInstance(context!!).getWorkInfoByIdLiveData(myWorkRequest.id).observe 의 Observer 에서 확인 할 수 있습니다.

(참고, workDataof를 통해 만들 수 있는 데이터 타입은  Strings, primitive types, or their array variants가 있고, 데이터 객체의 최대 크기 제한은 10KB입니다.)

결과는 다음과 같습니다.

Tagging work

WorkRequest 객체에 태그 문자열을 할당하여 작업을 논리적으로 그룹화 할 수 있습니다. 이를 통해 특정 태그가있는 모든 작업을 수행 할 수 있습니다.

예를 들어 WorkManager.cancelAllWorkByTag (String)은 특정 태그가있는 모든 작업을 취소하고 WorkManager.getWorkInfosByTagLiveData (String)는 해당 태그가있는 모든 작업의 ​​상태 목록이 포함 된 LiveData를 반환합니다.

다음은 WorkRequest에 tag를 추가하는 방법입니다.

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        val myWorkRequest = OneTimeWorkRequestBuilder<MyWorker>()
            .addTag("MyWork")
            .build()
        workmanager_start_btn.setOnClickListener {
            Log.d("TEST", "Click")
            WorkManager.getInstance(context!!).enqueue(myWorkRequest)
        }
    }

위의 code에서 addTag가 Tag를 추가하는 방법입니다.

 

더 자세한 사항은 Android Developer의 Defining your Work Requests 에서 확인 할 수 있습니다.

반응형