Android Kotlin 기초 09.2: WorkManager

이 Codelab은 Android Kotlin 기초 과정의 일부입니다. Codelab을 순서대로 진행하면 이 과정의 학습 효과를 극대화할 수 있습니다. 모든 과정 Codelab은 Android Kotlin 기본사항 Codelab 방문 페이지에 나열되어 있습니다.

소개

대부분의 실제 앱은 오래 걸리는 백그라운드 작업을 실행해야 합니다. 예를 들어 앱은 파일을 서버에 업로드하거나, 서버에서 데이터를 동기화하여 Room 데이터베이스에 저장하거나, 로그를 서버에 전송하거나, 데이터에 대해 비용이 많이 드는 작업을 실행할 수 있습니다. 이러한 작업은 UI 스레드 (기본 스레드)가 아닌 백그라운드에서 실행해야 합니다. 백그라운드 작업은 RAM, 배터리 같은 기기의 제한된 리소스를 소모합니다. 따라서 백그라운드 작업이 제대로 처리되지 않으면 사용자의 경험이 저하될 수 있습니다.

이 Codelab에서는 WorkManager를 사용하여 최적화되고 효율적인 방식으로 백그라운드 작업을 예약하는 방법을 알아봅니다. Android의 백그라운드 처리에 사용할 수 있는 다른 솔루션에 대해 자세히 알아보려면 백그라운드 처리 가이드를 참고하세요.

기본 요건

  • ViewModel, LiveData, Room Android 아키텍처 구성요소를 사용하는 방법
  • LiveData 클래스에서 변환을 실행하는 방법
  • 코루틴을 빌드하고 실행하는 방법
  • 데이터 결합에서 결합 어댑터를 사용하는 방법
  • 저장소 패턴을 사용하여 캐시된 데이터를 로드하는 방법

학습할 내용

  • 작업 단위를 나타내는 Worker을 만드는 방법
  • 작업 실행을 요청하는 WorkRequest를 만드는 방법
  • WorkRequest제약 조건을 추가하여 작업자가 언제 어떻게 실행되어야 하는지 정의하는 방법
  • WorkManager를 사용하여 백그라운드 작업을 예약하는 방법

실습할 내용

  • 네트워크에서 DevBytes 동영상 재생목록을 미리 가져오는 백그라운드 작업을 실행하는 작업자를 만듭니다.
  • 작업자가 주기적으로 실행되도록 예약합니다.
  • WorkRequest에 제약 조건을 추가합니다.
  • 하루에 한 번 실행되는 주기적 WorkRequest를 예약합니다.

이 Codelab에서는 이전 Codelab에서 개발한 DevBytes 앱을 사용합니다. (이 앱이 없는 경우 이 강의의 시작 코드를 다운로드하면 됩니다.)

DevBytes 앱에는 Google Android 개발자 관계팀이 만든 짧은 튜토리얼인 DevByte 동영상 목록이 표시됩니다. 동영상에서는 Android 개발을 위한 개발자 기능과 권장사항을 소개합니다.

하루에 한 번 동영상을 미리 가져와 앱의 사용자 환경을 개선합니다. 이렇게 하면 사용자가 앱을 열자마자 최신 콘텐츠를 볼 수 있습니다.

이 작업에서는 시작 코드를 다운로드하고 검사합니다.

1단계: 시작 앱 다운로드 및 실행

이전 Codelab에서 빌드한 DevBytes 앱이 있다면 계속해서 작업할 수 있습니다. 또는 스타터 앱을 다운로드할 수 있습니다.

이 작업에서는 시작 앱을 다운로드하여 실행하고 시작 코드를 검사합니다.

  1. DevBytes 앱이 아직 없는 경우 GitHub의 DevBytesRepository 프로젝트에서 이 Codelab의 DevBytes 시작 코드를 다운로드합니다.
  2. 코드의 압축을 풀고 Android 스튜디오에서 프로젝트를 엽니다.
  3. 테스트 기기 또는 에뮬레이터가 아직 연결되어 있지 않다면 인터넷에 연결합니다. 앱을 빌드하고 실행합니다. 앱이 네트워크에서 DevByte 동영상 목록을 가져와 표시합니다.
  4. 앱에서 동영상을 탭하여 YouTube 앱에서 엽니다.

