본문 바로가기
Android/Android 기초

[Android] 안드로이드 4대 컴포넌트(2) - 서비스(Service)

by 태크민 2023. 9. 3.

서비스 (Service)

서비스는 안드로이드 Application을 구성하는 4대 컴포넌트 중 하나이며, 액티비티(Activity)가 종료되어 있는 상태에서도 Background에서 동작하는 컴포넌트이다.

서비스는 기본적으로 UI가 동작하는 Main Thread에서 동작을하기 때문에, CPU에 집약적인 작업을 수행하거나 네트워킹  같이 시간이 오래 걸리는 작업을 할 때는 별도의 스레드를 작업을 수행하여야 ANR을 방지할 수 있다.

 


 

서비스의 종류

1. 백그라운드 서비스 (Background Service)

백그라운드 서비스는 사용자에게 보이지 않는 작업을 수행한다.

단, 앱이 API 26 이상을 대상으로 한다면 앱이 포그라운드에 있지 않을 때, 시스템에서 백그라운드 서비스 실행 제한을 적용한다.

 

2. 포그라운드 서비스 (Foreground Service)

포그라운드 서비스는 startForegroundService()를 호출하여 사용자에게 보이는 백그라운드 작업을 수행한다. 사용자에게 눈에 띄는 일부 작업을 수행하며, 사용자가 앱과 상호작용하지 않아도 계속 실행된다.

예를 들어 오디오 앱은 Foreground Service를 사용하여 오디오 트랙을 재생한다.

포그라운드 서비스를 사용할 때는 사용자가 서비스가 실행 중임을 적극적으로 인식하도록 알림을 표시해야 하며, 서비스가 중지되거나 포그라운드에서 제거되지 않는 한 이 알림을 해제될 수 없다.

포그라운드 서비스를 사용함으로 써 사용자들은 앱이 작업을 하고 있고 시스템 자원을 사용하고 있다고 확실하게 알 수 있다.

<주의 사항>

1. 시스템이 서비스를 생성한 후, 앱은 5초 이내에 서비스의 startForeground() 메서드를 호출하고 알림을 사용자에게 표시해야 한다. 그렇지 않으면 시스템이 서비스를 중단하고 ANR을 발생시킨다.

2. Android 9(API 레벨 28) 이상을 대상으로 하고 포그라운드 서비스를 사용할 경우 FOREGROUND_SERVICE 권한을 요청해야 한다. (선언 시 시스템은 앱에 자동으로 권한 부여) 

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

해당 권한을 선언하지 않고 포그라운드 서비스를 생성 시도할 경우 시스템이 SecurityException을 발생시킨다.

 

[서비스 코드]

public class MyForegroundService extends Service {

    int count = 0;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {

                    Log.e("Service", "서비스가 실행 중입니다...");
                    Log.e("Service", ""+ count);

                    try {
                        Thread.sleep(2000);
                        count++;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

		//포그라운드 알림 정보를 표시해줘야하기 때문에 notification 추가 구현
        final String CHANNELID = "Foreground Service ID";
        NotificationChannel channel = new NotificationChannel(
                CHANNELID,
                CHANNELID,
                NotificationManager.IMPORTANCE_LOW);

        getSystemService(NotificationManager.class).createNotificationChannel(channel);
               
        Notification.Builder notification = new Notification.Builder(this, CHANNELID)
                .setContentText("서비스가 실행중입니다.")
                .setContentTitle("service_ex3")
                .setSmallIcon(R.drawable.ic_launcher_background);

        startForeground(888, notification.build());

        return super.onStartCommand(intent, flags, startId);
    }


}

 

[액티비티 코드]

public class MainActivity extends AppCompatActivity {

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

        Intent serviceIntent = new Intent(this, MyForegroundService.class);// MyBackgroundService 를 실행하는 인텐트 생성
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 현재 안드로이드 버전 점검
            startForegroundService(serviceIntent);// 서비스 인텐트를 전달한 foregroundService 시작 메서드 실행
        }else {
            startService(serviceIntent);// 서비스 인텐트를 전달한 서비스 시작 메서드 실행
        }
    }
}

 

3. 바인드 서비스 (Bind Service)

바인드는 묶다라는 뜻으로,

바인드된 서비스란 클라이언트-서버 인터페이스 안의 서버 역할을 맡은 컴포넌트(서비스)를 말하며, bindService()를 호출하여 액티비티(Activity)와 같은 컴포넌트를 바인딩 시킬 수 있다.

이를 사용하면 컴포넌트(Activity 등)가 서비스에 요청을 보내고 결과를 받고, 프로세스 간 통신(IPC)을 실행할 수 있다.

바인드 서비스는 실제 컴포넌트가 바인딩 된 경우에만 실행이되며, 여러 컴포넌트가 한 번에 서비스에 바인딩 될 수 있다. 하지만 모든 컴포넌트가 바인딩 해제되면 서비스는 소멸된다.

 

시작된 서비스를 바인딩 시키기

