안드로이드 WorkManager는 왜


Android Kotlin WorkManager는 어디에 사용되며 어떻게 사용되나요?

  • WorkManager가 필요한 이유는 무엇입니까?
  • 다른 백그라운드 프로세스와의 차이점
  • WorkManager는 어디에서 사용할 수 있나요?
  • 워크매니저 사용법

WorkManager는 Android의 Jetpack 백그라운드 처리를 지원하기 위해 Google에서 만든 요소입니다.

이것은 최신 기술이므로 많은 문제와 버그가 수정되었습니다.

현재 대부분의 Android 백그라운드 작업은 WorkManager로 수행할 수 있다고 가정하는 것이 안전합니다.


WorkManager가 필요한 이유는 무엇입니까?

먼저 Android에서 백그라운드로 이동한 앱

자동으로 꺼지는 이유 알 필요가있다


안드로이드 메모리 사용량

Android 커널은 Linux 커널을 기반으로 개발되었습니다.

가장 큰 차이점은 스왑 공간없다는 것을

스왑 공간이란 무엇입니까?

  • RAM이 가득 찼을 때 사용
  • 메모리의 비활성 데이터를 스왑 공간으로 이동
  • 스왑 공간은 RAM 용량 증가를 대신할 수 없습니다.

    • 액세스 시간이 느린 하드 드라이브에 스왑 공간이 상주하기 때문입니다.


Android(기본적으로)에는 스왑 공간이 없기 때문입니다.

OOM 킬러프로세스를 강제 종료합니다.

OOM 킬러는 보이는 상태그리고 소비된 메모리 양에 따라 프로세스를 종료하여

메모리를 해제하는 데 사용됩니다.


각 프로세스는 자신의 oom_adj 점수가 있습니다.

이 점수는 앱의 상태(포그라운드, 백그라운드, 서비스 등)에 따라 결정됩니다.

그리고 OOM 킬러는 다음 조건으로 프로세스를 종료합니다.

여유 공간이 x보다 작으면 y보다 큰 oom_adj로 프로세스를 정리합니다!

그러므로

oom_adj 값이 ​​높을수록 OOM 킬러로 더 쉽게 정리할 수 있습니다.

다시 말해서,

앱에서 사용하는 메모리가 적을수록 프로세스 종료 위험이 낮아집니다.


두 번째로 이해해야 할 것은

신청 상태에 대한 이해.

가장 중요한 것은 앱입니다 백그라운드에서 계속 진행하고 싶다면

서비스 구성 요소.사용되어야한다.

서비스란 무엇입니까?

사용자 인터페이스를 제공하지 않고 백그라운드에서 장기 실행 작업을 실행할 수 있는 앱 구성 요소입니다.

서비스를 이용하는 이유는 다음과 같습니다.

  1. 프로세스에 긴 작업이 있음을 시스템에 알립니다.

  2. 서비스 이용 시 합리적인 oom_adj 점수 부여
  3. 서비스를 사용하면 별도의 프로세스에서 작업을 수행할 수 있습니다.

  4. 서비스는 Android 앱의 네 가지 주요 구성 요소 중 하나입니다.

여기에 문제가 있습니다

빨리 “별도의 프로세스에서 작업 수행”하는 것입니다.

별도의 프로세스에서 작업을 수행한다는 것은 배터리 소모가 엄청납니다!
!
!

따라서 구글도즈 모드단계적으로 도입되고 추가 개발되었습니다.

(= 백그라운드 작업을 크게 제한)

Doze Mod 공식 설명 링크

https://developer.android.com/training/monitoring-device-state/doze-standby?hl=en

절전 모드 및 앱 대기에 최적화 | 안드로이드 개발자 | 안드로이드 개발자

앱에서 Android 6.0의 절전 기능을 테스트하고 최적화합니다.

developer.android.com

구체적으로 앱마켓에 등록할 수 있는 앱에 대해 다음과 같이 제한을 추가하였습니다.

  • 2018년 8월 : 새로 게시된 앱은 API 26(Oreo 8.0) 이상이어야 합니다.

  • 2018년 11월 : 기존 앱도 API 26(Oreo 8.0) 이상이어야 함
  • 2019년부터 : 매년 targetSdkVersion 요구 사항이 개선됩니다.

    매년 Android는 새 버전을 출시하며 모든 앱은 이 API 레벨 이상을 대상으로 해야 합니다.

그래서 교체용으로 나온 서비스 작업 API보지 못하다


서로 다른 백그라운드 프로세스의 차이점