2단계: 코드 살펴보기

시작 앱에는 이전 Codelab에서 소개된 코드가 많이 포함되어 있습니다. 이 Codelab의 시작 코드에는 네트워킹, 사용자 인터페이스, 오프라인 캐시, 저장소 모듈이 있습니다. WorkManager를 사용하여 백그라운드 작업을 예약하는 데 집중할 수 있습니다.

  1. Android 스튜디오에서 모든 패키지를 펼칩니다.
  2. database 패키지를 살펴봅니다. 패키지에는 데이터베이스 항목과 Room를 사용하여 구현된 로컬 데이터베이스가 포함되어 있습니다.
  3. repository 패키지를 살펴봅니다. 이 패키지에는 앱의 나머지 부분에서 데이터 레이어를 추상화하는 VideosRepository 클래스가 포함되어 있습니다.
  4. 이전 Codelab의 도움을 받아 시작 코드의 나머지 부분을 직접 살펴보세요.

WorkManagerAndroid 아키텍처 구성요소 중 하나이며 Android Jetpack의 일부입니다. WorkManager는 지연 가능하고 실행이 보장되어야 하는 백그라운드 작업에 사용됩니다.

  • 지연 가능은 작업을 즉시 실행할 필요가 없음을 의미합니다. 예를 들어 분석 데이터를 서버로 전송하거나 백그라운드에서 데이터베이스를 동기화하는 작업은 지연될 수 있습니다.
  • 보장된 실행은 앱이 종료되거나 기기가 다시 시작되더라도 작업이 실행된다는 의미입니다.

WorkManager는 백그라운드 작업을 실행하는 동안 호환성 문제와 배터리 및 시스템 상태에 관한 권장사항을 처리합니다. WorkManager는 API 수준 14까지 호환성을 제공합니다. WorkManager는 기기 API 수준에 따라 백그라운드 작업을 예약하는 적절한 방법을 선택합니다. JobScheduler (API 23 이상) 또는 AlarmManagerBroadcastReceiver의 조합을 사용할 수 있습니다.

WorkManager를 사용하면 백그라운드 작업이 실행되는 시점에 대한 기준을 설정할 수도 있습니다. 예를 들어 배터리 상태, 네트워크 상태 또는 충전 상태가 특정 기준을 충족하는 경우에만 태스크를 실행할 수 있습니다. 이 Codelab의 뒷부분에서 제약 조건을 설정하는 방법을 알아봅니다.

이 Codelab에서는 하루에 한 번 네트워크에서 DevBytes 동영상 재생목록을 미리 가져오는 작업을 예약합니다. 이 작업을 예약하려면 WorkManager 라이브러리를 사용합니다.

  1. build.gradle (Module:app) 파일을 열고 WorkManager 종속 항목을 프로젝트에 추가합니다.

    최신 버전의 라이브러리를 사용하면 솔루션 앱이 예상대로 컴파일됩니다. 그렇지 않으면 문제를 해결하거나 아래에 표시된 라이브러리 버전으로 되돌리세요.
// WorkManager dependency
def work_version = "1.0.1"
implementation "android.arch.work:work-runtime-ktx:$work_version"
  1. 프로젝트를 동기화하고 컴파일 오류가 없는지 확인합니다.

