BindService클래스를 사용하는 앱을 만들다 보면,

여러가지 문제점들에 부딪힐 때가 있습니다.

이 클래스가 다양한 케이스에서 이용될 수 있고,

실행이 비동기적으로 되는 부분이 있어서,

사용하는데 주의가 필요하기 때문인데요

오늘은 이것에 대해서 정리해 보도록 하겠습니다.

 

참고로 이 글은 BindService의 기초적인 사용법에 관한 글은 아니므로,

BindService를 사용하는 방법은 아래 글을 참조해 주세요.

BindService 의 생성과 Activity 에서의 Bind

 

1. BindService

가볍게 BindService에 대해서 회고하면서 시작해 보도록 하겠습니다.

BindService는 Activity나 Fragment등이 클라이언트로 bind되어서

데이터를 주고받는 등의 일을 할 수 있게 해주는데요.

구현해야만 하는 onBind()콜백시 주어지는 IBinder객체를 이용해서,

클라이언트와 데이터를 주고 받을 수 있도록 되어 있습니다.

 

이제 본격적으로 bind와 unbind를 사용하면서 주의할 점에 대해서 알아보겠습니다.

2. Bind와 Unbind

사실 Bind와 UnBind를 할 때 가장 조심해야 할 포인트는,

Service에 Bind하고 UnBind하는 타이밍입니다.

Activity에서 onStart혹은 onResume시에

Service가 살아있으면 Bind해 주고,

onStop혹은 onPause에서 Unbind해 주어야 하는 것과 같은 로직을 가지고 있다면 더욱 그러한데요.

무엇보다도, 원하는 타이밍에 bind되거나 unbind되지 않을 수 있기 때문입니다.

먼저 bind할 때 사용하는 API인 bindService()메소드를 보도록 하겠습니다.

 

2-1. bindService

공식문서에 나와있는 것처럼, 이 API는 필요할 경우에는 서비스 객체를 생성해서,

클라이언트와 Service와 bind될 수 있도록 해 줍니다..

 

 

2-2. unbindService

unbind에 대해서 이야기 하기전에,

공식문서에 나와있는 Service의 lifecycle 에 대해서 보도록 하겠습니다.

아래 이미지의 우측 부분이 bindService됬을 경우의 lifecycle인데요.

 

 

만약, 모든 클라이언트들이 startService없이  bindService만으로 객체가 생성된 Service의 경우,

unbind되었다면, 서비스 스스로 stopSelf하거나 클라이언트가,

stopService()메소드를 사용하지 않아도 destroy되어집니다.

 

많은 경우, bindService만 사용하는 경우보다는, startService로 서비스를 구동시킨다음,

ux에 따라서 bind되었다가 unbind되는 과정을 거치게 될 것 입니다.

이럴 경우 unbind시키기 위해서는, 모든 클라이언트를 unbind한 다음,

stopService혹은 stopSelf를 해야하는 것 이지요.

예를 들어, 음악 앱같은경우, 서비스에서 계속 음악을 플레이 해주고,

클라이언트는 유저가 앱아이콘을 클릭해서, 포그라운드로 나올때만 서비스에 bind되도록 해주어 합니다.

 

2-3. unbind혹은 bind시의 문제들

만약, onResume상태 혹은 onStart상태에서 service가 bound되었는지 아닌지 어떻게 하면 알 수 있을까요?

물론, 저희는 bindService()함수에 인자로 ServiceConnection객체에,

onServiceConnected와 onServiceDisconnected를 가지고 있습니다.

이를 이용하면 되지 않을까 생각할 수 있는데요.

실제로 공식문서의 예제도 이러한 방식으로 작성되어 있습니다.

 

아래가 공식문서에 나오는 실제 코드인데요.

mBound를 변수로 이용해서, bind된 상태로서 이용하려고 하는 것 같습니다.

 

아래를 보시면 버튼 클릭시에,

mBound가 true이면 서비스의 randomNumber를 가져오는 방식을 취하고 있지요.

 

 

2-4. onServiceDisconnected로 인한 오해

그런데, 실제로 서비스 작업을 해보면 이것이 항상 작동하는 방식이 아니라는 것을 알 수 있습니다.

onServiceDisconnected의 mBound값은 항상 서비스가 unbind되었다는 것을 보장해주지 못하기 때문입니다.

무슨 소리인지 onServiceDisconnected의 공식문서 설명을 보도록 하겠습니다.


아래 이미지를 보시면, Connection을 잃었을 대 호출된다고 나와있는데요.

이것만 보면 맞는 것 같지만, 부연설명을 보면 그렇지 않습니다.

여기서 Connnection을 잃었다고 정의하는 상황은,

서비스의 Process가 원치않게 시스템에 의해서 Crash나거나 kille되었을 때를 의미한다는 것 이지요.

 

이 콜백이 호출된다고 그것이 Service에 대한 binding의 제거를 의미하지는 않는다고 합니다.

binding은 남겨져 있다가, 서비스가 다음에 다시 실행될 때, onServiceConnected가 호출된다고 하니,

더욱이 binding에 대한 상태를 보는 용도로는 부적절하다고 생각합니다.

이쯤되면, 메소드명이 잘못 지어진 것 아닌가 생각하게 됩니다.

 

2-5. Unbind에 대한 확인

그렇다면 어떻게 해야 Service가 unbind되었는지 알 수 있을까요?

방법을 정리해보기전에 한가지 고려해야 할 것이 있습니다.

bindService를 호출하면 생기는 서비스객체는 카운트되어지는 레퍼런스 객체라는 점 입니다.

경우에 따라서는 bind된만큼 unbind해주어야 정상적으로 동작하는 케이스도 존재합니다.

