#WorkManager Android Developer 공식문서
=>https://developer.android.com/topic/libraries/architecture/workmanager?hl=ko
=>https://developer.android.com/topic/libraries/architecture/workmanager/basics?hl=ko
-링크 : https://developer.android.com/guide/background?hl=ko
-백그라운드 작업은 즉시실행 , 지연실행 , 정시실행 이 세가지 카테고리로 분류한다. 그렇다면 각각의 카테고리는 어느 때에 실행되어야 하는 것일까?
-아래의 테스트를 통과하면서 남는 카테고리를 찾으면 된다.
-[1.사용자가 애플리케이션을 사용하는 동안 작업이 완료되어야 하는가?] : 이 경우는 즉시실행이 된다. 이 즉시실행은 코루틴을 사용하여 백그라운드 작업을 실행하면 된다.
-[2.작업이 정확한 시간에 실행되어야 하는가?] : 이 경우 정시 실행으로 넘어간다. 이 정시실행은 AlarmManager를 사용해서 백그라운드 작업을 실행하면 된다.
-[3.정확한 시간에 실행하지 않아도 되는 가?] : 이 경우 지연 실행으로 넘어간다. 이 지연실행을 WorkManager를 통해서 안드로이드의 백그라운드 작업을 실행한다. 우리는 이 WorkManager를 통해서 안드로이드에서 백그라운드 작업을 어떻게 실행하는지 공부 할 것이다.
-WorkManager는 라이브러리로서 비동기 작업을 스케쥴링하고 실행하며, 앱이 종료되더라도 시스템의 백그라운드에서 해당 앱을 실행 할 수 있게 해준다. 실행 할 작업을 doWork()를 구현해서 넣어주면 된다.
-WorkManager에서는 4가지의 Worker 유형을 제공해준다. [1.Worker][2.CoroutineWorker] [3.RxWorker][4.ListenableWorker] 이렇게 총 4가지의 유형을 제공해주는데, Kotlin 사용자에게는 CoroutineWorker를 권장한다고 공식문서에 나와있으므로 WorkManer를 앞으로는 CoroutineWorker라고 생각해도 될 듯 하다.
-안드로이드의 WorkManager 라이브러리에서 제공하는 클래스로 , 비동기 작업을 코루틴을 사용하여 수행 할 수 있게 해주는 ListenableWorker의 하위 클래스이다. 해당 클래스는 Coroutine+WorkManager를 결합하여 백그라운드 작업을 쉽고 효율적으로 처리 할 수 있게 도와준다.
-CoroutineWorker의 전체 코드는 아래와 같으며 주석처리된 부분에 설명도 나와있으므로 참고하면 좋을 듯 함.
-CoroutineWorker는 기본적으로 Dispatchers.Default라는 코루틴 디스패처에서 작업을 실행하기 때문에 CPU를 많이 사용하는 작업에 최적화된 스레드 풀에서 작업을 수행한다. 또한 doWork() 함수 내부에서 withContext()를 사용해서 다른 디스패처에서 작업을 수행 할 것을 권장하고 있다. 이는 coroutineContext가 @Deprecated 되었다는 메시지와 함께 구글에서 권장하고 있는 방법이다. doWork()를 override하여 작업 로직을 구현하면 되고 작업이 성공적으로 완료된다면 ListenableWorker.Result 타입의 결과를 반환한다. doWork()는 비동기 작업을 수행하기 위한 함수로서 suspend Function이다.
-setProgress() 함수를 사용하면 작업의 진행 상태를 업데이트 할 수 있다. setProgress() 함수를 통해 전달되는 Data 객체는 작업의 진행 상태에 대한 정보를 담고 있으며 , 이 정보는 앱의 UI 컴포넌트와 함게 공유 될 수 있어서 시각적으로 사용자에게 작업의 진행상태에 대한 정보를 알려 줄 수 있다.
public abstract class CoroutineWorker(
appContext: Context,
params: WorkerParameters
) : ListenableWorker(appContext, params) {
internal val job = Job()
internal val future: SettableFuture<Result> = SettableFuture.create()
init {
future.addListener(
Runnable {
if (future.isCancelled) {
job.cancel()
}
},
taskExecutor.serialTaskExecutor
)
}
/**
* The coroutine context on which [doWork] will run. By default, this is [Dispatchers.Default].
*/
@Deprecated(message = "use withContext(...) inside doWork() instead.")
public open val coroutineContext: CoroutineDispatcher = Dispatchers.Default
@Suppress("DEPRECATION")
public final override fun startWork(): ListenableFuture<Result> {
val coroutineScope = CoroutineScope(coroutineContext + job)
coroutineScope.launch {
try {
val result = doWork()
future.set(result)
} catch (t: Throwable) {
future.setException(t)
}
}
return future
}
/**
* A suspending method to do your work.
* <p>
* To specify which [CoroutineDispatcher] your work should run on, use `withContext()`
* within `doWork()`.
* If there is no other dispatcher declared, [Dispatchers.Default] will be used.
* <p>
* A CoroutineWorker is given a maximum of ten minutes to finish its execution and return a
* [ListenableWorker.Result]. After this time has expired, the worker will be signalled to
* stop.
*
* @return The [ListenableWorker.Result] of the result of the background work; note that
* dependent work will not execute if you return [ListenableWorker.Result.failure]
*/
public abstract suspend fun doWork(): Result
/**
* @return The [ForegroundInfo] instance if the [WorkRequest] is marked as expedited.
*
* @throws [IllegalStateException] when not overridden. Override this method when the
* corresponding [WorkRequest] is marked expedited.
*/
public open suspend fun getForegroundInfo(): ForegroundInfo {
throw IllegalStateException("Not implemented")
}
/**
* Updates the progress for the [CoroutineWorker]. This is a suspending function unlike the
* [setProgressAsync] API which returns a [ListenableFuture].
*
* @param data The progress [Data]
*/
public suspend fun setProgress(data: Data) {
setProgressAsync(data).await()
}
/**
* Makes the [CoroutineWorker] run in the context of a foreground [android.app.Service]. This
* is a suspending function unlike the [setForegroundAsync] API which returns a
* [ListenableFuture].
*
* Calling [setForeground] will throw an [IllegalStateException] if the process is subject to
* foreground service restrictions. Consider using [WorkRequest.Builder.setExpedited]
* and [getForegroundInfo] instead.
*
* @param foregroundInfo The [ForegroundInfo]
*/
public suspend fun setForeground(foregroundInfo: ForegroundInfo) {
setForegroundAsync(foregroundInfo).await()
}
@Suppress("DEPRECATION")
public final override fun getForegroundInfoAsync(): ListenableFuture<ForegroundInfo> {
val job = Job()
val scope = CoroutineScope(coroutineContext + job)
val jobFuture = JobListenableFuture<ForegroundInfo>(job)
scope.launch {
jobFuture.complete(getForegroundInfo())
}
return jobFuture
}
public final override fun onStopped() {
super.onStopped()
future.cancel(false)
}
}
-아래와 같이 모듈 수준의 build.gradle에 WorkManager 종속성을 추가해준다.
implementation ("androidx.work:work-runtime-ktx:${WorkManager_Verision}")
-CoroutineWorker()는 parameter로 Context와 WorkerParameters를 받기 때문에 이와 같은 설정과 함께 상속을 해줘야 한다.
class PhotoCompressionWorker(
private val appContext: Context,
private val params: WorkerParameters
): CoroutineWorker(appContext, params)
-doWork() 함수는 작업을 실행하려고 할 때 실행될 함수이며 , doWork()는 오버라이딩 해야된다. 그리고 해당 코드 블록 내부에서 params를 사용해서 작업을 실행 할 코드를 작성하면 된다.
-doWork() 함수내에서 작업을 하는데 있어서 필요한 데이터는 WorkerParameters에 들어있는 정보를 가져와야 한다. (WorkerParameters에 정보를 넣어주는 것은 RequestBuilder에서 보내준것이다.)
-doWork() 함수내에서 params를 이용새서 WorkRequest의 객체의 정보를 가져와서 작업하는데 사용 할 수 있다.
-params.inputData.[getString() , getLong() 등등의 get자료형()]을 활용해서 받아 올 수 있다.
override suspend fun doWork() : Result {
}
-doWork()의 작업에 필요한 입력 데이터를 보내줘야 한다. 이는 두가지의 방법을 가지고 있다. 첫째, 단일 실행 작업을 요청하느냐 둘째, 주기적으로 반복되는 작업을 요청하느냐 이렇게 두가지의 경우 중 어느 경우를 선택하느냐에 따라서 코드 작성하는 방법이 다르다.
-단일 실행 작업 요청
OneTimeWorkRequestBuilder<CoroutineWorker를 상속받은 클래스>()
.setInputData( workDataOf() )
.build() : WorkRequest 객체 생성
-위에서 만든것을 request 변수에 저장했다는 가정하에 최종적으로 workManager의 객체의 enqueue 메서드를 활용해서 요청된 작업을 큐에 추가하여 작업이 실행 될 수 있는 조건이 만족되었을 때 큐에 있는 작업을 시작 할 수 있게 해주어야 한다.
workManager.enqueue(request)
-주기적으로 반복되는 작업 요청
PeriodicWorkRequestBuilder<CoroutineWorker를 상속받은 클래스>(interval 숫자 , TimeUnit.종류(ex:HOUR , MINUTES , DAYS..))
.setInputData( workDataOf() )
.build() : WorkRequest 객체 생성
-위에서 만든것을 request 변수에 저장했다는 가정하에 최종적으로 workManager의 객체의 enqueue 메서드를 활용해서 요청된 작업을 큐에 추가하여 작업이 실행 될 수 있는 조건이 만족되었을 때 큐에 있는 작업을 시작 할 수 있게 해주어야 한다.
workManager.enqueue(request)
-
후...압축해도 예쁘네...