프로젝트에 코드를 추가하기 전에 WorkManager 라이브러리의 다음 클래스를 숙지하세요.

  • Worker
    이 클래스에서 백그라운드에서 실행할 실제 작업 (태스크)을 정의합니다. 이 클래스를 확장하고 doWork() 메서드를 재정의합니다. doWork() 메서드는 서버와 데이터를 동기화하거나 이미지를 처리하는 등 백그라운드에서 실행할 코드를 배치하는 곳입니다. 이 작업에서는 Worker를 구현합니다.
  • WorkRequest
    이 클래스는 백그라운드에서 작업자를 실행하기 위한 요청을 나타냅니다. WorkRequest를 사용하여 기기 연결 또는 Wi-Fi 연결과 같은 Constraints의 도움을 받아 작업자 작업을 실행하는 방법과 시기를 구성합니다. 이후 작업에서 WorkRequest를 구현합니다.
  • WorkManager
    이 클래스는 WorkRequest를 예약하고 실행합니다. WorkManager는 지정된 제약 조건을 준수하면서 시스템 리소스에 부하를 분산하는 방식으로 작업 요청을 예약합니다. 이후 작업에서 WorkManager를 구현합니다.

1단계: 작업자 만들기

이 작업에서는 Worker를 추가하여 백그라운드에서 DevBytes 동영상 재생목록을 미리 가져옵니다.

  1. devbyteviewer 패키지 내에서 work이라는 새 패키지를 만듭니다.
  2. work 패키지에서 RefreshDataWorker이라는 새 Kotlin 클래스를 만듭니다.
  3. CoroutineWorker 클래스에서 RefreshDataWorker 클래스를 확장합니다. contextWorkerParameters을 생성자 매개변수로 전달합니다.
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {
}
  1. 추상 클래스 오류를 해결하려면 RefreshDataWorker 클래스 내에서 doWork() 메서드를 재정의하세요.
override suspend fun doWork(): Result {
  return Result.success()
}

정지 함수는 일시중지되었다가 나중에 다시 시작할 수 있는 함수입니다. 정지 함수는 기본 스레드를 차단하지 않고 장기 실행 작업을 실행하고 완료될 때까지 기다릴 수 있습니다.

2단계: doWork() 구현

Worker 클래스 내의 doWork() 메서드는 백그라운드 스레드에서 호출됩니다. 이 메서드는 동기식으로 작업을 실행하며 ListenableWorker.Result 객체를 반환해야 합니다. Android 시스템은 Worker가 실행을 완료하고 ListenableWorker.Result 객체를 반환하는 데 최대 10분을 허용합니다. 이 시간이 만료되면 시스템에서 Worker를 강제로 중지합니다.

ListenableWorker.Result 객체를 만들려면 다음 정적 메서드 중 하나를 호출하여 백그라운드 작업의 완료 상태를 나타냅니다.

이 작업에서는 doWork() 메서드를 구현하여 네트워크에서 DevBytes 동영상 재생목록을 가져옵니다. VideosRepository 클래스의 기존 메서드를 재사용하여 네트워크에서 데이터를 가져올 수 있습니다.

  1. RefreshDataWorker 클래스의 doWork() 내에서 VideosDatabase 객체와 VideosRepository 객체를 만들고 인스턴스화합니다.
override suspend fun doWork(): Result {
   val database = getDatabase(applicationContext)
   val repository = VideosRepository(database)

   return Result.success()
}
  1. RefreshDataWorker 클래스에서 doWork() 내부의 return 문 위에 try 블록 내에서 refreshVideos() 메서드를 호출합니다. 작업자가 실행되는 시점을 추적하는 로그 추가
try {
   repository.refreshVideos( )
   Timber.d("Work request for sync is run")
   } catch (e: HttpException) {
   return Result.retry()
}

'해결되지 않은 참조' 오류를 해결하려면 retrofit2.HttpException를 가져오세요.

  1. 다음은 참고용으로 제공되는 전체 RefreshDataWorker 클래스입니다.
class RefreshDataWorker(appContext: Context, params: WorkerParameters) :
       CoroutineWorker(appContext, params) {

   override suspend fun doWork(): Result {
       val database = getDatabase(applicationContext)
       val repository = VideosRepository(database)
       try {
           repository.refreshVideos()
       } catch (e: HttpException) {
           return Result.retry()
       }
       return Result.success()
   }
}

