안드로이드 전체

안드로이드 컴포넌트 - Service

강용민 2021. 8. 4. 13:19

아래 모든 내용은 https://velog.io/@hwi_chance/Kotlin-9%EC%9E%A5.-%EC%95%B1-%EA%B0%9C%EB%B0%9C-Service-Content-Provider에서 갖고온 것이며, 이해를 위해 적어놓았다.

 

 

service는 안드로이드의 4가지 컴포넌트 중 하나로 백그라운드 작업을 위한 컴포넌트이다.

여기서 4가지 컴포넌트란 Activity,service,Broadcast Receiver,Content Provider를 뜻한다.

다만 서비스는 워커 쓰레드가 아닌 메인 쓰레드에서 실행되며 따라서 워커 쓰레드(멀티스레드기능중 하나)를 통해 백그라운드 작업을 처리하는 것과는 다른 동작 방식을 가진다.


service 실행 방식

서비스는 Started Service와 Bound Service 두가지 형태로 실행된다.

 

Started Service

Started Service는 서비스를 호출한 액티비티와는 관계 없이 독립적으로 동작하는 서비스로 startService() 메서드로 호출한다. 독립적으로 동작하기 떄문에 액티비티의 종료에 영향을 받지 않으며 Singleton으로 동작한다.

더보기

Singleton

클래스의 인스턴스를 오직 하나만 생성해서 유지하는 디자인 패턴(??아직 잘 이해 못함)

Bound Service

Bound Service는 서비스가 액티비티에 바인드되며 액티비티와 값을 주고받을 필요가 있을 때 사용한다.

또 Bound Service는 bindService() 메서드로 호출하며 하나의 Bound Service를 여러 액티비티가 사용할 수 있다. 다만 바인드 된 액티비티가 모두 종료되면 서비스도 종료된다는 제약이 존재한다.

 

(Q.우리 러닝어플을 만들 때는 Started Service쪽이 더 적합한 것 같은데 어떻게 생각하나?)

(A.런닝앱의 타이머 특성 상 다른 액티비티가 실행되어도 타이머는 유지되어야 하기에 Started Service방식으로 실행하는 것이 좋을 것 같다.다만 문제는 독립적으로 동작하기에 런닝앱이 중간에 에러가 떠 종료가 되면 종료가 되는지는 잘 모르겠다.)


서비스만들기

Service 클래스를 상속받아 서비스 클래스를 생성한다.

class MyService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        // Started Service에서 서비스 시작시 호출
    }
    override fun onBind(intent: Intent): IBinder {
        // Bound Service에서 서비스 연결시 호출
    }
}

서비스는 AndroidManifest.xml 에 등록되야 사용이 가능하며, application 내에 등록을 해야한다.

<service
    android:name=".MyService"	//class 등록
    android:enabled="true"		//??
    android:exported="true"/>	//??

서비스가 자동으로 재시작되게 하는 것이 일반적이다 보니 앱이 실행된 후에 startService 메소드를 이용해 서비스를 시작시키기만 하면 더 이상 startService 메소드를 호출할 일이 없지 않을까하는 의문이 들 수 있지만 그렇지 않다.

이미 알고 있는 것처럼 인텐트를 이용해 서비스를 시작시키면 인텐트 객체가 서비스로도 전달됩니다.

그리고 그 안에 부가데이터를 넣어 전달할 수 있으므로 서비스로 데이터를 전달하고 싶은 경우에는 startService가 더 자주 호출될 수 있습니다.

그런데 이 때의 startService 메소드는 서비스를 시작시키기 위한 목적이 아니라 명령이나 데이터를 전달하기 위한 용도로 사용됩니다.

이렇게 startService 메소드를 호출하면서 인텐트 안에 넣어 전달한 명령이나 데이터를 잘 처리할 수 있도록 onStartCommand라는 메소드를 사용할 수 있습니다.

새로운 서비스 클래스를 정의할 때는 다음과 같이 onCreate, onStartCommand, onDestroy 메소드를 재정의하는 경우가 많습니다.

 

Started Sevice 실행과 종료

fun serviceStart(view: View) {
    val intent = Intent(this, MyService::class.java)	//MyService 클래스의 런타임 참조를 얻음    
    intent.action = MyServcie.ACTION_START
    startServcie(intent)	//MyService의 서비스 시작
}

fun serviceStop(view: View) {
    val intent = Intent(this, MyService::class.java)
    stopService(intent)		//MyService의 서비스 종료
}

리플렉션(::)

더보기

 

