Android/JetPack

Jetpack - Workmanager (1)

봄석 2019. 5. 6. 14:16

 

 

Jetpack - WorkManager

jetpack의 Architecture의 일부인 Workmanager에 대하여 알아보도록 하겠습니다.

 

안드로이드 초기에는 백그라운드 작업이 수월했었습니다.

서비스 하나를 생성하여 쉽게 사용자 뒤에서 백그라운드 작업을 수행할 수 있었습니다.

브로드캐스트 리시버를 사용하여 개발자가 원하는 시점에 앱을 깨워서 작업할 수도 있었습니다.

 

workmanager 이전의 안드로이드 백그라운드 작업 자세히 알아보기 - https://beomseok95.tistory.com/192

 

Workmanager 이전의 안드로이드 백그라운드작업

Workmanager 이전의 안드로이드 백그라운드작업 Jetpack의 workmanager에대하여 알아보기 전에 이전의 안드로이드 백그라운드작업을 어떻게 처리했었는지 알아보도록 하겠습니다 . 1.android K (킷켓,API 19)이전..

beomseok95.tistory.com

하지만 안드로이드 버전이 올라갈수록 그 제약조건이 까다로워지기 시작했습니다.

 

오레오버전 - Android 8.0 ( API 26 ) 에서는 아래와 같은 제약이 생겼습니다

 

  • Background Service 제약 : App 이 background 일 때 foreground service 가 아니면 background service 를 쓸 수 없다.
  • Broadcast 제약 : 매니페스트에 등록한 암시적 broadcast 를 받을 수 없다. ( 약간의 예외는 있다. )

오레오 버전으로 타겟팅된 앱이 백그라운드에서 startService() 메서드 호출하면 백그라운드 서비스 제한이 있어서 exception이 발생합니다. 

 

위 같은 제약은 오레오 버전 이상일때 만 생기는 제약조건이라 타겟 API를 그아래 수준으로 내리는 방법도있습니다 . 하지만 

 

구글의 정책내용을 보자면

2018/8   새로 출시되는 앱은 api 26을 반드시 Target 적용

2018/11  기존 앱도 api 26 이상을 반드시 적용


같은 정책이 세워진만큼 위같은 제약조건을 피할수만은 없는 상황입니다.

 

 

Workmanager 를 쓰면 좋은 상황 

이 작업이 앱의 종료 여부와 상관없이 수행되어야 하는 작업, 즉 앱의 프로세스 수명과 별도로 살아남기 위한 작업에 사용하는것을 추천합니다.

 

예를 들어 이미지를 서버에 업로드 해야 하거나, 데이터를 분석하고 이를 데이터베이스에 저장해야 하는 작업에는 WorkManager 를 사용하는것이 좋습니다.

 

그러나 사용자가 현재 보고있는 UI 를 빠르게 변경해야 하는 작업이나 물건 구입 과정에서의 결제 진행 등 즉시 처리해야 하는 작업은 WorkManager 를 사용하지 않는것이 좋습니다.

 

WorkManager 의 작업은 반드시 실행되지만 그 처리가 상황에 따라 지연 되거나 도중에 중단될 경우 다시 실행 될수 있다는 것을 꼭 기억해야 합니다.

적절한 상황에서, WorkManager 는 AlarmManager 나 JobScheduler, JobDispather 를 대체하는 훌륭한 백그라운드 작업 처리 방법입니다

 

 

Workmanager 구성

WorkManager API 의 주요 클래스는 WorkManager, Worker, WorkRequest, WorkState 입니다.

  • WorkManager : 처리해야 하는 작업을 자신의 큐에 넣고 관리합니다. 싱글톤 인스턴트로 사용 하기 위해서 내부에 WorkManager 객체를 반환하는 getInstance() 함수가 있습니다. 이 메서드를 통해서 WorkManager 의 인스턴트를 받아 사용합니다.

  • Worker : 추상 클래스 입니다. 처리해야 하는 백그라운드 작업의 처리 코드를 이 클래스를 상속받아 doWork() 메서드를 오버라이드 하여 작성하게 됩니다. doWork() 메서드는 작업을 완료하고 결과에 따라 Worker 클래스 내에 정의된 enum 인 Result 의 값중 하나를 리턴해야 합니다. SUCCESS, FAILURE, RETRY 의 3개 값이 있으며 리턴되는 값에 따라 WorkerManager 는 해당 작업을 마무리 할것인지 재시도 할것인지, 실패로 정의하고 중단할것인지 이후 동작을 결정하게 됩니다.

  • WorkRequest : WorkManager 를 통해 실제 요청하게될 개별 작업입니다. WorkRequest 는 처리해야 할 작업인 Worker 와 작업 반복 여부 및 작업 실행 조건, 제약 사항 등 이 작업을 어떻게 처리할 것인지에 대한 정보가 담겨 있습니다. WorkRequest 는 반복 여부에 따라 다음의 두가지로 나뉘어 집니다.

  • OneTimeWorkRequest : 반복하지 않을 작업, 즉 한번만 실행할 작업의 요청을 나타내는 클래스 입니다.

  • PeriodicWorkRequest : 여러번 실행할 작업의 요청을 나타내는 클래스 입니다.

  • WorkState : WorkRequest 의 id 와, 해당 WorkRequest 의 현재 상태를 담는 클래스입니다. 개발자는 WorkState 의 상태 정보를 이용해서 자신이 요청한 작업의 현재 상태를 파악할수 있습니다. WorkState 는 ENQUEUED, RUNNING, SUCCEEDED, FAILED, BLOCKED, CANCELLED 의 6개의 상태를 가집니다.

WorkManager 사용을 위한 의존성 추가

프로젝트에 WorkManager 를 사용하기 위한 의존성 부터 추가합니다.