Worker는 작업 단위를 정의하고 WorkRequest는 작업이 언제 어떻게 실행되어야 하는지 정의합니다. WorkRequest 클래스에는 두 가지 구체적인 구현이 있습니다.

  • OneTimeWorkRequest 클래스는 일회성 작업에 사용됩니다. (일회성 작업은 한 번만 실행됩니다.)
  • PeriodicWorkRequest 클래스는 간격을 두고 반복되는 작업인 주기적 작업에 사용됩니다.

작업은 일회성이거나 주기적일 수 있으므로 이에 따라 수업을 선택하세요. 반복 작업 예약에 대한 자세한 내용은 반복 작업 문서를 참고하세요.

이 작업에서는 이전 작업에서 만든 작업자를 실행하는 WorkRequest를 정의하고 예약합니다.

1단계: 반복 작업 설정

Android 앱 내에서 Application 클래스는 활동, 서비스와 같은 다른 모든 구성요소를 포함하는 기본 클래스입니다. 애플리케이션 또는 패키지의 프로세스가 생성되면 Application 클래스 (또는 Application의 서브클래스)가 다른 클래스보다 먼저 인스턴스화됩니다.

이 샘플 앱에서 DevByteApplication 클래스는 Application 클래스의 서브클래스입니다. DevByteApplication 클래스는 WorkManager을 예약하기에 적합합니다.

  1. DevByteApplication 클래스에서 setupRecurringWork()라는 메서드를 만들어 반복되는 백그라운드 작업을 설정합니다.
/**
* Setup WorkManager background job to 'fetch' new network data daily.
*/
private fun setupRecurringWork() {
}
  1. setupRecurringWork() 메서드 내에서 PeriodicWorkRequestBuilder() 메서드를 사용하여 하루에 한 번 실행되는 주기적 작업 요청을 만들고 초기화합니다. 이전 작업에서 만든 RefreshDataWorker 클래스를 전달합니다. 시간 단위가 TimeUnit.DAYS인 반복 간격 1를 전달합니다.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .build()

이 오류를 해결하려면 java.util.concurrent.TimeUnit를 가져오세요.

2단계: WorkManager로 WorkRequest 예약

WorkRequest를 정의한 후 enqueueUniquePeriodicWork() 메서드를 사용하여 WorkManager로 호출할 수 있습니다. 이 메서드를 사용하면 고유한 이름의 PeriodicWorkRequest를 대기열에 추가할 수 있으며, 특정 이름의 PeriodicWorkRequest는 한 번에 하나만 활성화될 수 있습니다.

예를 들어 동기화 작업 하나만 활성화되도록 할 수 있습니다. 동기화 작업 하나가 대기 중인 경우 작업을 실행하도록 하거나 ExistingPeriodicWorkPolicy를 사용하여 새 작업으로 대체할 수 있습니다.

WorkRequest를 예약하는 방법에 대해 자세히 알아보려면 WorkManager 문서를 참고하세요.

  1. RefreshDataWorker 클래스에서 클래스 시작 부분에 컴패니언 객체를 추가합니다. 이 작업자를 고유하게 식별하는 작업 이름을 정의합니다.
companion object {
   const val WORK_NAME = "com.example.android.devbyteviewer.work.RefreshDataWorker"
}
  1. DevByteApplication 클래스에서 setupRecurringWork() 메서드의 끝에 enqueueUniquePeriodicWork() 메서드를 사용하여 작업을 예약합니다. ExistingPeriodicWorkPolicy에 KEEP enum을 전달합니다. repeatingRequestPeriodicWorkRequest 매개변수로 전달합니다.
WorkManager.getInstance().enqueueUniquePeriodicWork(
       RefreshDataWorker.WORK_NAME,
       ExistingPeriodicWorkPolicy.KEEP,
       repeatingRequest)

이름이 같은 대기 중 (미완료) 작업이 있는 경우 ExistingPeriodicWorkPolicy.KEEP 매개변수로 인해 WorkManager가 이전 주기적 작업을 유지하고 새 작업 요청을 삭제합니다.

  1. DevByteApplication 클래스 시작 부분에 CoroutineScope 객체를 만듭니다. Dispatchers.Default을 생성자 매개변수로 전달합니다.
