GDSC HUFS 3기/Android with Kotlin Team 5

[5팀] 코틀린 안드로이드 기초강의_46~48 | 코루틴으로 이미지 불러오기, 서비스, 포그라운드

Yoon-Min 2021. 11. 30. 18:35

이 글은 이것이 안드로이드다 with 코틀린(개정판)을 참고하여 작성하였습니다.

작성자 : 윤승민

개발환경은 Windows, Android Studio입니다. 

 

 

1. 코루틴으로 이미지 다운로드

코루틴??

비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴

 

동시성 프로그래밍 개념을 코틀린에 도입한 것이 코루틴이다.

 

코루틴의 기능

  • 경량
  • 메모리 누수 감소
  • 기본으로 제공되는 취소 지원
  • Jetpack 통합

 

코루틴을 사용해서 이미지 불러오기

 

textView에 이미지 URL을 입력해서 button을 클릭하면 이미지가 출력되는 방식을 구현해본다.

 

1. 다운로드 버튼을 클릭

 

2. 코루틴에서 이를 인식

 

3. 보이지 않게 설정한 프로그래스 바를 활성화시켜서 이미지가 로드 중임을 표현한다.

binding.buttonDownload.setOnClickListener { //'다운로드'버튼 클릭 이벤트
    CoroutineScope(Dispatchers.Main).launch { //코루틴 작동
        binding.progress.visibility = View.VISIBLE // 프로그래스 바를 보이게 전환
        val url = binding.editUrl.text.toString() //loadImage함수에 인자로 넘겨준다.

        val bitmap = withContext(Dispatchers.IO) {
            loadImage(url) 
        }

        binding.imageView.setImageBitmap(bitmap) // 비트맵을 이용해 이미지 삽입
        binding.progress.visibility = View.INVISIBLE // 이미지가 삽입되면 프로그래스 바는 다시 안보이게 설정
    }
}

 

 4. 비트맵을 꺼내기

💡 비트맵 - 안드로이드에서 이미지를 표현하기 위해 사용되는 객체

 

// 비트맵을 리턴하는 함수
suspend fun loadImage(imageUrl : String) : Bitmap {
    val url = URL(imageUrl)
    val stream = url.openStream()

    return BitmapFactory.decodeStream(stream)
}

 

5. 결과를 확인해본다.

 

이미지 URL을 Paste하고 다운로드 버튼을 클릭한다.
이미지 URL을 textView에 Paste
무서운 야생의 북극곰이 등장했다.

 


 

2. 서비스

 

안드로이드의 서비스- 이게 뭔가요??

  • 4대 컴포넌트 중에 하나
  • 백그라운드에서 동작 (사용자에게 보이지 않음 → UI가 없다.)
  • 백그라운드에서 동작하기 때문에 다른 작업을 해도 영향 없이 계속 실행된다.

 

서비스의 유형

 

포그라운드

  • 포그라운드 서비스는 사용자에게 잘 보이는 몇몇 작업을 수행
  • 예를 들어 오디오 앱이라면 오디오 트랙을 재생할 때 포그라운드 서비스를 사용
  • 포그라운드 서비스는 사용자가 앱과 상호작용하지 않을 때도 계속 실행
💡 ex) 음악 어플에서 노래를 재생시키고 노래 들으면서 웹툰 보기

 

백그라운드

  • 백그라운드 서비스는 사용자에게 직접 보이지 않는 작업을 수행

 

 

바인드

  • 바인딩된 서비스는 클라이언트-서버 인터페이스를 제공하여 구성 요소가 서비스와 상호작용
  • 프로세스 간 통신(IPC)으로 수행
  • 바인딩된 서비스는 또 다른 애플리케이션 구성 요소가 이에 바인딩되어 있는 경우에만 실행

 


 

그렇다면 서비스를 어떻게 만드나요?

 

서비스를 생성하려면 서비스의 하위 클래스를 생성해야 한다.

 

class MyService : Service() { // MyService라는 이름의 서비스의 하위 클래스 생성
	// 콜백 메서드를 재정의
}

 

몇 개의 콜백 메서드를 재정의해야 한다.

중요한 콜백 메서드는 다음과 같다.

 

 

onStartCommand()

 

시스템이 이 메서드를 호출하는 것은 또 다른 구성 요소가 서비스를 시작하도록 요청하는 경우다.

이때 startServicer()를 이용한다.

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

