[android] Service

sundays·2023년 3월 20일
0

android

목록 보기
12/18

background에서 오래 작업을 수행해야 하는 구성요소를 원한다면 service를 채택합니다. 흔하게는 UX는 존재 하지 않고 다른 애플리케이션을 작업하더라도 back-ground에서 실행됩니다. 이외에도 구성 요소를 서비스에 바인딩하여 서비스하거나 IPC와 같은 다른 프로세스와의 통신을 수행할 수도 있습니다.

  • fore-ground
    audio앱이라면 오디오 트랙과 같이 알림을 표시하여 사용하고 만약 사용자가 상호작용 하지 않을때도 유지할수 있습니다.

  • back-ground
    사용자에게 직접 보이지 않는 작업을 수행할 때 사용합니다.

  • bind
    bindService() 를 사용하여 다른 애플리케이션 구성요소가 바인딩 되어있는 경우에 사용됩니다. 프로세스 간 통신 (IPC) 를 수행하여 결과를 받거나 실행 될 수 있습니다. 구성요소를 여러개 한꺼번에 바인딩 하거나 바인딩이 모두 해제되면 서비스가 소멸됩니다.

서비스는 자신의 호스팅(hosting) process의 기본 스레드에서 실행됩니다. 자신의 스레드를 직접 생성하지 않고 별도로 지정하지 않는 한 별도의 프로세스에서 실행되지 않습니다. CPU의 작업이 길거나 지속적인 경우 서비스 내에 스레드를 생성하는 방법을 사용하도록 합시다. 만약 이렇게 하지않고 애초에 서비스를 실행하지 않고 스레드를 사용하는 방법도 있습니다.

Service vs Thread

만약 사용자가 상호작용하는 동안 기본 스레드 밖에서 작업을 수행해야 한다면 새 스레드를 생성해야 합니다. 예를 들면 액티비티가 fore-ground에서만 음악을 재생해야 할 경우 onCreate()안에 스레드를 생성하고 onStart()에서 실행해서 onStop()에서 중단하는 방식을 사용하고 Thread가 아닌 AsyncTask 나 HandlerThread를 사용하는 것도 방법입니다.

서비스를 꼭 사용해야 하는 경우는 메인스레드를 사용하기에 적합한 집약적인 작업이나 차단 작업이 필요한 경우에는 서비스내에서 스레드를 생성하여 사용하여야 합니다

서비스의 생성

서비스를 생성시 Service의 하위 클래스를 생성하고 콜백 매서드를 몇가지 정의해야 하며 서비스에 바인딩할 구성 요소로 override해서 사용할 수 있는 callback method들 입니다

class ExampleService : Service() {
    private var startMode: Int = 0             // indicates how to behave if the service is killed
    private var binder: IBinder? = null        // interface for clients that bind
    private var allowRebind: Boolean = false   // indicates whether onRebind should be used

    override fun onCreate() {
        // 서비스가 생성됨
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // 서비스가 시작됨. startSerivce() 호출시
        return mStartMode
    }

    override fun onBind(intent: Intent): IBinder? {
        // 다른 구성요소가 해당 서비스에 바인딩되고자 하는 경우. bindSerivce() 호출시
        return mBinder
    }

    override fun onUnbind(intent: Intent): Boolean {
        // 모든 클라이언트를 언바인드 하고자 하는 경우. unbindService() 호출시
        return mAllowRebind
    }

    override fun onRebind(intent: Intent) {
    	// bindService()로 클라이언트가 바인드 되고 후에 onUnbind() 가 이미 호출 된 경우
    }

    override fun onDestroy() {
        // 서비스가 더 이상 사용되지않고 소멸되어 모든 리소스를 정리하는 경우
    }
}

서비스 호출 방법

  • startService()를 호출시
    onStartCommand()의 호출이 발생되며 stopSelf() 로 중단, 또는 stopService() 를 호출하여 중단될때까지 실행 중인 상태로 유지 됩니다

  • bindService() 를 호출시
    해당 서비스는 구성요소가 바인딩 된 경우에만 실행되며 서비스가 모든 클라이언트에서 바인딩이 해제되면 시스템이 이것을 소멸시키게 됩니다

그럼에도 불구하고 서비스는 종료가능성을 가지고 있습니다. 만약 서비스가 fore-ground가 아닌 back-ground에서 실행되고 장시간 실행되면 시스템이 우선순위가 낮아지면서 서비스가 종료될 가능성이 높아지게 되기 때문에 가능한 서비스의 리소스를 재사용하게 작성해서 서비스가 더 빠르게 작동가능합니다. 다만 onStartCommand()에서 반환값에 따라 다를 수 있습니다.

Manifest.xml

service는 application의 manifest.xml 에서 선언해야 합니다. application 하위에 요소로 추가합니다.

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

service를 실행할때 사용해야 하는 intent는 Explicit-intent를 사용하도록 해야 합니다. 이전의 activity가 어떤 것인지 작성해야 보안위험이 없으며 사용자가 어느 서비스가 시작되는 지 시각적으로 볼수 없게 되기 때문입니다 21버전에서부터 implicit-intent를 사용하게 되면 오류가 나게 되었습니다. 사용자에게 description 옵션을 주어서 특성의 대한 문장을 기재할 수도 있습니다.

IntentService

대부분의 서비스 작업들은 여러개의 서비스를 동시에 요청하지 않고 단방향으로 작업되는 Service 일 것입니다. 이것을 의도로 만들어진것이 IntentService입니다.

/**
 * A constructor is required, and must call the super [android.app.IntentService.IntentService]
 * constructor with a name for the worker thread.
 */