일반적으로 바인딩 되지 않은 서비스는 자신을 실행시킨 컴포넌트와의 통신이 불가능하다.

통신을 하기 위해서는 바인딩 서비스를 생성해여야 하며, 이를 위해서 Binder 클래스를 상속하고 그 인스턴스를 onBind()에서 반환하는 방식을 사용해여야 한다.

이 방식을 사용하면 클라이언트(서비스를 시작한 컴포넌트)는 Binder를 받아 Service에서 제공하는 함수에 직접 엑세스 할 수 있다.

이렇게 하면 클라이언트와 서비스가 통신을 주고 받는 것이 가능해지는 것이다.

 

[서비스 코드]

public class Foreground_Service extends Service {
	..
	  //Binder 클래스는 Ibinder는 Implement한 Class
	  public class Mybinder extends Binder {
	       public Foreground_Service getService(){
	           return Foreground_Service.this;
	       }
	  }
	
      private MyBinder mBinder = new MyBinder();
	  @Nullable
	  @Override
	  //IBinder는 Interface임
	  public IBinder onBind(Intent intent) {
	        return mBinder;
	  }
}

 

[액티비티 코드]

public class MainActivity extends AppCompatActivity {
...

    bindService(new Intent(this, Foreground_Service .class),mConnection,BIND_AUTO_CREATE); //서비스생성 & 바인드

    ServiceConnection mConnection = new ServiceConnection() {
        //바인드가 됐을 때 호출
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Foreground_Service.Mybinder binder = (Foreground_Service.Mybinder) service;
            Foreground_Service mservice = binder.getService();
        }
l
        //(예기치 못한 상황)안드로이드 OS에 의해 바인드가 끊겼을 때
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mservice = null;
            mbound = false;
        }
    }

 


서비스의 생명주기

onCreate()

시스템은 서비스(Service)가 처음 생성되었을 때(서비스가 onStartCommand() 또는 onBind()를 호출하기 전) onCreate()를 딱 한 번 호출한다. 즉 서비스가 이미 실행중인 경우는 이 메서드는 호출이 되지 않는다.

 

onStartCommand (Intent intent, int flags, final int startId)

최초에 startService()를 호출했을 때 onCreate()가 호출되고, 그 이후에 startService()를 호출 시 onStartCommand()가 호출된다.

onStartCommand()가 실행되면 서비스가 시작되고 백그라운드에서 무한히 실행될 수 있다.

서비스를 실행한 컴포넌트가 종료되도 서비스는 종료되지 않는다.

서비스 작업 완료 시, 서비스 내에서 stopSelf()를 호출하거나, 다른 컴포넌트에서 stopService()를 호출하여 종료시켜야 한다.

또한, onStartCommand()메서드는 반드시 정수를 반환해야 하는데 아래 2가지 CASE를 선택하여 return 시킬 수 있다.

  • START_NOT_STICKY
    시스템이 서비스를 중단시키면 서비스를 재생성하지 않는다. 이는 서비스가 불필요하게 실행되는 일을 피할 수 있는 가장 안전한 옵션이다.
  • START_STICKY
    시스템이 서비스를 중단하면 서비스를 다시 생성하고 onStartCommand()를 호출한다. 이것은 미디어 플레이어 같은 무한히 실행중인 앱에 적합하다.

onBind(Intent intent)

시스템은 다른 컴포넌트가 해당 서비스에 바인딩되고자 하는 경우(bindService 호출) onBind()를 호출한다.

이 메소드를 구현할 때 클라이언트가 서비스와 통신을 주고 받기 위해 사용할 인터페이스를 제공해야 하며 이때 IBinder를 반환한다.

이 메소드는 필수로 구현해야 하지만, 바인딩을 허용하지 않으려면 null을 반환하면 된다.

 

onUnbind(Intent intent)

바인드가 해제될 때 호출된다.

클라이언트가 서비스와의 접속을 마치려면 unbindService를 호출하면 된다.

일반적으로 백그라운드에서와 같이 바인딩이 필요없는 상황에서는 바인드를 끊어주어야 하며, 이를 지키지 않을 경우 메모리 누수로 이어질 수 있다.

        //백그라운드에서 바인딩이 필요없는 상황에서 바인드를 끊어준다.(앱이 동작하고있을 때만 바인드 유지)       
        @Override
        protected void onStop() {
            super.onStop();
            if (mbound) {
                unbindService(mConnection);
                mbound = false;
            }
        }

        //그리고 onstart()에서 다시 바인드 시켜준다.
        @Override
        protected void onStart() {
            super.onStart();
            bindService(new Intent(this, Foreground_Service.class), mConnection, BIND_AUTO_CREATE); //서비스생성 & 바인드

        }

 

onDestroy()

시스템이 이 메소드를 호출하는 것은 서비스를 더이상 사용하지 않고 소멸시킬 때이다. 서비스는 스레드, 등록된 리스너 등의 리소스를 정리하기 위해 이것을 구현한다. 이는 서비스가 수신하는 마지막 호출이다.