private val applicationScope = CoroutineScope(Dispatchers.Default)
  1. DevByteApplication 클래스에서 코루틴을 시작하는 delayedInit()이라는 새 메서드를 추가합니다.
private fun delayedInit() {
   applicationScope.launch {
   }
}
  1. delayedInit() 메서드 내에서 setupRecurringWork()를 호출합니다.
  2. Timber 초기화를 onCreate() 메서드에서 delayedInit() 메서드로 이동합니다.
private fun delayedInit() {
   applicationScope.launch {
       Timber.plant(Timber.DebugTree())
       setupRecurringWork()
   }
}
  1. DevByteApplication 클래스의 onCreate() 메서드 끝에 delayedInit() 메서드 호출을 추가합니다.
override fun onCreate() {
   super.onCreate()
   delayedInit()
}
  1. Android 스튜디오 창 하단에서 Logcat 창을 엽니다. RefreshDataWorker을 기준으로 필터링
  2. 앱을 실행합니다. WorkManager가 반복 작업을 즉시 예약합니다.

    Logcat 창에서 작업 요청이 예약된 후 성공적으로 실행되었음을 보여주는 로그 문을 확인합니다.
D/RefreshDataWorker: Work request for sync is run
I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

WM-WorkerWrapper 로그는 WorkManager 라이브러리에서 표시되므로 이 로그 메시지는 변경할 수 없습니다.

3단계: (선택사항) 최소 간격으로 WorkRequest 예약

이 단계에서는 시간 간격을 1일에서 15분으로 줄입니다. 이렇게 하면 주기적 작업 요청의 로그가 작동 중인 것을 확인할 수 있습니다.

  1. DevByteApplication 클래스의 setupRecurringWork() 메서드 내에서 현재 repeatingRequest 정의를 주석 처리합니다. 주기적 반복 간격이 15분인 새 작업 요청을 추가합니다.
// val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
//        .build()
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
       .build()
  1. Android 스튜디오에서 Logcat 창을 열고 RefreshDataWorker로 필터링합니다. 이전 로그를 지우려면 Clear logcat 아이콘을 클릭합니다 .
  2. 앱을 실행하면 WorkManager가 반복 작업을 즉시 예약합니다. Logcat 창에서 로그를 확인합니다. 작업 요청이 15분마다 한 번 실행됩니다. 15분 동안 기다리면 다른 작업 요청 로그가 표시됩니다. 앱을 실행 상태로 두거나 닫을 수 있습니다. 작업 관리자는 계속 실행됩니다.

    인터벌이 15분 미만인 경우도 있고 15분 초과인 경우도 있습니다. (정확한 타이밍은 OS 배터리 최적화에 따라 달라집니다.)
12:44:40 D/RefreshDataWorker: Work request for sync is run
12:44:40 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
12:59:24 D/RefreshDataWorker: Work request for sync is run
12:59:24 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:15:03 D/RefreshDataWorker: Work request for sync is run
13:15:03 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:29:22 D/RefreshDataWorker: Work request for sync is run
13:29:22 I/WM-WorkerWrapper: Worker result SUCCESS for Work 
13:44:26 D/RefreshDataWorker: Work request for sync is run
13:44:26 I/WM-WorkerWrapper: Worker result SUCCESS for Work
 

축하합니다. 작업자를 만들고 WorkManager로 작업 요청을 예약했습니다. 하지만 문제가 있습니다. 제약 조건을 지정하지 않았습니다. WorkManager는 기기의 배터리가 부족하거나, 절전 모드이거나, 네트워크 연결이 없더라도 하루에 한 번 작업을 예약합니다. 이는 기기 배터리와 성능에 영향을 미치며 사용자 환경이 저하될 수 있습니다.

다음 작업에서는 제약 조건을 추가하여 이 문제를 해결합니다.

