본문 바로가기
Mobile : Android/Framework

[Android Component] 서비스(Service) #3 - 바운드 서비드(Bound Service) : 로컬 바인딩

by 신숭이 2021. 8. 24.

2021.08.09 - [Android/Android Architecture] - [Android Component] 서비스(Service) #2 - 스타티드 서비스(Started Service)

 

[Android Component] 서비스(Service) #2 - 스타티드 서비스(Started Service)

서비스(Service) #2 - 스타티드 서비스(Started Service) 스타티드 서비스(Started Service) 스타티드 서비스는 공식 문서에선 Unbound Service 라고 하는데, Unbound는 바운딩되었다가 언바운드 한 것인지..

full-stack.tistory.com

△ 선행 포스트

 

서비스(Service) #3 - 바운드 서비드(Bound Service) : 로컬 바인딩

 

 

바인드 된(Bound) 서비스는 스타티드(Started) 서비스에 비해 더욱 포괄적인 개념이다.

일단 바인드(Bind)가 무슨 말인지 알아보자면 서로 엮어놓는다는 뜻이다. 액티비티(Client)가 있고 서비스(Server)가 있을때, 이 둘을 엮어놔 언제든지 클라이언트(액티비티)는 서버(서비스)의 API( Service 의 Public 메서드) 를 이용가능하게 만들어 놓는다.

 

스타티드 서비스는 액티비티에서 서비스를 시작시켜놓고 처리 결과를 콜백으로 받는 '콜백 함수'에 비유할 수 있다면, 바인드 서비스는 API를 제공하는 서버에 비유하는게 적합해보인다.

 

용어가 다소 헷갈리는데 나도 항상 헷갈린다. 영어 문법으로 구분하는게 편하다.

액티비티 입장에서 서비스로 바인드해야 하므로 액티비티 관점에서 이걸 논할 땐 : "바인딩 서비스"

서비스 입장에서는 액티비티로부터 바인드 '당했으므로' : "바인드 된(Bound, 바운드) 서비스"

공식 문서에서는 Bound (바인드 된) 이라는 용어를 사용하기에 이하 ' 바운드 서비스 '라고 하겠다.

 

왜 도입부에 더욱 포괄적인 개념이라 했냐면 스타티드 서비스 역시 바인딩 할 수 있고(다만 생명주기가 복잡해짐), 바인드 서비스의 종류 및 활용 방식이 훨씬 다양하기 때문이다. 로컬/원격 프로세스에서 활용가능한 서비스를 구현할 수도 있다. 

 

 

 

바운드 서비스의 종류

 

공식 문서에서는 이 종류를 언급하지 않는다. 다만 많은 선배 개발자들 사이에서는 바운드 서비스를 크게 '리모트 서비스'와 '로컬 서비스'로 구분한다. 개념은 간단하지만 구현방식은 살짝 다르다.

 

  • 로컬(Local) 바인딩 서비스 : 로컬 프로세스에서만 이용할 수 있도록한 서비스. 다른 프로세스에서 쓸 일이 없다면 가장 일반적인 바인딩 서비스를 구현하는 방식일 것이다. 나의 예시를 들자면 회사 제품과 통신하는 블루투스 API를 로컬 바인딩 서비스로 구현하였다.

 

  • 리모트(Remote) 바인딩 서비스 : 다른(원격) 프로세스에서도 접근하는 것을 전제로 만드는 서비스. 때문에 IPC(프로세스간 통신) 에 대한 개념이 필수적이다. IPC의 방법은 Message 를 이용하는 방법과 AIDL(안드로이드 인터페이스 정의 언어)를 이용하는 방법으로 나뉜다. 멀티 스레딩이 필요없다면 Message 방식이면 충분하다.

 

 

바운드 서비스의 생명주기

 

간단히 개요만 살펴보고 자세한 설명은 구현에서 이어하겠다.

생명주기

  • onCreate() : 바운드 서비스는 특성상 여러 클라이언트로부터 바인딩 될 수 있다. 따라서 클라이언트가 Context 메서드인 bindService() 메서드를 호출 시, 한 번도 호출된 적 없어 처음 서비스를 생성하는 서비스라면 onCreate() 메서드를 수행한다.
  • onBind() : onCreate() 이후에 수행하며, 이미 생성된 서비스를 클라이언트가 바인딩한다면 이 메서드만 수행한다. 
  • onUnbind() : 클라이언트에서 unbindService()를 수행시 호출 된다. 해당 클라이언트는 서비스와 언바인드 된다. 다만 이를 호출한다고 무조건 onDestroy()가 호출되진 않는다. 
  • onDestroy() : 모든 클라이언트에서 언바인드 되었을때 비로소 호출되는 함수이다. 

 

 