repositories {
        google()
        jcenter()
    }
dependencies {
	 ext.kotlin_version = "1.0.1"

    // 필수1 , java 를 사용한다면 다음의 줄을 추가 하세요
    implementation "android.arch.work:work-runtime:$work_version"
    
    // 필수2 , kotlin 을 사용한다면 다음의 줄을 추가 하세요
    implementation "android.arch.work:work-runtime-ktx:$work_version"

    // 옵션 , Firebase JobDispatcher 를 사용하려면 다음의 줄을 추가 하세요
    implementation "android.arch.work:work-firebase:$work_version"
}

19.5.6 최신버전은 1.0.1 입니다.

 

 

1) work 작성 

class SimpleWorker(context: Context, workerParameters: WorkerParameters) : Worker(context,workerParameters) {
    override fun doWork(): Result {
        ... // TO DO SOMETING
        return Result.success()
    }
}

 

Worker 클래스를 상속받은 클래스를 만들고 doWork() 메서드를 오버라이드 합니다. 처리 결과에 따른 Result 값을 리턴해야 합니다.

2) Request 작성

val workRequest = OneTimeWorkRequestBuilder<SimpleWorker>().build()

val workManager = WorkManager.getInstance()
        
workManager?.enqueue(workRequest)

OneTimeWorkRequestBuilder를 이용하여 한번 실행되는 작업을 생성합니다.

그리고 WorkManager 클래스의 getInstance() 메서드로 싱글턴 객체를 받아서 WorkManager 의 작업 큐에 OneTimeWorkRequest 객체를 추가 하면 끝입니다.

 

3) 반복 Request 작성

val workRequest = PeriodicWorkRequestBuilder<SimpleWorker>(15, TimeUnit.MINUTES).build()

val workManager = WorkManager.getInstance()

workManager?.enqueue(workRequest)

 

반복되는 작업은 PeriodicWorkRequestBuilder 를 이용하여 PeriodicWorkRequest 객체를 생성하여 WorkManager 의 큐에 추가 하면 됩니다. 이때 첫번째 인자로는 반복될 인터벌 값, 두번째 인자로는 이 인터벌의 시간타입이 필수로 들어가야 합니다. 다음과 같은 예시는 15분 반복을 뜻하며 TimeUnit 에정의된 다른 시간 타입 enum 을 사용할수도 있습니다.

 

반복 시간에 사용할수 있는 가장 짧은 최소값은 PeiodicWorkRequest 클래스 내의 MIN_PERIODIC_INTERVAL_MILLIS 상수로 정의되어 있으며 15분 보다 짧은 시간은 사용할수 없습니다.

 

 

제약 조건을 가지는 작업 만들기

WorkManager 는 제약 조건을 추가하여 작업을 실행할수 있습니다. 예를 들어내 작업이 네트워크가 연결된 환경에서만 수행될수 있는 작업이라면 이 조건을 추가하여 네트워크가 연결됐을때는 작업 처리를 수행하도록 할수 있습니다. 제약 조건은 다양하게 추가할수 있습니다.

해당 제약조건이 만족되면 작업을 수행하고, 조건이 만족되지 않으면 작업을 취소하며, 처리가 완료되지 못하고 실패 하였다면 제약조건이 만족되는 다음 타이밍에 다시 처리를 시도합니다.

 

제약조건은 Constraints 클래스의 Builder 를 이용하여 생성하여 WorkRequest 에 추가합니다. 이제 WorkRequest 는 처리해야 하는 작업과 함께 제약 조건의 정보를 가지게 됩니다. 이를 WorkManager 의 큐에 추가 하면 WorkManager 는 이제 제약 조건을 확인 하여 적당한 시기에 작업을 처리하게 됩니다.

 

// 네트워크 연결 상태 와 충전 중 인 상태를 제약조건으로 작업생성하기
val constraints = Constraints.Builder()
             .setRequiredNetworkType(NetworkType.CONNECTED)
             .setRequiresCharging(true)
             .build()

val requestConstraint  = OneTimeWorkRequestBuilder<SimpleWorker>()
             .setConstraints(constraints)
             .build()

val workManager = WorkManager.getInstance()

workManager?.enqueue(requestConstraint)

 

 

작업체인  -  작업 연결하기

workmanager 는 여러가지 작업을 서로 연결하여 순서대로 실행할 수 있습니다 . 마치 RxJava를 쓰는것 처럼 말이죠 (RxJava와 같이 쓰는 방법도 있습니다)

val work1 = OneTimeWorkRequestBuilder<SimpleWork1>().build()
val work2 = OneTimeWorkRequestBuilder<SimpleWork2>().build()

WorkManager.getInstance()?.apply {
        beginWith(work1).then(work2).enqueue()
}

위처럼 두가지 작업이 있을 때 ,

WorkManager 는 work1 를 적절한 타이밍에 수행하고, work1 가 완료 된 이후 work2 를 작업 수행합니다.

 

 

// 3개의 처음 작업
val beginWork1 = OneTimeWorkRequestBuilder<BeginWork>().build()
val beginWork2 = OneTimeWorkRequestBuilder<BeginWork>().build()
val beginWork3 = OneTimeWorkRequestBuilder<BeginWork>().build()

val work1 = OneTimeWorkRequestBuilder<Work1>().build()
val work2 = OneTimeWorkRequestBuilder<work2>().build()

WorkManager.getInstance()?.apply {
         beginWith(beginWork1, beginWork2, beginWork3)
         .then(work1)
         .then(work2)
         .enqueue()
}

WorkManager 의 beginWith() 메서드에 추가된 세가지의 작업이 완료되는 종료시점이 모두 다르더라도

, work1은 세개의 작업이 모두 완료 된 이후에 시작되게됩니다.