티스토리 뷰

Android/Android Multi Threading

11. 서비스

안드로이드용용 2016. 8. 23. 10:52

이번 장에서는 서비스를 통한 비동기 실행에 초점을 맞춘다. 비동기 실행자와 결합한 서비스는 백그라운드 태스크 실행을 위한 가장 강력한 도구이다.


11.1 비동기 실행을 위해 서비스를 사용해야 하는 이유

서비스 대신 일반 스레드를 이용하면 두 가지 위험이 있다.

  • 구성요소 생명주기와 스레드 생명주기의 분리
    스레드 생명주기는 안드로이드 구성요소 자바 객체 주기와 독립적이므로, 구성요소가 끝나더라도 계속 진행한다. 때문에 스레드가 종료될 때까지 자바 객체가 가비지 컬렉션이 되지 않는 메모리 릭이 발생할 수 있다.

  • 호스팅 프로세스의 생명주기
    런타임이 프로세스를 종료하면 프로세스의 모든 스레드가 종료된다. 이는 데이터가 유지되기도 전에 백그라운드 스레드가 중단되는 프로세스 종료의 위험을 증가시킨다.
브로드케스트 리시버와 엑티비티에서의 테스크 실행은 생명주기가 스레드에서 분리되는 반면에, 서비스는 테스크가 끝난 후 서비스를 종료할 수 있다.

11.2 지역, 원격, 전역 서비스
서비스는 클라이언트 구성요소 라고 불리는 다른 구성요소들에서 보내진 인텐트를 통해 시작된다.
서비스가 같은 프로세스 내에서 클라이언트로 사용되면 지역이고, 외부 프로세스에서 사용되면 원격이다. 

  • 지역 서비스
    구성요소는 같은 UI 스레드에서 실행되고, 같은 힙 메모리 영역을 공유한다.

  • 전용 원격 서비스
    서비스는 원격 프로세스에서 실행되지만, 응용프로그램에 속한 클라이언트 구성요소에만 접근 할 수 있다. 힙 메모리 영역을 공유하지 않으며, 원격 메서드를 호출하는 데는 바인더 메커니즘을 이용하여 호출한다.

  • 전역 원격 서비스
    서비스는 다른 응용 프로그램에 노출된다. 외부 접근은 인텐트 필터를 통해 제공된다.


안드로이드에서 가장 일반적으로 사용하는 서비스는 지역 서비스이다.
지역 프로세스에서 실행하는 장점은 다음과 같다.
  • IPC 대신 공유된 자바 객체를 통해 더 쉽고 빠르게 통신
  • 클라이언트 스레드로부터 서비스 태스크를 실행 제어
  • 적은 메모리 소비
11.3 생성과 실행
서비스는 Service 클래스의 확장으로 정의하고, 매니패스트에 반드시 정의되어야 한다.
<service android:name="com.wifi.eat.EatService" />

서비스가 원격 프로세스에 할당되어 실행된다면, 할당되는 프로세스는 프로세스 이름과 접근 권한을 선언한 android:process 속성에 의해 정의된다. 전용 원격 프로세스는 콜론(:)으로 시작하는 속성값을 가진다.
<service 
  android:name = "com.wifill.eat.EatService"
  android:process=":com.wifill.eat.PrivateProcess" />

다른 프로그램에서는 이 Service 클래스 이름을 볼 수 없으므로, intent filter를 설정한다.
<service 
  android:name = "com.wifill.eat.EatService"
  android:process=":com.wifill.eat.PrivateProcess">
  <intent-filter>
     <action android:name="..." />
     <category android:name="..." />
   </intent-filter>
</service>

