Android Background 처리와 WorkManager

hjseo-dev·2022년 9월 14일
1

Android

목록 보기
18/18

📍 Thread vs Service


Q. Service 내에서 Thread를 사용해야 한다면 그냥 일반적인 코드에서 Thread를 사용하는 것이 무엇이 다를까?

Thread의 문제: 안드로이드 컴포넌트가 아니므로 독자적인 생명주기도 없고 Main Thread가 아니기 때문에 앱을 나가면 프로세스가 유지되지 않는다. 만약에 OOM Killer(메모리 부족에 의한 특정 프로세스 종료)에 의해서 프로세스가 종료되면 다시 재시작 될 것이라는 보장도 없다.

Service 내부 동작: 안드로이드 4대 컴포넌트 중 하나로서 독자적인 생명주기를 가지고 있고 Main Thread에서 동작하기 때문에 사용자가 앱을 나가도 프로세스가 유지된다. 만약 강제로 프로세스가 죽을 경우 다시 살아날 수도 있다.

📍 Service

  • 백그라운드 작업에 사용, ui thread에서 사용하나 앱이 느려지므로 별도의 스레드 처리 필요

  • 시작: startService, 종료: stopService, 실행 중 종료 : stopSelf
    ex) 음악재생, 파일 입출력, 네트워크 트랜잭션.. etc

  • 백그라운드, 바인더, 포그라운드 3가지 유형

  • 서비스는 잘못 사용할 확률이 높아 스레드 처리를 직접하지 않기위해
    IntentService가 등장

  • Thread 처리할 때 동시에는 안되며 하나씩 작업

  • API 30 에서 부터 IntentService가 deprecated되어 JobIntentService로 대체됨

📍 JobIntentService

  • IntentService와 동작은 비슷하나 코드가 다름
  • 실행 : enqueueWork(), 안드로이드 오레오 8.0 이상에서 동작하고 그 이하에서는 startService()로 동작, onHandleWork()에서 동작 처리
  • 제한시간 10분으로 강제종료 되며 그 안에 끝나는 서비스에만 사용하도록 해야함
  • BIND_JOB_SERVICE 권한 필수

📍 WorkManager

  • JetPack에 포함되며 서비스로 할수 있는 것 대체 가능
  • 오래걸리는 작업은 Worker로 구현
  • WorkRequest는 한번수행 or 반복수행 있음
    (네트워크, 배터리 상태 변경시 트리거 가능)
  • WAKE_LOCK 관리하여 권한 추가 가능 > 오레오 이상에서는 가능, 이전에는 원래 쓰던 것으로 관리
  • foreground 서비스로 동작 가능

언제 WorkManager를 사용하는가?

  • 백그라운드에서 작업을 실행하면 RAM 및 배터리와 같은 제한된 리소스가 소비됨에 따라서 사용자 환경이 저하될 수 있다

  • WorkManager 는 모든 OS 백그라운드 실행 제한을 고려하여 백그라운드 실행에 권장되는 솔루션이다

사용 예시

  1. 지연가능한 작업의 보장된 실행 : WorkManager
    서버에 로그 업로드하는 경우
    업로드 / 다운로드 할 콘텐츠 암호화 / 복호화
  2. 외부 이벤트에 대한 응답으로 시작된 작업 : FCM + WorkManager
    이메일과 같은 새로운 온라인 컨텐츠 동기화
    Firebase Cloud Messaging을 사용해서 앱에 알리고
    WorkManager로 작업 요청을 생성해 콘텐츠를 동기화 한다
    모든 작업을 WorkManager를 사용하는 것은 올바른 사용 방법이 아니다

사용자가 현재 보고있는 UI를 빠르게 변경하는 작업이나 결제 진행 등 즉시 처리해야 하는 작업은 ForgroundService를 사용하거나 ThreadPool, Rx등을 사용

참조 : Modern background execution in Android

💡 예시

  • 구현 순서

WorkManager 클래스 생성 -> WorkManager Request 생성 -> Equeue Request (워크매니저 실행 요청) -> 상태 업데이트 얻기(작업 대기 및 수신)

WorkManager를 이용해 작업을 등록하고 실행하려면 아래의 클래스를 이용합니다.

  • Worker: 작업 내용을 가지는 추상 클래스
  • WorkRequest: 작업 의뢰 내용으로 이를 상속한
  • OneTimeWorkRequest, PeriodicWorkRequest 두 개의 클래스를 사용
  • Constraints: WorkRequst의 제약 조건 명시

Worker 클래스를 생성한다
doWork() 매서드로 백그라운드 스레드에서 동기식으로 실행됨

class SimpleWorker : Worker() {
  override fun doWork(): WorkerResult {
  // 처리해야할 작업 명시 
  //return은 SUCCESS, FAILURE, RETRY가 있다. 
    return WorkerResult.SUCCESS
  }
}

