Android Service

노력을 즐겼던 사람·2020년 11월 7일
1

손자취 앱개발

목록 보기
2/4
post-thumbnail

Service란?

  • 서비스로는 네트워크 트랜잭션, 음악 플레이어, 파일 I/O 등과 같은 content provider 와 상호작용 하는 모든 것들을 백그라운드에서 실행할 수 있다.

  • Service 는 백그라운드에서 장기적인 기능을 동작시킬 수 있는 application component 이다.

  • 사용자가 다른 어플리케이션을 사용하고 있어도 실행될 수 있다.

서비스는 호스트 프로세스의 메인 스레드에서 동작한다. 별도의 스레드를 생성하지 않으며 지정하지 않는다면 독립적인 프로세스를 생성하지 않는다.

Service의 종류

  • Foreground
  • Background
  • Bound

Foreground

Foreground service 서비스가 수행하는 동작을 사용자가 알아야할 때 사용한다. 때문에 사용자에게 notofication 을 통해 실행되고 있다고 알려야한다.

예를들어 음악 플레이어는 실행될 때 지금 실행중인 음악 등의 정보를 notification 을 통해 알려주고 있다. 이 notification 은 서비스가 중단되거나 foreground 에서 제거되지 않는 이상 지울 수 없다.

Background

Background service 는 사용자에게 알릴 필요가 없는 기능들을 담당한다.
예를들어 서비스를 사용하여 저장소를 압축하는 것등 사용자가 알 필요가 없는 것들을 담당한다.

Bound

serviceapplication componentbindService() 를 호출해서 service 에 바인딩되면 bound 된다. 그렇다면 bound 된다는게 무슨 말일까?

bound service 는 서버-클라이언트 구조의 인터페이스를 제시한다.

이 인터페이스의 컴포넌트와 서비스와 상호작용하고 요청을 전송하고 결과를 수신하는 등 프로세스와 상호작용 할 수 있게 한다(InterProcess Communication... IPC라고 부르는 듯)

bound serviceapplication component가 바인딩되어 있을 때만 실행된다. 한번에 여러개의 application component를 바인딩 할 수 있다.

Service 생성하기 - 이론편

서비스를 생성하기 위해서는 ServiceImplement 해야한다.
안드로이드의 LifeCycle 을 핸들링 하기 위한 콜백 메소드들을 재정의하고 필요한 경우 바인드를 위한 메소드도 오버라이드 해야한다. 일반적으로 가장 중요하다고 생각되는 콜백함수를 나열해보자면 아래와 같다.

  • onStartCommand()
  • onBind()
  • onCreate()
  • onDestroy()

onStartCommand()

시스템에서 액티비티와 같은 다른 컴포넌트에서 startService() 를 호출하게 되면 이 메소드가 실행되고 서비스가 시작된다.

이 메소드를 구현한 후 서비스를 중단하기 위한 stopSelf() 또는 stopService() 메소드를 호출해야한다.

만약 바인딩만하고 싶으면 이 메소드를 구현할 필요는 없다.

onBind()

시스템에서 액티비티와 같은 다른 컴포넌트에서 bindService() 를 호출하게 되면 이 메소드가 실행되고 서비스에 바인딩된다.

이 메소드의 구현체에서는 IBinder 를 리턴하여 클라이언트와 서비스가 통신할 수 있는 인터페이스를 제공해야한다.

항상 이 메소드를 구현해야한다. 바인딩을 원하지 않는다면 그냥 null을 반환하는 식으로라도 구현해야한다.

onCreate()

시스템에서는 서비스를 처음 만들 때 이 메소드를 호출한다. 이 메소드는 onStartCommand() 또는 onBind() 를 호출하기 전에 호출되며 서비스가 이미 실행중일 경우에 이 메소드는 호출되지 않는다. 그래서 Service 에 대해서 설정할 것들이 있으면 이 메소드에 구현해야한다.

onDestroy()

시스템에서는 서비스가 더 이상 사용되지 않거나 서비스를 제거할 때 이 메소드를 호출한다. 서비스가 실행한 스레드, 리스너, 리시버 등의 리소스들을 삭제하기 위한 동작이 구현되어야 한다. 서비스가 호출하는 마지막 메소드이다.

만약 bindService() 를 통해서 서비스를 생성했다면 즉, onStartCommand() 를 호출하지 않았으면 바인딩 된 서비스가 종료되면 시스템에서 서비스를 제거한다.

안드로이드는 디바이스에 메모리가 부족할 때 서비스를 종료해서 시스템 디바이스 자원을 회복한다. 그리고 서비스가 백그라운드에서 오래 실행될수록 시스템에 의해 서비스가 종료되기 쉽다. 만약 시스템이 자원을 확보하기 위해서 서비스를 죽이면 서비스를 다시 실행할 수 있는 자원이 확보되면 onStartCommand() 메소드를 다시 실행한다.

Service 생성하기 - 코드편

Manifest 등록하기

사용하고 싶은 모든 서비스들은 반드시 manifest 에 정의되어야 한다. 아래와 같은 코드를 작성하자

<manifest ... >
  ...
  
  <application ... >
      <service android:name=".ExampleService" android:exported="false" />
      ...
  </application>