원하는 코드의 런타입 떄의 위치(런타임 참조를 찾기위해 사용하는 기능. 예를 들어 MyService::class.java 라고 작성하면 MyService 클래스의 런타임 참조를 얻는것. .java가 붙는 이유는 코틀린 클래스를 자바 클래스로 변환해주기 위함.

(Q.왜 java클래스로 변한해주는 것인가? java를 사용하지 않는다면 그냥 코틀린클래스로 사용 해도되는가?)

 

Bound Service 연결과 종료 (아직 잘 모름)

Bound Service를 액티비티와 연결하기 위해선 Binder 와 ServiceConnection을 생성해야 한다.

바인더

더보기

안드로이드에서 바인더는 각각 독립된 프로세서들을 연결해 주는 역할을 한다.

안드로이드에서는 각 독립적으로 운영되는 프로세스, 특히 서비스의 기능을 이용할 수 있도록 제공하는 것이 바인더의 핵심이라고 하겠다.

응용 프로그램 개발자의 입장에서는 서비스를 제공하는 서비스 프로세서와 어떠한 식으로 연결하여 응용 프로그램이 운영 되는지 이해하기 위해 바인더에 대한 내용을 알아야 하고, 안드로이드 시스템을 개발하는 시스템 개발자의 입장에서는 기존 서비스의 변경이나 기능 추가 또는 새로운 서비스를 구성하기 위해 어떠한 표준 인터페이스를 통해 서비스를 구현해야 하는지 알아야 하기 때문에 바인더에 대해 이해를 하고 있어야 한다.

바인더에 대한 개념을 이해하기 위해서는 먼저 IPC의 개념에 대해 이해를 하고 있어야 한다.

https://www.oss.kr/info_techtip/show/32d5f561-b998-496c-a328-a58a5555e2c6

// MyService.kt
inner class MyBinder : Binder() {	//각각 독립된 프로세서들을 연결해 주는 역할
    fun getService(): MyService {
        // 액티비티와 서비스가 연결되면 이 메서드를 통해 서비스에 접근
        return this@MyService
    }
}
val binder = MyBinder()

override fun onBind(intent: Intent): IBinder {
    return binder
}

this@label

더보기

내부 범위에서 외부 범위에 접근하기 위한 한정자

onSeviceConnected() 메서드는 서비스가 연결되면 호출되고 onServiceDisconnected()메서드는 서비스가 비정상적으로 종료되었을 때 호출됩니다.

따라서 isService 변수를 두고 현재 서비스가 연결되어 있는지를 확인하는 로직이 필요합니다.

var myService : MyService? = null	//MyService객체 생성
var isService = false	//Service 사용 여부
val connection = object : ServiceConnection {
	override fun onServiceConnected(name: ComponentName, service: IBinder){	//서비스가 연결되면 호출
    	val binder = service as MyService.MyBinder
        myService = binder.getService()
        isService = true	//Service 실행됨을 알림
        }
        
        override fun onServiceDisconnected(name : ComponentName){	//서비스가 비정상적으로 종료되었을 때 호출)
        	isService = false
        }
  	}
    
fun serviceBind(view: View){
	val intent = Intent(this, MyService::class.java)
    bindService(intent, connection, Context.BIND_AUTO_CREATE)
}

fun serviceUnbind(view: View){
	if(isServie){
    	unbindService(connection)
        isService = false
    }
}

Bound Service의 메서드 호출하기

Bound Service는 Started Service와 달리 액티비티에서 서비스의 메서드를 직접 호출하여 사용할 수 있다.

// MyService.kt
fun serviceMessage() : String {
	return "Hello"
}

//MainActivity.kt
myService?.serviceMessage()

 

포어그라운드 서비스

기본적으로 서비스는 모두 백그라운드 서비스되며 포어그라운드로 사용하기 위해선 시스템에 알려줘야 합니다.

포어그라운드 서비스 사용 단계

  1. 포어그라운드 서비스 권한을 명시
  2. <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
  3. 서비스 실행
  4. startForegroung() 메서드를 통해 포어그라운드 서비스임을 시스템에 알림

포어그라운드 서비스 코드 작성

서비스를 포어그라운드로 사용하기 위해선 알림바에 알림을 함께 띄워야한다.

class Foreground : Service() {
    val CANNEL_ID = "ForegroundChannel" // 알림에 사용될 채널
    
    // ...
    
    fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // 알림은 채널 단위로 동작
            val serviceChannel = NotificationChannel(
                CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT      
            )
        }
        val manager = getSystemService(
            NotificationManager::class.java
        )
        manager.createNotificationChannel(serviceChannel)
    }
    
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        createNotificationChannel()
        
        val notification:Notification = NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTile("Foreground Service)
                .setSmallIcon(R.mipmap.ic_launcher_round)
                .build()
                
        startForeground(1, notification)
        
        return super.onStartCommand(intent, flags, startId)
    }
}

액티비티에서 서비스 실행

포어그라운드 서비스는 ContextCompat.startForegroundService()를 사용해서 실행해야합니다.

val intent = Intent(this, Foreground::class.java)
ContextCompat.startForegroundService(this, intent)

 

'안드로이드 전체' 카테고리의 다른 글

안드로이드 컴포넌트  (0) 2021.08.06
안드로이드 컴포넌트-Activity  (0) 2021.08.06
1.안드로이드 컴포넌트 4가지  (0) 2021.08.04
코루틴 개념  (0) 2021.08.04
.2 레이아웃  (0) 2021.08.04