11.4 생명주기
서비스 구성요소는 onCreate와 onDestroy 콜백 사이에서 활성화 된다.
public class EatService extends Service {
    @Override
    public void onCreate() { // 구성요소 초기화 }

    @Override
    public void onDestroy() { // 사용된 자원을 청소 }

    @Override
    public IBinder onBind(Intent intent) {  // 통신 인터페이스 반환 }
}
서비스는 두가지 타입이 있다.
  • 시작 서비스
    시작 요청에 의해 생성되고, 중지 요청에 의해 소멸된다.

  • 바운드 서비스
    구성요소가 서비스에 바인딩될 때 생성되고, 모든 구성요소가 서비스에서 언바인딩 될 때 소멸된다.
서비스 구성요소는 Context.startService나 Context.bindService를 통해 바인딩한 클라이언트 구성요소에 의해 생성된다.

11.5 시작 서비스
Context.startService(Intent)를 호출하여 시작한다. 여러 구성요소에 의해 호출이 가능하다. 시작 서비스는 onBind에서 그냥 null을 리턴하고 onStartCommand 메서드를 구현하면 된다. UI스레드 위에서의 onStartComand의 순차 처리는 스레드 안전을 보장하므로, 동기화는 고려하지 않아도 된다.

11.5.1 onStartCommand 구현
onStartCommand는 시작 서비스를 구현하고 비동기 태스크 실행을 초기화하기 위한 중요한 메서드다. 인수는 다음과 같다.
  • 인텐트 : 비동기 실행을 위해 사용되는 데이터, 예를 들면 네트워크 자원의 URL
  • 전달 메서드 : 시작 요청의 기록을 반영하는 플래그, 0, START_FLAG_REDELIVERY (1), START_FLAG_RETRY(2)
  • 시작 ID : 런타임에서 제공하는 고유 식별자
11.5.2 재시작을 위한 옵션
onStartCommand의 반환값과 두 번째 인수를 통해 서비스가 종료된 후에 발생하는 상황을 제어할 수 있다. 인텐트를 런타임에 재전송 요청하여 재시작을 하거나 인텐트를 포기할 수도 있다.

이러한 모든 선택은 호출의 반환값(운영모드)에 의해 활성화될 수 있다.
  • START_STICKY
    서비스는 다른 보류 요청이 있든지 없든지 새로운 프로세스에서 재시작된다.

  • START_NOT_STICKY
    프로세스가 종료되었을 때 보류 중인 시작 요청이 있는 경우에만 서비스를 재시작 하는 것을 제외하고 START_STICKY와 같다.

  • START_READLIVER-INTENT
    서비스는 재시작되고, 보류중인 요청과 이전에 시작되었으나 오나료될 기회가 없었던 요청 모두 수신한다. 두 번째 인수에 START_FLAG_RETRY 플래그 설정과 함께 전달된다.
    • 값 0 : 기본 값, 새로운 프로세스가 시작 된다.
    • START_FLAG_REDELIVERY(값 1) : 인텐트가 이전에 서비스로 전달된 인텐트이다.
    • START_FLAG_RETRY(값 2) : 인텐트가 이전에 전달되었거나 시작할 기회가 없었지만 서비스가 종료 및 재시작될 때 보류 중이었던 인텐트임을 나타낸다.
몇가지 샘플 사례
  • 싱글 백그라운드 태스크 재시작
    클라이언트 구성요소에서 제어해야 하는 백그라운드 태스크를 실행하는 서비스를 사용하고 싶을 경우 사용한다 stopService를 호출하기 전까지는 서비스가 완료되어서는 안된다. 
    서비스가 항상 재시작하고 백그라운드 태스크도 재시작할 수 있도록 START_STICKY를 반환

  • 백그라운드 태스크 무시
    프로세스 종료 후 재시작되면 안되는 태스크를 실행한다. 서비스가 자동으로 재시작 되지 않도록 START_NOT_STICKY 를 반환

  • 끝나지 않은 백그라운드 재시작
    재시작 되기 원하는 백그라운드 태스크를 실행한다. 
    START_REDELIVER_INTENT를 반환하여 인텐트가 다시 전달되고 새로운 스레드에서 재시작될 수 있게 해야 한다.

11.5.3 사용자 제어 서비스
사용자 제어 서비스는 다른 안드로이드 구성요소에 의해 서비스가 종료되어야 한다고 지시가 있을 때까지 실행해야 하는 작업에 사용한다. 종료 또한 사용자 액션에 의해 실행한다. (음악 재생, 위치 관리, 블루투스 연결..)

11.5.4 태스크 제어 서비스
태스크 제어 서비스는 응용 프로그램이 백그라운드 스레드에서 오래 걸리는 작업을 위해 서비스를 이용할 수 있게 해 준다.

11.6 바운드 서비스
바운드 서비스는 클라이언트 구성요소가 서비스의 메서드를 호출하여 활용할 수 있는 통신 인터페이스이다. 
클라이언트 구성요소는 Context.bindService 로 서비스에 바인딩할 수 있다. 이 서비스는 처음 바인딩이 설정될 떄 생생되고, 바인딩이 살아 있도록 유지하는 클라이언트 구성요소가 없을 떄 소멸된다.
public class EatService extends Service {
    @Override
    public IBinder onBind(Intent intent) { // 통신 인터페이스를 반환 }

    Override
    public boolean onUnbind(Intent intent) { // 마지막 구성요소가 언바인딩됨 }
}
  • onBind
    클라이언트가 Context.bindService 를 통해 처음 서비스로 바인딩될 때 호출

  • onUnbind
    모든 바인딩이 언바인딩될 때 호출된다.
바운드 서비스는 클라이언트가 통신 채널로 사용될 수 있는 onBind에서 IBinder 구현을 반환한다. IBinder는 bindService가 호출될 때 바인딩 클라이언트가 제공하는 ServiceConnection 인터페이스를 통해 클라이언트로 반환된다.

boolean bindService (Intent service, ServiceConnection conn, int flag)
바인딩은 ServiceConnection에 의해 관찰되고 바인딩 클라이언트 구성요소는 바인딩이 설정될 때 통지받을 수 있는 구현을 제공한다.

비동기 실행의 동작 및 관리는 통신 유형에 따라 다르다.

  • 지역 바인딩
    가장 일반적인 유형으로, 서비스와 클라이언트가 같은 VM에서 실행되어 같은 힙 메모리를 공유한다.
    public class BoundLocalService extends Service {
        private final ServiceBinder mBinder = new ServiceBinder();
        
        public IBinder onBind(Intent intent) {
            return mBinder;
        }
    
        public class ServiceBinder extends Binder {
            public BoundLocalService getService() {
                return BoundLocalService.this;
            }
        }
    
        //클라이언트에 공개된 메서드
        public void publishMethod1() { ... };
        public void publishMethod2() { ... };
    }
    


클라이언트가 지역 서비스에 바인딩되면 클라이언트는 통신 인터페이스에 의해 공개된 메서드를 호출 할 수 있다.
public class BoundLocalService extends Service {
    public interface OperationListener {
        public void onOperationDone(int i);
    }

    public void doLongAsyncOperation(final int i , final OperationListener listener) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                int result = longOperation(i);
                listener.onOperationDone(result);
           }
         }).start();
      }
     private int longOperation(int i) {
     // 오래 걸리는 작업에서 결과를 반환
    }
}

엑티비티에서 위 서비스 사용

public class BoundLocalActivity extends Activity {