이전 작업에서는 WorkManager를 사용하여 작업 요청을 예약했습니다. 이 작업에서는 작업을 실행할 시기에 관한 기준을 추가합니다.

WorkRequest를 정의할 때 Worker를 실행할 시간에 관한 제약 조건을 지정할 수 있습니다. 예를 들어 기기가 유휴 상태일 때만 또는 기기가 플러그에 연결되어 있고 Wi-Fi에 연결되어 있을 때만 작업을 실행하도록 지정할 수 있습니다. 작업 재시도를 위한 백오프 정책을 지정할 수도 있습니다. 지원되는 제약 조건Constraints.Builder의 설정 메서드입니다. 자세한 내용은 작업 요청 정의를 참고하세요.

1단계: Constraints 객체 추가 및 제약 조건 하나 설정

이 단계에서는 Constraints 객체를 만들고 객체에 네트워크 유형 제약 조건 하나를 설정합니다. (제약 조건이 하나만 있는 로그를 더 쉽게 확인할 수 있습니다. 나중에 다른 제약 조건을 추가합니다.)

  1. DevByteApplication 클래스의 setupRecurringWork() 시작 부분에 Constraints 유형의 val을 정의합니다. Constraints.Builder() 메서드를 사용합니다.
val constraints = Constraints.Builder()

이 오류를 해결하려면 androidx.work.Constraints를 가져오세요.

  1. setRequiredNetworkType() 메서드를 사용하여 constraints 객체에 네트워크 유형 제약 조건을 추가합니다. 기기가 무제한 네트워크에 연결된 경우에만 작업 요청이 실행되도록 UNMETERED 열거형을 사용합니다.
.setRequiredNetworkType(NetworkType.UNMETERED)
  1. build() 메서드를 사용하여 빌더에서 제약 조건을 생성합니다.
val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .build()

이제 새로 만든 Constraints 객체를 작업 요청에 설정해야 합니다.

  1. DevByteApplication 클래스의 setupRecurringWork() 메서드 내에서 Constraints 객체를 정기 작업 요청 repeatingRequest로 설정합니다. 제약 조건을 설정하려면 build() 메서드 호출 위에 setConstraints() 메서드를 추가합니다.
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

2단계: 앱을 실행하고 로그 확인

이 단계에서는 앱을 실행하고 제한된 작업 요청이 백그라운드에서 일정한 간격으로 실행되는 것을 확인합니다.

  1. 이전에 예약된 작업을 취소하려면 기기 또는 에뮬레이터에서 앱을 제거하세요.
  2. Android 스튜디오에서 Logcat 창을 엽니다. Logcat 창에서 왼쪽에 있는 Logcat 지우기 아이콘을 클릭하여 이전 로그를 지웁니다. work을 기준으로 필터링
  3. 제약 조건이 어떻게 작동하는지 확인할 수 있도록 기기나 에뮬레이터에서 Wi-Fi를 사용 중지합니다. 현재 코드는 요청이 무제한 네트워크에서만 실행되어야 함을 나타내는 제약 조건 하나만 설정합니다. Wi-Fi가 꺼져 있으므로 기기가 요금제 또는 무제한 네트워크에 연결되어 있지 않습니다. 따라서 이 제약 조건은 충족되지 않습니다.
  4. 앱을 실행하고 Logcat 창을 확인합니다. WorkManager는 백그라운드 작업을 즉시 예약합니다. 네트워크 제약 조건이 충족되지 않으므로 작업이 실행되지 않습니다.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
  1. 기기나 에뮬레이터에서 Wi-Fi를 사용 설정하고 Logcat 창을 확인합니다. 이제 네트워크 제약 조건이 충족되는 한 예약된 백그라운드 작업이 약 15분마다 실행됩니다.