이에 대해서는 공식문서에도 아래와 같이 reference counted라고 나와있습니다.

 

onStart와 onStop에서만 항상 bind와 unbind를 하는 경우라면 그렇게 복잡하지는 않겠습니다.

하지만, 유저와의 인터랙션에 의해서, bind가 시작되는 경우가 있을테고,

bind가 시작되면 생성된 Notification에 의해서 Activity에 진입한 경우도 마찬가지 일 것이구요.

 

2-5. 해결방법 생각해 보기

구글의 공식 Reference에서 Service Class쪽의 문서를 보면,

조금은 다르게 이것에 대해 접근한 예제가 있는데요.

bindService함수가 return 해주는 값을 근거로

mShouldUnbind 라는 Boolean값을 set해주고,

이 값을 근거로 unbind해야하는지를 결정하는 것 입니다.

 

mShouldUnbind값을 근거로 할 때 주의해야 할 경우는,

Notification을 통해서 Activity로 들어올 때 일 것 같습니다.

Notification에서 FLAG_ACTIVITY_NEW_TASK로 FLAG를 설정한 intent를 pendingIntent에 넣는 경우가 많기 때문에,

기존의 설정된 변수값들이 무시될 수 있는 것이지요.

이경우 굉장

 

bindService의 return값을 보면 아래와 같이 설명되어 있습니다.

해당 서비스의 클라이언트의 bind가 허가되었을 때, 시스템이 service를 생성하는 과정에 있을 경우,

 true를 반환한다고 되어있네요.

(해석이 정확한지는 모르겠지만, 중요한 것은 허가된 클라이언트가 bind될 때 나오는 값이라는 것이지요.)

 

다만, 이렇게 하는 방법이 현재 bind되었는지 않되었는지를 알려주지는 못합니다.

bind가 정상적으로 시도되었던 적이 있으니, 언젠가는 conneciton을 release해 주어야 하고,

그것을 가지고 가정하는 것 뿐이지요.

정확하게 bind된 상태에 대해서 추적하고자 한다면,

Service클래스에서 onBind, onRebind 그리고 onUnbind를 이용해서 bind카운트를 추적하고,

그것을 클라이언트에서 조회해서 사용하는 방법도 있을 것 같습니다.

참고로 onRebind가 정상적으로 동작하기 위해서는,

아래의 공식문서에서의 설명처럼 onUnbind가 return값이  true가 되도록 override해 주어야 합니다.

 

 

ServiceClass의 값에 대해서 조회할 수 없거나,

onBind된 적이 없다면, 그것은 bind되지 않았다고 확실히 할 수 있을 것 이니까요.

다만 아쉬운 점은, onUnbind는 현재 접속된 모든 클라이언트가 disconnected되었을 때,

마지막에 호출된다는 점 인데요. 이점을 꼭 염두해 두어야 겠지요.

클라이언트가 여러개일 경우, 해당 클라이언트가 bind된 것인지의 여부를 확인하기는 어렵습니다.

클라이언트가 한개일 경우에만 의미가 있을 수 있겠네요.

 

 

2-6. 아직도 문제는 남아있다

네, 아직도 생각해야 할 점이 남아있습니다.

bindService() 함수를 호출한다고 바로 bind되는 것은 아니구요.

마찬가지로 unbindService()를 호출할 때에도 바로 unbind가 보장되는 것이 아닙니다.

bind와 unbind가 비동기적으로 실행되기 때문인데요.

따라서, unbindService했다고 바로 unbind되었는지 체크를 하게 된다면,

잘못된 결과를 받을 수도 있게 됩니다.

비동기 프로그래밍이 항상 그러하듯이,

실행 시점에 있어서 보장 받지 못하게 되는 것 이지요.

 

따라서 Service에 대한 bind와 unbind에 대한 상태를 확인하기 바로 직전에,

bind를 하거나 unbind를 하였고 그 상태를 확인하려고 한다면,

잘못된 결과를 받을 수 있다는 점 입니다.

이 부분이  비동기라는 사실을 머리속에 염두해 두고,

개발을 해야 하겠습니다.

 

추가적으로 한가지 상황을 더 생각해 보자면,

노티피케이션을 따라서 NEW_TASK에서 생성된 Activity가

onCreate뒤에, 이전에 있던 Activity의 onDestroy가 호출되는

기가막힌 상황이 발생할 수 있습니다.

그럼 보통 onDestroy에 listener를 remove해 주기 때문에,

bind만 되고 데이터는 전달받지 못하는 상황이 발생할 수 있지요.

이래저래 bindService를 완벽히 구현하는 일은

정말 쉽지 않은 것 같습니다.

 

2-7. 결론

정답이 있다기 보다는 주어진 상황에 맞추어서 해결해야 할텐데요.

우선은 시나리오에 대해서 명확하게 할 필요가 있을 것 같구요.

예를 들면, 유저가 버튼을 누르면 bind되었다가,

나갔다가 들어오는 onStart에 bindService의 동작유무를 기준으로 bind를 시도하고,

onStop에 unbind해 주고,

유저 버튼에 다시 unbind된다와 같은 것 말이지요.

 

 

예를 들면, 클라이언트가 하나여서,

Service에서 bind상태를 CompanionObject에서 가지고 있다가,

클라이언트에서 이것에 직접 액세스 해서,

확인하는 방식을 사용할 수도 있을 것 같습니다.

 

 

 

bind와 unbind가 비동기 실행코드이므로,

bind와 unbind가 유저에 의해서 실행되어서,

위의 방법만으로 확실하게 상태를 파악하기 어려운 경우는,

bind시에 return되는 값을 mShouldUnbind를 이용해서,

bind가 정상적으로 실행되었는지 더블체크할 수도 있을 것 같습니다.

 

728x90

+ Recent posts