</manifest>
  • <service> 에는 권한과 같은 속성을 추가할 수 있다.

  • android:exported="false" 속성을 추가함으로써 다른 앱에 의해서 서비스가 실행되는 것을 막을 수 있다.

  • 앱의 보안을 위해서 서비스를 시작할 때 항상 explicit intent 를 사용하며 intent filter를 선언하지 않아야 한다.

  • android:description 속성을 추가해서 사용자에게 실행중인 서비스에 대한 설명을 제공해서 사용자가 이 서비스의 출처를 모르고 중단시키지 않도록 할 수 있다.

서비스 정의하기

위에서 언급한대로 서비스 실행은 액티비티와 같은 다른 componentstartService() 메소드 호출에 의해 이루어진다.

서비스는 자신을 실행시킨 component 와는 독립적인 lifecycle 을 가진다. 때문에 백그라운드에서 독립적으로 실행될 수 있다. 심지어 자신을 실행시킨 component 가 더 이상 존재하지 않더라도 말이다.

위와 같은 이유로 자신이 stopSelf() 를 호출하거나 다른 componentstopService() 와 같은 메소드를 호출해서 중단 시키는 것이 필요하다.

서비스는 필요한 경우 사용할 Intent를 넘겨 받을 수 있다. 넘겨 받은 IntentonStartCommand() 메소드에서 핸들링 할 수 있다.

예를 들어 어떠한 데이터를 서비스를 활용해서 데이터베이스에 저장하고 싶을 때 startService() 를 호출하며 Intent를 전달한다. 서비스는 넘겨받은 데이터를 토대로 작업을 수행하고 중지, 파괴된다.

위에서 언급했지만 서비스는 별도의 스레드나 프로세스를 생성하지 않는다. 따라서 서비스를 많이 발생시키면 성능의 저하가 발생할 수 있다. 필요한 경우 스레드나 프로세스를 생성하여 서비스를 실행시켜야 한다.

Service 클래스는 우리가 정의할 모든 서비스의 기본이 되는 클래스이다. 우리가 이 클래스를 extend 해야해서 사용하는데 새로운 스레드를 생성하는 것을 추천한다.

Service의 하위 클래스인IntentService 클래스는 worker thread 를 활용해서 요청을 하나씩 처리한다. 그런데 오레오 버전부터는 JobIntentService 사용을 추천한다.

그리고 서비스 작업의 분배를 위해 WorkManager 사용을 강력하게 권장한다. 자세한 내용은 Guide to background processing을 참고하자.

코드 살펴보기

넘겨받는 각 Intent 들을 처리하기 위해서 Service 를 상속받을 수 있다. 가장 기본적인 구현체를 살펴보자.

public class HelloService extends Service {
  private Looper serviceLooper;
  private ServiceHandler serviceHandler;

  // 스레드로부터 메시지를 수신하는 핸들러 선언
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      
      @Override
      public void handleMessage(Message msg) {
      	  // 우리가 수행하고 싶은 작업을 여기에 선언한다.
          // 이번 샘플에서는 5초 슬립을 수행한다.
          try {
              Thread.sleep(5000);
          } catch (InterruptedException e) {
              // 인터럽트 상태에서 회복한다.
              Thread.currentThread().interrupt();
          }
          // ID를 통해 서비스를 중단하므로 엉뚱한 서비스를 종료하지 않는다.
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Main Thread가 아닌 새로운 스레드에서 서비스를 실행한다.
    // 새로운 스레드에서 실행하면 UI Blocking을 방지할 수 있다.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // 새로운 스레드의 Looper를 핸들러에 전달한다.
    serviceLooper = thread.getLooper();
    serviceHandler = new ServiceHandler(serviceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // 각 요청이 실작할 때마다 ID와 함께 시작 메시지를 보낸다.
      // 이를 통해 우리 요청의 상태를 알 수 있다.
      Message msg = serviceHandler.obtainMessage();
      msg.arg1 = startId;
      serviceHandler.sendMessage(msg);

      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // Binding을 하지 않으니 null 리턴
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

위의 코드는 Handler 를 통해서 백그라운드 스레드를 생성하고 요청을 백그라운드 스레드에서 수행한다. 위의 코드는 IntentService와 같이 모든 요청을 차례대로 처리한다.

onStartCommand() 메소드에서 리턴하는 정수는 서비스의 상태를 나타낸다.

Service 시작하기

위에서 계속 언급하지만 다른 액티비티 같은 application component 에서 Intent 를 매개변수로 가지는 startService() 혹은 startForegroundService() 를 실행함으로써 서비스를 시작할 수 있다. 아래 코드와 같이 작성하면 된다.

Intent intent = new Intent(this, HelloService.class);
startService(intent);

Intent 를 전달하면 단방향 통신이다. 만약 서비스에 대한 결과를 반환받고 싶다면 PendingIntent 를 전송한다. 그러면 서비스는 boradcast 를 사용해서 결과를 반환할 수 있다.

참고문서
정리를 너무 잘해주신 블로그

profile
노력하는 자는 즐기는 자를 이길 수 없다 를 알면서도 게으름에 지는 중

0개의 댓글