본문으로 바로가기
반응형

L OS부터 다소 tight하게 background 작업을 제한하기 시작했습니다.

N OS에는 더욱 심해 졌고, O OS에서도 더욱 심해지네요.


휴대폰의 성능을 올리고, 배터리 절감 효과를 위해서 background service의 동작을 제한하는 부분들이 점점 강한 압박이 되어 돌아오고 있습니다.

O OS에서는 background App이 background service를 수행하지 못하도록 막습니다.

좌절이죠..

그리고 Foreground service나 JobScheduler를 이용하여 해당 동작을 대체하도록 가이드 하고 있습니다.


Overview

JobSchedular는 Job이 완료되는것을 보장합니다.
다만 system level에서 동작하기 때문에 다른 앱의로 인하여 background 작업을 수행 할 때 좀더 intelligent 하게 동작하도록 도와줍니다.
똑똑하다는 의미는 Trigger의 조건을 network, battery 기반으로 한다는 의미이며 API24부터는 메모리도 조건의 고려대상으로 한다고 하네요.

예를 들자면 network이 무료이거나, 충전이 꼽혀있거나, 단말이 idle 상태일때 background 작업을 돌도록 유도할 수 있습니다.

JobSchedular의 목적은 시스템 레벨에서 JobScheduler를 도입함으로써 유사한 작업 요청을 일괄 적으로 같이 처리하는 데 집중할 수 있으므로 배터리와 메모리 모두에서 눈에 띄게 개선됩니다. (요건..가이드에 나온 말입니다.)


1.background로 동작할 job을 구성.

JobService class를 상속받아서 구성하며 실제로 Service가 됩니다.
각각 동작해야할 내용에 따라, 조건에 따라 여러개를 만들어서 등록시킬 수 있으며, 따라서 app의 architecture에 따라 각각을 분리하여 만든다면
구조적으로나, 관리면에서 좋아질 수 있습니다.


Override가 필요한 methods

- onStartJob(): Job이 수행될때 불리는 callback 함수

  case1: 간단한 작업인 경우 함수 내부에 로직을 직접 구현하고 false를 return합니다.
          이는 system에게 모든 작업이 끝났음을 알립니다.
  case2: 복잡하거나 network, IO같이 background thread를 띄우는 경우 true를 return 시켜야 합니다.
         이는 system에게 thread가 아직 돌고 있으며, wakelock을 더 길데 잡고있어야 함을 알려주는 역할을 합니다.
         
 이 함수는 main thread에서 수행되므로 필요한 경우 background thread를 따로 띄워서 사용해야 합니다!!!

@Override
public boolean onStartJob(final JobParameters params) {
  mDownloadArtworkTask = new DownloadArtworkTask(this) {
    @Override
    protected void onPostExecute(Boolean success) {
      jobFinished(params, !success);
    }
  };
  mDownloadArtworkTask.execute();
  return true;
}

jobFinished()은 override 할수 없으며 system이 호출하지 않습니다.

이 함수는 return true 경우 실제 system이 언제 wakelock을 풀어야 할지를 알려줍니다.

만약에 호출하지 않는다면, 배터리를 잡아먹는 앱으로 간주되겠죠..


추가적으로 jobFinished()은 두개의 param을 받습니다.

wakelock을 풀어야하는 job(현재 자신)과, retry 여부 입니다.

따라서 true를 넣으면 backoff algorithm에 따라서 job이 재 실행 됩니다.

작업이 완료되었으면 false를 명시적으로 넘겨줘야 겠죠~


- onStopJob(): Job이 완료되기전에 취소된 경우 불리는 callback 함수

이 함수는 동작중에 trigger 조건이 바뀌는 경우 발생합니다.

예를 들면 trigger 조건이 무료 네트워크와 충전중인 경우를 만족해서 Job이 수행되었으나, Job이 완료되기전에 network이 cellular로 바뀐다거나

충전 케이블이 뽑힌다거나 하는경우 이죠.


이때 return true를 하면, 해당 작업은 rescheduling 됩니다. 추후 재동작이 필요없다면 false를 return하면 됩니다.

사실 이 동작은 매우 critical 합니다. 대용량으로 파일을 다운로드 받거나, 수백의 contact을 싱크하는중에 발생한다면, 복구를 해야할지..이어하기를 해야할지를 판단해야하며, 그에 따른 로직도 추가적으로 들어가야합니다.

이 로직이 쉬운 로직은 아니겠죠??

따라서 가이드 되기로는 가능한 작은 단위로 작업을 수행해서 나중에 이어받기나 이어하기가 가능하도록 구현하라고 합니다.

JobSchedular의 엄청난..단점이네요.