class HelloIntentService : IntentService("HelloIntentService") {

    /**
     * The IntentService calls this method from the default worker thread with
     * the intent that started the service. When this method returns, IntentService
     * stops the service, as appropriate.
     */
    override fun onHandleIntent(intent: Intent?) {
        // Normally we would do some work here, like download a file.
        // For our sample, we just sleep for 5 seconds.
        try {
            Thread.sleep(5000)
        } catch (e: InterruptedException) {
            // Restore interrupt status.
            Thread.currentThread().interrupt()
        }

    }
}

IntentService 는 Service를 상속하여 만들어진 클래스로 Service 보다 사용법이 더욱 간단하여 단순작업에 용의합니다.
그러나 IntentService 가 현재는 deprecated되어 androidx.work.WorkManager 또는 androidx.core.app.JobIntentService 를 사용하기를 고려하면 됩니다. 이부분에 대한 내용은 따로 포스팅이 필요할 것 같습니다.

서비스 호출

서비스를 실행할때 explicit-intent를 startService() 또는 startForegroundService()에 전달하도록 하면 됩니다. 그렇게 하면 android-system이 service의 onStartCommand() 를 호출하고 여기에 서비스를 지정하는 Intent를 전달합니다.

Intent(this, HelloService::class.java).also { intent ->
    startService(intent)
}

만약 앱이 fore-ground에 있지 않으면 back-ground service 생성 및 사용에 대해 제한해야 합니다. 만약 fore-ground service를 생성해야 하면 startForegroundService()를 호출하여 fore-ground 로 승격될 것이라고 알 수 있게 합니다 (버전 26이상)

서비스 중단

시스템 메모리를 회복하고 서비스가 onStartCommand() 반환 후 계속 실행되는 경우를 제외하고는 수명주기를 직접 관리해야 합니다. 스스로 stopSelf() 를 호출하여 종료하거나 다른 구성요소에서 stopService() 를 호출하여 중지 시킬 수 있습니다.

만약 onStartCommand() 에 여러 요청을 동시에 처리해야하는 경우에 처리가 끝난후에 바로 서비스가 종료되게 하면 안됩니다. 이렇게 하면 프로세스가 겹치는 문제가 발생하여 다른요청까지 중단될 가능성이 있습니다. stopSelf(int) 를 사용하여 가장 최근 시작 요청을 기준으로 ID값을 차례대로 부여하여 ID값이 일치하는 서비스만 중단되게 할 수 있습니다.

바인딩 서비스 호출

startService()가 아닌 bindSerivce()를 호출하여 애플리케이션 구성 요소를 서비스에 바인딩하여 연결을 유지하여 서비스를 호출하는 방법입니다.
이방법은 onBind() callback method를 override하여 Ibinder 를 반환하도록 해야합니다.
bindService()를 호출하여 서비스에 있는 메서드를 호출하여 시작하여 만약 바인딩 된 구성 요소가 없으면 시스템에서 소멸시키게 됩니다. onStartCommand()와는 다른 종료방법을 가집니다

	override fun onStart() {
        super.onStart()
        // Bind to LocalService
        Intent(this, LocalService::class.java).also { intent ->
            bindService(intent, connection, Context.BIND_AUTO_CREATE)
        }
    }

    override fun onStop() {
        super.onStop()
        unbindService(connection)
    }

서비스에 한꺼번에 여러개 바인딩될때 unbindService() 를 호출하여 바인딩을 해제 할 수 있습ㄴ다

fore-ground service

이 경우애는 시스템이 메모리가 부족해도 시스템이 종료할 후보가 될 수 없습니다. 상태 표시줄에 대한 알람과 서비스를 중단하거나 fore-ground에서 제거하지 않는 한 알람을 해제 할 수 없습니다. 음악 서비스의 음악을 재생하는 오디오 플레이어는 fore-ground에서 실행되게 설정해야 합니다.
사용자가 이 앱을 사용하는 것이 계속 표시되어 이 알람이 사용자와 상호작용할 액티비티를 시작하게 합니다. 이 경우에는 startForeground() 를 사용합니다.

val pendingIntent: PendingIntent =
        Intent(this, ExampleActivity::class.java).let { notificationIntent ->
            PendingIntent.getActivity(this, 0, notificationIntent, 0)
        }
        
// 상태 표시줄
val notification: Notification = Notification.Builder(this, CHANNEL_DEFAULT_IMPORTANCE)
        .setContentTitle(getText(R.string.notification_title))
        .setContentText(getText(R.string.notification_message))
        .setSmallIcon(R.drawable.icon)
        .setContentIntent(pendingIntent)
        .setTicker(getText(R.string.ticker_text))
        .build()

startForeground(ONGOING_NOTIFICATION_ID, notification) // ID는 0보다 큰 값

서비스 수명 주기

서비스의 수명 주기는 startService()bindService() 로 수명주기가 다릅니다

서비스의 전체 수명

onCreate() - onDestroy()

서비스의 활성 수명

  • onStartCommand() 에서 시작되어 intent를 bindService()에 전달
  • onBind() 에서 시작되어 intent 를 bindSerivce()에 전달

결론

Service는 사용자가 fore-ground를 포함해서 보여지지 않는 작업을 장시간할때 주로 사용되고 있습니다. 그리고 여러번 호출하게 되도 인스턴스가 하나 이기 때문에 여러번 호출하게 되면 onStartCommand()가 호출되며 만약 호출되지 않으면 onCreate() 메서드를 실행하고 대기하는 상태가 됩니다.

Reference

profile
develop life

0개의 댓글