유저가 사용하던 앱을 벗어나면서 앱이 foreground상태에서 벗어나게 됩니다.

해당 앱의 서비스가 foregroundService로 선언하고 시작했음에도,

해당 앱을 idle상태로 보고,

System에 의해서 destroy되는 경우가 있는데요.

 

구글의 레퍼런스폰보다는 다양한 제조사의 배터리나 메모리를 관리하는 앱에서 발생하는 경우가 많습니다.

이럴 때 생기는 문제가 어떤 것들이 있고,

어떤 식으로 대응하면 좋을지 생각해 보겠습니다.

 

앱이 서비스를 destroy하고 다시 Service를 시작시키는 경우에 어떻게 할 것인지는,

onStartCommand콜백에서 return값을 정해진 flag상수값을 줌으로써,

정의할 수 있는데요.

많이 쓰는 flag값으로는 START_STICKY와 START_NOT_STICKY가 있습니다.

이중 먼저 START_STICKY flag값에 대해서 먼저 알아보도록 하겠습니다.

 

1. START_STICKY flag

START_STICKY flag상수값을 onStartCommand에서 return해 주면,

Service 가 System에 의해서 destroy될 때,

다시 Service를 restart하도록 정의하는 것 인데요.

 

 

이에 대한 내용은 공식문서의 설명을 보면 아래와 같은데요.

START_STICKY를 onStartCommand에서 리턴해 줄 경우, System에 의해서 destroy되면,

started state에 머물렀다가, 새로운 서비스 객체를 생성한 후에,

다시 onStartCommand를 호출하는 것을 보장한다고 나와있습니다.

그 대신 intent는 null값으로 넘어오므로 그 부분에 대해서 대응하라고 나와있구요.

 

 

여기까지 보면,

그냥 START_STICKY flag를 return해 주어서 문제를 해결하면 될 것 같은데요.

실제로는 어떤지 로그를 보면서 생각해 보겠습니다.

 

2. 실제 실행시켰을 때의 Log

위에서 언급한대로 드물기는 하지만,

시스템에 의해서 destroy되었다가 restart될 때의 로그를 보면 다음과 같습니다.

아래이미지와 같이, ActivityManager가 restart를 scheduling하였고,

약 30초 정도 뒤에 재 시작 한 것을 볼 수 있습니다.

 

실제 디버깅을 하면서 겪어보면,

이렇게 재시작되는 시간은 폰이나 상황마다 매우 다르게 나오는데요.

짧게는 몇십초, 많게는 onStartComand가 3,4분 후에 호출된 적도 본 적이 있습니다.

앱 서비스를 하는 사람으로서, 유저에게 서버의 정보가 서비스의 노티피케이션을 통해 제공되고 있다가,

잠시 끝겼다 제공된다면 3,4분 정도는 너무나 크리티컬한 시간이라고 할 수 있겠네요.

 

 

광고

 

3. 문제점

START_STICKY flag를 리턴해 줌으로서,

서비스의 재생성과 onStartCommand의 호출이 보장되기는 하지만,

onStartCommand에 전달되는 intent 값이 null이 나온다는 사실과

언제 호출될지에 대한 부분이 보장되지 않는다는 점이 문제가 되는데요.

 

특히 위에서 언급한데로,

최악의 경우 3,4,분정도 지난후에 재시작 되는 경우에는,

문제가 있을 것 같은데요.

 

예를 들면, 실시간으로 서버 정보를 보여주다가,

3,4분 후에 정지되었다고 서비스에서 노티를 주었다고 가정해 보겠습니다.

그 이전에 유저가 앱에 들어가서 해당 서비스가 제대로 동작하지 않고,

DB에 저장된 정보도 뭔가 잘못되었다는 것을 인지할 수 있는데요.

실시간으로 제공되는 서비스의 경우는 문제가 큰 것 같네요.

 

유저가 동작하던 서비스가 중단되었던 사실을 알아채고 한참 지나서야,

정지되었다고 알릴 수도 있다는 것 인데요.

다른 방법은 없는 걸까요?

 

4. START_NOT_STICKY

그럼 서비스를 다시 재 실행하지 않게 해주는 flag인