11:31:44 D/DevByteApplication: Periodic Work request for sync is scheduled
11:31:47 D/RefreshDataWorker: Work request for sync is run
11:31:47 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]
11:46:45 D/RefreshDataWorker: Work request for sync is run
11:46:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:03:05 D/RefreshDataWorker: Work request for sync is run
12:03:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:16:45 D/RefreshDataWorker: Work request for sync is run
12:16:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:31:45 D/RefreshDataWorker: Work request for sync is run
12:31:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
12:47:05 D/RefreshDataWorker: Work request for sync is run
12:47:05 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...] 
13:01:45 D/RefreshDataWorker: Work request for sync is run
13:01:45 I/WM-WorkerWrapper: Worker result SUCCESS for Work [...]

3단계: 제약 조건 추가

이 단계에서는 PeriodicWorkRequest에 다음 제약 조건을 추가합니다.

  • 배터리가 부족하지 않습니다.
  • 기기 충전
  • 기기 유휴 상태입니다. API 수준 23 (Android M) 이상에서만 사용할 수 있습니다.

DevByteApplication 클래스에서 다음을 구현합니다.

  1. DevByteApplication 클래스의 setupRecurringWork() 메서드 내에서 배터리가 부족하지 않은 경우에만 작업 요청이 실행되어야 함을 나타냅니다. build() 메서드 호출 전에 제약 조건을 추가하고 setRequiresBatteryNotLow() 메서드를 사용합니다.
.setRequiresBatteryNotLow(true)
  1. 기기가 충전 중일 때만 실행되도록 작업 요청을 업데이트합니다. build() 메서드 호출 전에 제약 조건을 추가하고 setRequiresCharging() 메서드를 사용합니다.
.setRequiresCharging(true)
  1. 기기가 유휴 상태일 때만 실행되도록 작업 요청을 업데이트합니다. build() 메서드 호출 전에 제약 조건을 추가하고 setRequiresDeviceIdle() 메서드를 사용합니다. 이 제약 조건은 사용자가 기기를 적극적으로 사용하지 않는 경우에만 작업 요청을 실행합니다. 이 기능은 Android 6.0 (Marshmallow) 이상에서만 사용할 수 있으므로 SDK 버전 M 이상의 조건을 추가합니다.
.apply {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
       setRequiresDeviceIdle(true)
   }
}

constraints 객체의 전체 정의는 다음과 같습니다.

val constraints = Constraints.Builder()
       .setRequiredNetworkType(NetworkType.UNMETERED)
       .setRequiresBatteryNotLow(true)
       .setRequiresCharging(true)
       .apply {
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
               setRequiresDeviceIdle(true)
           }
       }
       .build()
  1. setupRecurringWork() 메서드 내에서 요청 간격을 하루에 한 번으로 다시 변경합니다.
val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
       .setConstraints(constraints)
       .build()

다음은 주기적 작업 요청이 예약된 시점을 추적할 수 있는 로그가 포함된 setupRecurringWork() 메서드의 전체 구현입니다.

private fun setupRecurringWork() {

       val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.UNMETERED)
               .setRequiresBatteryNotLow(true)
               .setRequiresCharging(true)
               .apply {
                   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                       setRequiresDeviceIdle(true)
                   }
               }
               .build()
       val repeatingRequest = PeriodicWorkRequestBuilder<RefreshDataWorker>(1, TimeUnit.DAYS)
               .setConstraints(constraints)
               .build()
       
       Timber.d("Periodic Work request for sync is scheduled")
       WorkManager.getInstance().enqueueUniquePeriodicWork(
               RefreshDataWorker.WORK_NAME,
               ExistingPeriodicWorkPolicy.KEEP,
               repeatingRequest)
   }
  1. 이전에 예약된 작업 요청을 삭제하려면 기기 또는 에뮬레이터에서 DevBytes 앱을 제거하세요.
  2. 앱을 실행하면 WorkManager가 즉시 작업 요청을 예약합니다. 작업 요청은 모든 제약 조건이 충족되면 하루에 한 번 실행됩니다.
  3. 이 작업 요청은 앱이 실행되지 않더라도 앱이 설치되어 있는 한 백그라운드에서 실행됩니다. 따라서 휴대전화에서 앱을 제거해야 합니다.