경우에 따라 작업을 수행하는 코드를 작성..

1. 한번 수행, 여러번 반복하는 경우

// 한번만 수행
val workRequest = OneTimeWorkRequestBuilder<SimpleWorker>().build()
//15분마다 작업 실행 
val workRequest = PeriodicWorkRequestBuilder<SimpleWorker>(15, TimeUnit.MINUTES).build()
val workManager = WorkManager.getInstance()
workManager?.enqueue(workRequest)

2. 특정 제약조건을 충족해야 실행되도록 할 경우

constraints로 표시한다

//배터리 충전중일때 실행(예시)
val constraints = Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
								.build()

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

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

3. 연결된 작업을 실행할 경우

A 작업 이후 B 작업을 해야한다면, beginWith과 then 사용하여 표시함

val workA = OneTimeWorkRequestBuilder<AWorker>().build()
val workB = OneTimeWorkRequestBuilder<BWorker>().build()

WorkManager.getInstance()?.apply{
		beginWith(workA).then(workB).enqueue() 
}

더 자세한 정보 참고
https://dongsik93.github.io/til/2020/05/15/til-jetpack-workmanager/

ex) FCM 알람

WorkerParameters > getExtra처럼 데이터를 전달 받는데 사용

class ScheduledWorker(appContext: Context, workerParams: WorkerParameters) :
    Worker(appContext, workerParams) {

    override fun doWork(): Result {
        // Get Notification Data
        val title = inputData.getString(EXTRA_TITLE).toString()
        val message = inputData.getString(EXTRA_BODY).toString()
        // FCM 전송(FCM 전송하는 Utils 클래스 생성)
        NotificationUtil(applicationContext).showNotification(
            title,
            message
        )
        //성공
        return Result.success()
    }
}

foreground, background, doze mode일 경우 모두 10분전에 알람 받을 수 있도록 설정

private fun workManagerAlarm(
        scheduledTime : String, title : String, body : String
    ) {
    	//workMAnager 에 넘겨줄 파라미터 값 
        val data =
            workDataOf(
                EXTRA_BODY to body, EXTRA_TITLE to title
            )
		//지정한 시간의(schedudleTIme) 10분 이전의 타임스탬프 계산
        val timeDiff =
            scheduledTime.toLong() - System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(10)
        //파라미터와 지연 시간 설정
        val workRequest = OneTimeWorkRequestBuilder<ScheduledWorker>().setInputData(data)
            .setInitialDelay(timeDiff, TimeUnit.MILLISECONDS).build()
        //워크매니저 생성
        val workManager = WorkManager.getInstance(applicationContext)
        //워크매니저 실행
        workManager.enqueue(workRequest)
    }]

📍 백그라운드 처리

  • 즉각적인 실행이 필요한가? ex) 누르면 바로 동작
    (코틀린 coroutine 사용 / 자바 API 30 excuterService, WorkManager 등 사용)
    정확한 시간에 해야하면 AlarmManager 사용, 잠자기 모드 해제 가능
  • 여유를 줄 수 있는가? ex) 파일 다운시.. 지연이 필요 / WorkManager 사용 가능

💡 결론

서비스 직접 실행은 되도록 피해야하며, JobIntentService를 사용 가능함 (10분 지연)
이외 WorkManager 로 사용 -> 간단하고 짧은 작업 / 긴 작업(foreground로) 등 사용

  • 앱에 오랜 시간 표시해야 할 경우..
    workManager에 foreground로는 무리가 있어 따로 서비스를 구현해야한다

이외의 Background Task

  • 만일 사용자가 보고 있는 빠르게 변경하는 작업이나 즉시 처리해야 하는 작업이라면 WorkManager의 사용 대신 Foreground Service를 사용하는 것이 좋습니다.

또한 WorkManager은 지연이 될 수 있기 때문에, 정확한 시간에 동작해야하는 작업은 AlarmManager을 사용하는 것이 좋습니다.

버전 별 이슈

  • Android 12의 Foreground Service 실행 제한이 생김. 따라서 Foreground Service의 권장 대안으로 WorkManager을 제안한다.
  • WorkManager 2.7.0 부터 앱은 setEcpedited() 메서드를 활용해 가능하며 이 새로운 API는 Android 12에서 실행되면 신속 처리 작업을 사용하고, 이전 버전에서는 Foreground Service를 이용하여 이를 처리하기 때문에 이전 버전과의 호환성을 제공한다.

관련 내용 : https://developer.android.com/topic/libraries/architecture/workmanager/how-to/define-work#expedited

출처 : youtube.com/watch?v=ZqhrZ8_3jlg&t=125
https://youngest-programming.tistory.com/361

0개의 댓글