서비스를 중단하려면 본인이 직접 중단시켜야 함 → ( stopSelf() or stopService()를 호출하여 중지)

 

 

onCreate()

 

서비스가 처음 생성되었을 때 호출되고 이후에는 호출되지 않음 → 일회성 설정 절차를 수행

 💡 onStartCommand() 호출 전에 먼저 호출됨

 

 

onBind()

 

다른 구성 요소가 해당 서비스에 바인딩되고자 하는 경우 이 메서드를 호출

메서드를 구현할 때에는 클라이언트가 서비스와 통신을 주고받기 위해 사용할 인터페이스를 제공

이때 IBinder를 반환

 

 

서비스의 생명주기를 보면 서비스의 시작과 끝을 알 수 있다.

 

 

서비스를 직접 구현해보자

 

레이아웃 XML에 버튼을 생성해서 버튼을 누르면 동작을 하도록 설정

3개의 버튼을 만들어본다.

 

  • 서비스 시작을 담당하는 '서비스 시작'버튼
  • 서비스 중지를 담당하는 '서비스 중지'버튼
  • 서비스의 바인드를 담당하는 '서비스 바인드'버튼

1. 버튼을 클릭하면 특정 동작을 하도록 onClick설정 (특정 동작을 명령하는 함수를 생성)

 

2. 버튼을 만들었으면 서비스 클래스를 만든다.

class MyService : Service() {

    inner class MyBinder : Binder(){
        fun getService():MyService{
        	return this@MyService
        }
    }

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

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
				// '서비스 시작'버튼을 누르면 log가 출력되도록 action 설정
        val action = intent?.action
        when(action){
            ACTION_CREATE -> create()}
            
        return super.onStartCommand(intent, flags, startId)
    }
			
    fun create(){
    	Log.d("서비스", "create()가 호출됨")
    }
}

 

3. 메인 액티비티에 함수를 생성하고 실행하여 버튼을 누른 뒤 'create()가 호출됨'로그를 확인

// 서비스 시작을 위해 함수인 serviceStart호출
fun serviceStart(view:View){
    serviceIntent.action = MyService.ACTION_CREATE
    startService(serviceintent)
}

// 서비스 중지를 위해 함수인 serviceStop호출
fun serviceStop(view:View){
    stopService(serviceIntent)
}

// bindService의 인자로 줄 connection 정의
var myService:MyService? = null
var isService = false
val connection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName?, iBinder: IBinder?) {
        isService = true
        val binder = iBinder as MyService.MyBinder
        myService = binder.getService()
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        isService = false
    }
}

// 바인드를 위해 bindService 호출
fun serviceBind(view:View){
    bindService(intent, connection,Context.BIND_AUTO_CREATE)
}

 

 


 

3. 포그라운드

 

포그라운드의 역할특징

💡 상태 표시줄 알림을 표시한다.

ex)

  • 음악을 재생하는 음악 플레이어 앱. 알림에는 현재 재생 중인 노래가 표시
  • 사용자의 허가를 받은 후 포그라운드 서비스에서 사용자의 달리기를 기록하는 피트니스 앱
  • 알림은 현재 피트니스 세션 동안 사용자가 이동한 거리를 표시

 

포그라운드를 구현해보자

 

1. 포그라운드 클래스를 작성하자.

class Foreground : Service() {

    val CHANNEL_ID = "FGS153"
    val NOTI_ID = 123

    fun createNotificationChannel(){
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
            val serviceChannel = NotificationChannel(CHANNEL_ID,"FOREGROUND",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 = NotificationCompat.Builder(this,CHANNEL_ID) //띄울 노티피케이션을 만들어서 사용할 채널까지 같이 넣어준다.
            .setContentTitle("Foreground Service") //노티피케이션의 제목부분
            .setSmallIcon(R.mipmap.ic_launcher_round) //아이콘
            .build()

        startForeground(NOTI_ID,notification) //포그라운드에서 실행되도록 요청
        return super.onStartCommand(intent, flags, startId)
    }

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

 

2. 서비스를 시작하자.

//포그라운드 시작
fun serviceStart(view:View){
    var intent = Intent(this,Foreground::class.java)
    ContextCompat.startForegroundService(this,intent) //포그라운드 시작을 알림
}

fun serviceStop(view: View){
    var intent = Intent(this,Foreground::class.java)
    stopService(intent) //종료를 알림
}

 

3. 결과를 확인해보자

서비스 시작을 누른다.