START_NOT_STICKY를 사용하면 어떨까요?

onDestroy콜백에서 유저에게 노티를 해 주고,

서비스를 중단하면 될 것 같은데요.

 

여기서 문제는 서비스가 destroy될 때,

onDestroy콜백이 호출된다고 100%보장되지 않는다는 점 입니다.

따라서, 여기서 어떤 로직을 넣어서,

갑작스런 destroy에 대응하려고 하는 방법은

호출이 될 수도 있다는 점에서는 없는 것 보다는 낳을 수 있겠지만,

완전한 방법은 될 수 없습니다.

 

5. START_FLAG_REDELIVERY

한가지 고민해 볼 수 있는 옵션 중 하나가, START_FLAG_REDELIVERY flag입니다.

이 flag값을 return해 주면 START_STICKY flag처럼,

서비스가 시스템에 의해 재실행되는 것을 보장하면서도,

onStartCommand실행시에 최초에 받았던 intent값을 다시 전달받을 수 있습니다.

 

5-1.  redeliver되었는지 알아내는 방법

이것은 onStartCommand에 전달되는 flag값을 통해서 알 수 있는데요.

 

START_FLAG_REDELIVERY의 상수값은 3이므로, 이 값이 나왔다면,

redeliver된 것으로 판단할 수 있습니다.

 

하지만 여기서도 주의해야 할 점은 절대로 onStartCommand가 시스템이 destroy하자마자,

재실행을 보장받거나 하는 것이 아니라는 점 입니다.

여전히 destroy 된 후에 재실행까지의 실행시간에 있어서 알지 못하는 점이 있습니다.

 

게다가 여기서는 Activity의 onSaveInstanceState처럼 특정상황의 데이터들을 저장했다가 사용하는 것이 아니라,

onStartCommand실행시 넘어온 초기 intent값만을 가지고 있기 때문에,

그 값이 Service가 실행되면서 변화하는 값이라면 의미가 크지 않을 수 있습니다.

 

6. 문제에 대한 대응 정리

위에서 다루었던 세가지 flag를 이용한 대응에 대해서 다시한번 정리해 보겠습니다.

6-1. START_FLAG_REDELIVERY flag사용

재실행시 다시 전달받는 intent값이 의미가 있고, 재실행되는 시점이 불분명하더라도 괜찮다면,

이 flag를 이용해서 시스템에 의해 재실행된 Service를 이용할 수 있습니다.

 

6-2. START_STICKY flag사용

재실행시 null값이 intent로 들어오므로, 이것을 이용해서,

서비스를 정상적으로 종료시키거나, 다시 초기화 해서 시작할 수 있습니다.

위에서 계속 언급한대로 언제 다시 서비스가 재실행할지 알수없다는 점 때문에,

유저가 앱을 실행해서 서비스가 죽은 것을 인지한 후에,

뒤늦게 서비스를 종료하거나 초기화 하는 것이 문제가 될 수 있습니다.

 

6-3. START_NOT_STICKY flag사용

서비스가 destroy되더라도 신경쓰지 않고 그냥 놓아두는 것 인데요.

destroy될 때, service의 onDestroy를 호출하지 않게 되면,

유저에게 서비스의 종료를 알릴 수 있는 방법이 존재하지 않습니다.

유저에게 알리지 않아도 되는 background 서비스에서 의미가 있을 수 있겠네요.

 

결국 어떤 flag값을 선택하느냐로 귀결되는데요.

잘모르겠다고 아무생각없이 무조건 START_STICKY를 선택하는 것 만은 피해야 할 것 같네요.

오히려, 좀더 심플한 START_NOT_STICKY가 디폴트 값이 되어야 할 것 같습니다.

7. 정리

완벽한 방법은 없는 것 같구요

저희가 할 수 있는 것은, 각각의 상황에 맞는 최선의 대응을 하는 방법밖에 없는 것 같습니다.

위에서 언급은 하지 않았지만, 클라이언트가 bind되는 bindService일 경우는 어떻게 해야할지등,

않그래도 Service자체가 고려해야 할 것이 많은데요.

역시나 쉽지가 않습니다.

 

+ Recent posts