   private static class ServiceListener implements BoundLocalService.OperationListener {
       private WeakReference mWeakActivity;
       
       private ServiceListener(BoundLocalActivity activity) {
          this.mWeakActivity = new WeakReference(activity);
       }

       @Override
       public void onOperationDone(fianl int someResult) {
           final BoundLocalActivity localReferenceActivity = mWeakActivity.get();
           if (localreferenceActivity != null) {
              localReferenceActivity.runOnUiThread(new Runnable() {
                  @Override
                  public void run() {
                       // UI 스레드 업데이트
                  }
              });
          }
       }
    }
    public void onClickExecuteOnClientUiThread(View v) {
      if (mBoundService != null ) {
          mBoundLocalService.doLongAsyncOperation(new ServiceListener(this));
      }
    }
}

11.7 비동기 기술 선정

위에서 설명한 두 가지 사용 사례에 대해선 다음과 같은 고려를 해 보면 좋다.


  • 태스크 제어 서비스에 대한 순차 실행
    태스크 제어 서비스는 사용된 비동기 기술에 따라 순차적, 혹은 동시 실행이 가능하다.
    순차적 실행은 12장에서 배울 IntentService를 사용하는 것이 좋다.

  • 지역 프로세스에서 전역 실행자를 포함한 AsyncTask
    AsyncTask는 10장에서 설명했다시피, 전역 실행자를 갖기 때문에 태스크가 지연되거나 실행이 안될 수 있기 때문에, 다른 구성요소로 실행되는 AsyncTask는 비전역 실행자 같은 커스텀 실행자를 사용하거나 스레드 풀을 대체해서 사용하는 것이 좋다.
11.8 마치며
서비스는 UI스레드에서 백그라운드로 실행되며, 태스크를 백그라운드 스레드로 옮기는 데 상당히 유용하다. 특히 테스크 제어는 서비스에서 제어가 가능하기 때문에 유용하다.


댓글
댓글쓰기 폼
공지사항
최근에 달린 댓글
Total
65,392
Today
3
Yesterday
1
TAG
more
«   2022/10   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
글 보관함