UI

 

지난 예제(스타티드 서비스)를 조금 변형하여 액티비티에서 서비스를 바인딩한 후, 서비스의 메서드를 호출하는 버튼을 만들어보도록 한다. LocalBoundService 클래스를 추가하였다.

 

 

 

로컬 바인딩 서비스 구현

 

LocalBoundService 클래스 (로컬 바운드 서비스)

public class LocalBoundService extends Service {

    private final IBinder mBinder = new LocalBinder();

    public class LocalBinder extends Binder {   // 클라이언트 측에서 사용할 바인더
        public LocalBoundService getService(){  // 바인더를 통해 서비스의 인스턴스를 전달
            return LocalBoundService.this;
        }
    }

    @Override
    public IBinder onBind(Intent intent) {      // 바인드되면 실행되는 생명주기 메서드
        return mBinder;                         // 클라이언트에 바인더 전달(Connection 콜백에서 수신 처리)
    }

    public int getData(){       // 바인딩한 클라이언트가 사용가능한 메서드
        return 100;
    }
}

 

기본적으로 컴포넌트간 직접 메서드 사용은 불가능하기에, 서비스에서는 클라이언트(액티비티)에게 전달할 바인더(Binder)를 구현한다. 이렇게 만든 로컬 바인더는 onBind() 메서드에서 리턴된다.

 

 

 

MainActivity 클래스 ( ServiceConnection 콜백 구현)

public class MainActivity extends AppCompatActivity {

    private Button btn_call;

    // "Local Bound Service"
    private LocalBoundService mLocalBoundService;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LocalBoundService.LocalBinder binder = (LocalBoundService.LocalBinder) service;
            mLocalBoundService = binder.getService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mLocalBoundService = null;
        }
    };

이 콜백을 통해 액티비티는 서비스에 바인딩할 수 있고, bind.getService() 로 서비스 인스턴스를 받는 시점 부터 서비스의 메서드를 사용할 수 있게 된다.

 

MainActivity 클래스 ( bindService 호출)

 @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(getApplicationContext(),LocalBoundService.class);
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(mConnection);
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn_call = findViewById(R.id.btn_call);
        btn_call.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(),"서비스 메서드 리턴 값 : "+mLocalBoundService.getData(),Toast.LENGTH_SHORT).show();
            }
        });
    }

바운드 서비스는 액티비티의 onStart() ~ onStop()에서 바인딩 및 언바인딩 하는 것이 좋다.  onResume(), onPause() 등은 화면이 조금만 가려도 호출되니, 성능상 좋지 않다.

 

여기서 bindService()의 파라미터를 살펴 보면 intent와 ServiceConnection 콜백을 넘겨주고, 마지막에 거의 디폴트로 Context.BIND_AUTO_CREATE 상수를 넘겨준다. 이 상수는 "스타티드 이면서 바인드 서비스"가 아니라면 필수적으로 넘겨줘야한다. 이 옵션의 가장 기본적인 기능은 서비스가 생성된 적이 없다면 새로 서비스를 생성하는 옵션이다.  기능은 되게 다양한데 다음과 같다.

 

  • 서비스와 바인딩한 클라이언트가 여러개 있을때 만약 stopService를 호출하더라도 서비스를 종료하지 않음
  • 메모리 문제로 바인드 서비스가 종료 또는 크래시 발생으로 강종 되더라도 바로 다시 살아나 재연결
  • 서비스가 생성된 적이 없다면 새로 서비스를 생성

 

이렇게 서비스와 액티비티가 바인딩 되면 다음과 같이 액티비티에서 서비스의 메서드를 사용가능하다.

 

 

mLocalBoundService.getData()

 

 

 

 

 

다만 단순 내부DB 이용해 데이터를 가져오는 행위는 콘텐트 프로바이더(Content Provider)로도 분명 훨씬 가볍게 구현가능하므로 바운드 서비스를 구현해야할 정도의 규모인지는 늘 생각하며 설계해야한다. 오버엔지니어링은 지양할 것

 

 

댓글