[Kotlin] 9장. 앱 개발 - Service, Content Provider

Hwichan Ji·2020년 12월 28일
1

Kotlin

목록 보기
9/11
post-thumbnail

이것이 안드로이드다 with 코틀린(고돈호 지음) 으로 공부한 내용을 정리한 글입니다.

Serivce

Service는 안드로이드의 4가지 컴포넌트 중 하나로 백그라운드 작업을 위한 컴포넌트입니다. 다만 서비스는 워커 쓰레드가 아닌 메인 쓰레드에서 실행되며 따라서 워커 쓰레드를 통해 백그라운드 작업을 처리하는 것과는 다른 동작 방식을 가집니다.

서비스의 실행 방식

서비스는 Started ServiceBound Service 두 가지 형태로 실행됩니다.

Started Service

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

Singleton
클래스의 인스턴스를 오직 하나만 생성해서 유지하는 디자인 패턴

Bound Service

Bound Service는 서비스가 액티비티에 바인드되며 액티비티와 값을 주고받을 필요가 있을 때 사용합니다. 또 Bound Service는 bindService() 메서드로 호출하며 하나의 Bound Service를 여러 액티비티가 사용할 수 있습니다. 다만 바인드된 액티비티가 모두 종료되면 서비스도 종료된다는 제약이 존재합니다.

서비스 만들기

서비스 클래스

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

// MyService.kt
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에 등록되야 사용할 수 있습니다.

<!-- AndroidManifest.xml-->
<service
    android:name=".MyService"
    android:enabled="true"
    android:exported="true"/>

Started Service 실행과 종료

fun serviceStart(view: View) {
    val intent = Intent(this, MyService::class.java)
    intent.action = MyServcie.ACTION_START
    startServcie(intent)
}

fun serviceStop(view: View) {
    val intent = Intent(this, MyService::class.java)
    stopService(intent)
}

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

Bound Service 연결과 종료

Bound Service를 액티비티와 연결하기 위해선 BinderServiceConnection을 생성해야 합니다.

// 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
내부 범위에서 외부 범위에 접근하기 위한 한정자

onServiceConnected() 메서드는 서비스가 연결되면 호출되고 onServiceDisconnected() 메서드는 서비스가 비정상적으로 종료되었을 때 호출됩니다. 따라서 isService 변수를 두고 현재 서비스가 연결되어 있는지를 확인하는 로직이 필요합니다.

var myService: MyService? = null
var isService = false
val connection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
        val binder = service as MyService.MyBinder
        myService = binder.getService()
        isService = true
    }
    
    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 (isService) {
        unbindService(connection)
        isService = false
    }
}

Bound Service의 메서드 호출하기

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

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

// MainActivity.kt
myService?.serviceMessage()

포어그라운드 서비스

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

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

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

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

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

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)

Content Provider

Content Provider는 안드로이드의 4가지 컴포넌트 중 하나로 앱의 데이터를 다른 앱에 제공하는 컴포넌트입니다. 하지만 실제로 콘텐트 프로바이더를 구현하는 일은 거의 없고, 주로 다른 앱이나 안드로이드 OS에 이미 구현되어 있는 콘텐트 프로바이드로부터 데이터를 받습니다. 이때 사용되는 도구가 Content Resolver입니다.

Content Resolver 사용하기

Content Resolver로 MediaStore에서 미디어 정보를 읽어오는 과정은 다음과 같습니다.

  1. 데이터의 종류에 따른 MediaStore 상의 주소 정의
val listUrl = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
  1. 가져오고 싶은 데이터의 정보를 정의
val proj = arrayOf(MediaStore.Audio.Meida._ID, MediaStore.Audio.Media.TITLE)
  1. 데이터 클래스를 정의(필수는 아님)
data class Music (val id: String, val title: String)
  1. 콘텐트 리졸버가 제공하는 query() 메서드로 쿼리를 실행
val cursor = contentResolver.query(listUrl, proj, 검색 조건, 조건의 값, 정렬 순서)
profile
안드로이드 개발자를 꿈꾸는 사람

0개의 댓글