작업 스케줄러 의 모습

ComponentName service = new ComponentName(this, MyJobService.class);
JobScheduler mJobScheduler = (JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(jobId, serviceComponent)
 .setRequiredNetworkType(jobInfoNetworkType)
 .setRequiresCharging(false)
 .setRequiresDeviceIdle(false)
 .setExtras(extras).build();
mJobScheduler.schedule(jobInfo);

작업 스케줄러시작된 작업을 예약합니다.

적시에 시스템 MyJobService그리고 발사 onStartJob() 안에 있는 것을 실행합니다.

하지만 작업 스케줄러API21 이상에서만 사용할 수 있으며

그 후 버그가 너무 많습니다.

작업 디스패처로 대체된다

하지만 작업 디스패처 또한 제대로 작동하지 않는 장치가 있기 때문에

~ 후에 JobIntentService로 대체된다

하지만 JobIntentService 또한 원하는 시간에 정확히 작동하지 않는 문제가 있었기 때문에

작업 관리자나타날거야.


WorkManager의 등장

위 내용을 보면 개발자는 안드로이드 버전과 휴대폰 기종 등입니다.

여러 가지를 고려하면 백그라운드 서비스를 만드는 문제에 직면하게 될 것입니다.

이러한 문제를 해결하기 위해 수행된 작업 작업 관리자보지마.

작업 관리자다음과 같은 속성이 있습니다.

  • 구현을 단순화하기 위해 시스템 기반 백그라운드 처리 API를 제공합니다.

    • 이 API를 사용하면 앱이 포그라운드에서 실행되지 않는 경우에도 백그라운드에서 작업을 실행할 수 있습니다.

  • 이를 통해 개발자는 백그라운드 처리에 대해 걱정할 필요가 없습니다.

  • 따라서 대부분의 Service, JobIntentService 등은 서비스 기능 교환 가능하다
  • 단기 및 장기 작업 모두 가능합니다.

  • 네트워크 상태, 배터리 상태 등에 의해 트리거될 수 있습니다.

  • 전경에서도 사용 가능
  • FCM과 함께 사용할 수 있습니다.

작업 관리자다음과 같은 구성 요소가 있습니다


WorkManager 구성

  • 작업 관리자
    • 처리할 작업을 자체 대기열에 배치 및 관리
    • 싱글톤으로 구현되었으므로 getInstance()를 사용하여 WorkManager의 인스턴스를 가져옵니다.

  • 노동자
    • 추상 클래스이며 처리할 백그라운드 작업의 처리 코드는 이 클래스를 상속하고 doWork() 메서드를 재정의하여 작성합니다.

      • 일하다()
        • 작업 완료 결과에 따라 작업자 클래스에 정의된 열거형 결과다음 값 중 하나를 반환해야 합니다.

        • SUCCESS, FAILURE, RETRY의 세 가지 값이 있으며 반환된 값에 따라 WorkManager는 작업을 완료할지, 재시도할지 또는 오류로 정의하고 중지할지를 결정합니다.

  • 작업 요청
    • WorkManager를 통해 실제로 요청되는 개별 작업입니다.

    • 여기에는 다음과 같은 이 작업을 처리하는 방법에 대한 정보가 포함되어 있습니다.

      예를 들어, 수행할 작업, 작업 반복 여부, 작업의 실행 조건 및 제한 사항입니다.

    • 반복 여부에 따라 onTimeWorkRequest와 PeriodicWorkRequest로 나뉩니다.

    • onTimeWorkRequest
      • 반복되지 않는 작업, 즉 한 번만 수행되는 작업에 대한 요청을 나타내는 클래스입니다.

    • 정기 작업 요청
      • 작업을 여러 번 실행해야 하는 요구 사항을 나타내는 클래스입니다.

  • 작업 조건
    • WorkRequest의 ID와 해당 WorkRequest의 현재 상태를 포함하는 클래스입니다.

    • 이 WorkState의 상태 정보를 사용하여 요청한 작업의 현재 상태를 확인할 수 있습니다.

    • 6가지 상태: ENQUEUED, RUNNING, SUCCEEDED, FAILED, BLOCKED, CANCLLED

WorkManager 사용

작업자 정의

무언가를 업로드하는 작업자가 있다고 가정합니다.

작업에서 수행할 모든 작업 일하다() 그것에 정의

class UploadWorker(context:Context, params:WorkerParameters):Worker(context, params) {
    companion object{
        const val KEY_WORKER = "key_worker"
    }
    // Worker에서 실시할 작업을 doWork() 안에서 정의
    override fun doWork(): Result {
        try{
            // 넘겨 받은 데이터를 가져온다
            val count = inputData.getInt(MainActivity.KEY_COUNT_VALUE, 0)
            for (i in 0 until count){
                Log.d("UploadWorker", "Uploading $i")
            }

            val time = SimpleDateFormat("dd/M/yyyy hh:mm:ss")
            val currentData = time.format(Date())

            // worker가 끝난 이후 반환할 데이터를 정의
            val outPutData = Data.Builder()
                .putString(KEY_WORKER, currentData)
                .build()

            // 데이터를 반환함과 동시에 success 처리
            return Result.success(outPutData)
        }catch (e:Exception){
            // worker 결과를 fail 처리
            return Result.failure()
        }
    }
}

MainActivity에서 WorkManager를 정의하고 Work를 실행합니다.

노동자

한 번만 사용된 OneTimeWorkRequest

지정된 간격으로 반복되는 PeriodicWorkRequest

두 종류가 있습니다.

다음을 수행할 수도 있습니다.

  • Bundle과 유사한 방식으로 원하는 데이터를 작업자에게 전달할 수 있습니다.

  • 작업이 끝나면 결과와 결과 값을 함께 반환할 수 있습니다.

  • Worker와 liveData를 결합하여 Work의 결과를 관찰할 수 있습니다.

  • 여러 작업을 연결하여 순차적으로 실행하거나 그룹화하여 동시에 처리할 수 있습니다.

class MainActivity : AppCompatActivity() {
    companion object{
        val KEY_COUNT_VALUE = "key_count"
    }
    private lateinit var binding : ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        // 일반적인 Worker 실행
        binding.button1.setOnClickListener {
            setOnOneTimeWorkRequest()
        }
        // 정해진 기간마다 반복하는 Worker 실행
        binding.button2.setOnClickListener {
            setPeriodicWorkRequest()
        }
    }

    private fun setOnOneTimeWorkRequest(){
        val workManager = WorkManager.getInstance(applicationContext)
        // Worker에서 사용할 수 있는 데이터를 넣을 수 있음
        val data = Data.Builder().putInt(KEY_COUNT_VALUE, 125)
            .build()

        // Constraints.Builder()를 통해 실행 제약을 설정할 수 있음
        val constraints = Constraints.Builder()
            // 네트워크가 연결되어있는 상태일 때
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()

        // OneTimeWorkRequest 생성
        val uploadRequest = OneTimeWorkRequest.Builder(UploadWorker::class.java)
            // 정의한 제약을 설정
            .setConstraints(constraints)
            // 사용할 데이터를 넣는다
            .setInputData(data)
            .build()

        // 다수의 OneTimeWorkRequest를 생성
        val filteringRequest = OneTimeWorkRequest.Builder(FilteringWorker::class.java)
            .build()

        val compressingRequest = OneTimeWorkRequest.Builder(CompressingWorker::class.java)
            .build()

        val downloadingWorker = OneTimeWorkRequest.Builder(DownloadingWorker::class.java)
            .build()

        // list로 묶거나
        val paralleWorks = mutableListOf<OneTimeWorkRequest>()
        paralleWorks.add(downloadingWorker)
        paralleWorks.add(filteringRequest)

        // then을 사용해서 하나씩 선형으로 실행하게 가능함
        workManager.beginWith(paralleWorks).then(compressingRequest).then(uploadRequest)
            .enqueue()  // enqueue로 Work를 실행

        // workManager와 liveData를 묶어서 worker의 상태를 observe로 확인 가능
        workManager.getWorkInfoByIdLiveData(uploadRequest.id)
            .observe(this, Observer {
                binding.textView.text = it.state.name
                // work의 처리가 끝났을 때
                if (it.state.isFinished){
                    // outputData로 worker가 끝난 이후 결과를 받을 수 있음
                    // 단, 해당 worker에서 Result에 값을 넣어서 반환해야함
                    val data = it.outputData
                    val message = data.getString(UploadWorker.KEY_WORKER)
                    Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT).show()
                }
            })
    }

    // 설정한 주기로 반복하는 worker 정의
    private fun setPeriodicWorkRequest(){
        val periodicWorkRequest = PeriodicWorkRequest
            .Builder(DownloadingWorker::class.java, 15, TimeUnit.MINUTES)
            .build()
        WorkManager.getInstance(applicationContext).enqueue(periodicWorkRequest)
    }
}