@Override
public boolean onStopJob(final JobParameters params) {
  if (mDownloadArtworkTask != null) {
    mDownloadArtworkTask.cancel(true);
  }
  return true;
}


일단 이렇게 service를 구현했다면, AndroidManifest.xml에 등록시켜줘야 합니다.

<service
  android:name=".sync.DownloadArtworkJobService"
  android:permission="android.permission.BIND_JOB_SERVICE"
  android:exported="true"/>

permission 정보가 반드시 들어가야 합니다.


2. 동작을 trigger할 condition을 설정

JobScheduler의 장점은 시간을 기준으로 동작하는게 아니라 조건을 기반으로 동작한다는 점입니다.

이런 조건들은 JobInfo를 이용하여 등록할 수 있습니다.

JobInfo를 만들려면 job을 구분할 수 있는 ID와 JobService 두가지값을 필수로 넣어줘야 합니다.

Job의 내용이 간단하더라도, 네트워크가 필요한 경우에 다른 job과 동시에 모아서 작업이 수행된다는 점과, 성공과 실패 여부에 따라 retry를 간단히

수행할 수 있다는 점은 JobScheduler를 사용하는데 장점으로 볼 수 있다고 합니다만, 개인적으로는 동시에 작업이 일어나는 경우가 장점일지는 와닿지 않는군요.

JobScheduler jobScheduler =
    (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(new JobInfo.Builder(LOAD_ARTWORK_JOB_ID,
    new ComponentName(this, DownloadArtworkJobService.class))
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .build());


조건으로 줄수 있는 다른 값들은 아래와 같습니다.


- Network type (metered/unmetered)

  유료/무료 네트워크에서 동작하도록 구분지어 조건을 걸 수 있습니다.

  하지만 network과 상관없이 동작하는 경우나 local에서 일어나는 작업인 경우, 조건에 추가할 필요가 없겠죠?

  

- Charging and Idle

  충전중이거나, idle일때 동작하도록 조건을 겁니다.

  이는 resource를 많이 사용해야 하는경우에 추천되는 조건입니다. 여기서 idle이란 doze mode의 idle은 아니고 screen off 정도의 idle 상태를 말합니다.

  

- Content Provider update

  원하는 특정 URI에 따라 trigger 되도록 할수도 있습니다.

  아쉽게도 API24 이상입니다만, contact이나 calendar등의 DB에 변경되거나, 사진이 변경되는 경우등에 유용하게 사용할 수 있겠네요.

  동작은 observer와 같습니다. trigger되는 시간의 delay를 줄수도 있고, URI의 하부 URI도 감지할지 선택 할 수 있습니다. 이것도 observer와 같죠.

  

- Backoff criteria

  설정하지 않으면 exponential policy으로 동작하지만 원하는 backoff algorithm을 직접 구현할 수 있습니다.

  이건 위에서 언급했듯이 onStopJob()에서 return true인 경우에 동작합니다.

  

- Minimum latency and override deadline

  만약 Job이 적어도 얼마시간안에 동작하지 않는다면, 또는 특정시간만큼 지연되면 안된다라는 조건을 등록하는 api 입니다.

  주어진 다른 조건을 만족하지 못해서 동작이 되지 않는경우, 이 deadlined을 설정하면, deadline에 따라 trigger됩니다.

  또한 기반 조건 만족이 아니라 deadline에 의해 수행되었는지는 isOverrideDeadlineExpired() api를 통해서 확인할 수 있습니다.

    

- Periodic

  주기적으로 동작해야 한다면, 반복 시간을 등록할 수 있습니다.

  단, 정확한 시간단위로 동작을 보장하지 않으며, 해당 시간안에 적어도 한번은 수행하는건 보장합니다.

  이 조건은 setMinimumLatency(long) 또는 setOverrideDeadline(long)와 같이 사용될 경우 error가 발생합니다.

  

- Persistent

  reboot이 되더라도 자동으로 jobSchedular에 등록되어야 한다면 사용하는 조건입니다.

  꼭! RECEIVE_BOOT_COMPLETED permission을 추가해야 합니다.

  

- Extras

  추가 정보를 넘겨야 하는 경우 사용합니다. primitive값만 넣을수 있습니다.   

  

3. schedule()을 이용한 JobInfo 등록

아래와 같이 등록하면 이제 조건에 따라 scheduling 됩니다.

JobScheduler jobScheduler =
    (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(new JobInfo.Builder(LOAD_ARTWORK_JOB_ID,
    new ComponentName(this, DownloadArtworkJobService.class))
    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
    .build());


원문: https://medium.com/google-developers/scheduling-jobs-like-a-pro-with-jobscheduler-286ef8510129

동영상: https://youtu.be/XFN3MrnNhZA?list=PLWz5rJ2EKKc-lJo_RGGXL2Psr8vVCTWjM


반응형