좋습니다. DevBytes 앱에서 동영상을 매일 사전 가져오기 위한 배터리 친화적인 작업 요청을 구현하고 예약했습니다. WorkManager는 시스템 리소스를 최적화하여 작업을 예약하고 실행합니다. 사용자와 배터리 모두 만족할 것입니다.

Android 스튜디오 프로젝트: DevBytesWorkManager

  • WorkManager API를 사용하면 안정적으로 실행해야 하는 지연 가능한 비동기 작업을 쉽게 예약할 수 있습니다.
  • 대부분의 실제 앱은 오래 걸리는 백그라운드 작업을 실행해야 합니다. 최적화되고 효율적인 방식으로 백그라운드 작업을 예약하려면 WorkManager를 사용하세요.
  • WorkManager 라이브러리의 기본 클래스는 Worker, WorkRequest, WorkManager입니다.
  • Worker 클래스는 작업 단위를 나타냅니다. 백그라운드 작업을 구현하려면 Worker 클래스를 확장하고 doWork() 메서드를 재정의합니다.
  • WorkRequest 클래스는 작업 단위를 실행하기 위한 요청을 나타냅니다. WorkRequestWorkManager에서 예약하는 작업의 매개변수를 지정하는 기본 클래스입니다.
  • WorkRequest 클래스에는 두 가지 구체적인 구현이 있습니다. 일회성 작업에는 OneTimeWorkRequest를 사용하고 주기적 작업 요청에는 PeriodicWorkRequest를 사용합니다.
  • WorkRequest를 정의할 때 Worker를 실행할 시점을 나타내는 Constraints를 지정할 수 있습니다. 제약 조건에는 기기가 연결되어 있는지, 기기가 유휴 상태인지, Wi-Fi가 연결되어 있는지 등이 포함됩니다.
  • WorkRequest에 제약 조건을 추가하려면 Constraints.Builder 문서에 나열된 set 메서드를 사용하세요. 예를 들어 기기 배터리가 부족한 경우 WorkRequest가 실행되지 않도록 하려면 setRequiresBatteryNotLow() 설정 메서드를 사용합니다.
  • WorkRequest를 정의한 후 작업을 Android 시스템에 전달합니다. 이렇게 하려면 WorkManager enqueue 메서드 중 하나를 사용하여 작업을 예약합니다.
  • Worker가 실행되는 정확한 시간은 WorkRequest에 사용된 제약 조건과 시스템 최적화에 따라 달라집니다. WorkManager은 이러한 제한사항을 고려하여 최상의 동작을 제공하도록 설계되었습니다.

Udacity 과정:

Android 개발자 문서:

기타:

이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 진행하는 학생에게 출제할 수 있는 과제가 나열되어 있습니다. 다음 작업은 강사가 결정합니다.

  • 필요한 경우 과제를 할당합니다.
  • 과제 제출 방법을 학생에게 알립니다.
  • 과제를 채점합니다.

강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 적절하다고 생각되는 다른 과제를 출제해도 됩니다.

이 Codelab을 직접 진행하는 경우 이러한 과제를 자유롭게 사용하여 배운 내용을 테스트해 보세요.

질문 1

WorkRequest 클래스의 구체적인 구현은 무엇인가요?

OneTimeWorkPeriodicRequest

OneTimeWorkRequestPeriodicWorkRequest

OneTimeWorkRequestRecurringWorkRequest

OneTimeOffWorkRequestRecurringWorkRequest

질문 2

다음 중 WorkManager가 API 23 이상에서 백그라운드 작업을 예약하는 데 사용하는 클래스는 무엇인가요?

JobScheduler만 해당

BroadcastReceiverAlarmManager

AlarmManagerJobScheduler

SchedulerBroadcastReceiver

질문 3

WorkRequest에 제약 조건을 추가하는 데 사용하는 API는 무엇인가요?

setConstraints()

addConstraints()

setConstraint()

addConstraintsToWorkRequest()

다음 강의 시작: 10.1 스타일 및 테마

이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.