관련 키워드

Thread, UiThread, MainThread, Jank, ANR, Asynchronous, Synchronous, Blocking, Non-Blocking, Concurrency, Parallelism, Handler, Looper, MessageQueue, AsyncTask

 

 


왜 비동기가 필요한가?

 

안드로이드에서는 UI를 그리는 메인스레드가 매우 중요하다.

 

애플리케이션이 실행되고 메인액티비티가 메모리에 로드되면

메인스레드를 포함한 프로세스가 생성된다.

 

안드로이드는 초당 60프레임을 지원하고 한 프레임은 16ms안에 그려져야 한다.

16ms 안에 화면을 그리는 작업이 완료되지 않으면 쟁크가 발생한다.

(쟁크; jank : 프레임 누락으로 인해 끊겨보이는 현상)

5초 이상의 긴 작업을 메인스레드에서 실행한다면 ANR이 발생한다.

(ANR; Application Not Responding)

 

결국 안드로이드에서 UI스레드가 원활하게 돌아가기 위해서는 비동기가 필수로 사용된다.

 

 

 

 

 


그럼 비동기는 무엇인가?

 

동기(Synchronous)

작업을 수행하고 그 작업이 완료될 때까지 다른 작업을 하지못하고 기다리는 방식

 

예를 들어 편의점에서 혼자 일을 한다면 카운터 일 물건 정리나 청소 등의 일을 하는 동안 전혀할 수 없다.

즉, 메인스레드가 UI를 그려야만 하는데 무거운 작업(시간이 오래걸리는)을 한다면 그 동안 UI를 그릴 수 없어 화면이 멈춰있을 것이다.

 

 

비동기(Asynchronous)

어떤 작업을 수행하지만 완료와 상관없이 계속해서 작업을 할 수 있는것

 

편의점이 바빠 한 명의 직원을 더 고용했다. 한명은 계속해서 카운터 일을 보고 물건 정리나 청소 등의 일은 다른 직원에게 시키면 카운터가 비는 일이 없다.

메인 스레드는 계속해서 UI를 그리고, 무거운 작업은 추가적인 스레드에게 시키면 된다.

 

 

 

 

사실 위에서 설명한 동기는 Sync-Bloking이고, 비동기는 Async-NonBlocking이다.

Sync-NonBlocking, Async-Blocking은 거의 사용되지 않기 때문에 우선은 위에 설명만 알아도 된다.

하지만 더 자세히 알고 싶다면 아래 포스팅 참조

 

Sync, Async, Blocking, NonBlocking 추천 포스팅

https://homoefficio.github.io/2017/02/19/Blocking-NonBlocking-Synchronous-Asynchronous/

 

Blocking-NonBlocking-Synchronous-Asynchronous

꽤 자주 접하는 용어다. 특히나 요즘들어 더 자주 접하게 되는데, 얼추 알고는 있고 알고 있는게 틀린 것도 아니지만, 막상 명확하게 구분해서 설명하라면 또 만만치가 않은.. 그래서 찾아보면 ��

homoefficio.github.io

 

 

 

 

 


그럼 어떻게 구현하지?

 

무거운 작업을 새로운 스레드에서 한다고 치자.

작업이 끝나고 그 결과를 가지고 UI에 보여주고 싶다.

UI관련 작업은 메인스레드에서만 해야하는데 어떻게 보여줄까?

 

이때 Handler가 필요로 하다.

 

Handler는 스레드의 작업을 관리하고 스레드간에 작업을 전달하기 위한 용도로 존재한다.

 

굉장히 오랜시간동안 Handler가 UI를 변경하기 위한 용도라고만 생각해왔다.

작업스레드에서 UI를 처리하기 위해 주로사용하다 보니 그런 고정관념이 생겼던 것 같다.

 

 

새로운 스레드에서 작업을 마치면 결과를 가지고 변경할 UI 작업을

Handler를 통해 메인스레드에 전달하면 된다.

 

 

 

 

 


Handler 예시

 

    private val handler = MainHandler(this)

    companion object {
        const val HANDLE_WORK_UPDATE_PROGRESS = 1
        const val HANDLE_WORK_DONE = 2

        class MainHandler(
            activity : MainActivity
        ) : Handler() {
            private val activity = WeakReference<MainActivity>(activity)
            override fun handleMessage(msg: Message) {
                when(msg.what) {
                    HANDLE_WORK_UPDATE_PROGRESS -> activity.get()?.updateProgress(msg.obj as Int)
                    HANDLE_WORK_DONE -> activity.get()?.showDoneMessage()
                }

            }
        }
    }

    private fun processAndUpdateUI() {
        Thread {
            for(i in 0..10) {
                Thread.sleep(1000L)

                handler.obtainMessage().apply {
                    what = HANDLE_WORK_UPDATE_PROGRESS
                    val progress = i*10
                    obj = progress
                }.let {
                    handler.sendMessage(it)
                }
            }
            handler.sendEmptyMessage(HANDLE_WORK_DONE)
        }.start()
    }

    fun showDoneMessage() {
        tv_main_state.text = "completed"
    }

    fun updateProgress(progress: Int) {
        tv_main_state.text = "progress $progress"
    }

 

processAndUpdateUI()에서 10초동안 작업이 진행하며

매 1초마다 진행정도를 전달하는 UI작업 (HANDLE_WORK_UPDATE_PROGRESS)

10초가 모두 지나고 완료를 표시하는 UI작업 (HANDLE_WORK_DONE)

두가지를 Handler를 통해 처리하는 간단한 예시이다.

 

Handler에 메세지 외에도 Runnable을 던질 수도 있다.

 

AsyncTask, runOnUiThread 역시 내부적으로는 Handler를 이용하고 있다.

 

 

 

 

 


Handler의 동작 원리

 

 

위에 그림에서 Handler, MessageQueue, Looper역할을 알아야 한다.

 

 

1. Handler를 통해 스레드의 외부 혹은 내부에서 작업을 받는다.

2. 처리해야할 작업을 MessageQueue에 enqueue한다.

3. Looper는 처리해야할 작업이 있는지 계속해서 확인하고 Handler에게 처리를 맡긴다.

4. Handler는 해당 작업에 맞는 처리를 한다.

 

 

메인스레드에서 위와 같이 동작한다고 보면된다.

 

우리가 새로 만드는 작업 스레드는 여기서 Looper가 없기 때문에 직접 만들고 loop해줘야 한다.

그렇기 때문에 Looper를 포함한 HandlerThread를 지원한다. 

 

 

추천 포스팅

https://academy.realm.io/kr/posts/android-thread-looper-handler/

 

안드로이드 백그라운드 잘 다루기 Thread, Looper, Handler

안드로이드 백그라운드 잘 다루기 Thread, Looper, Handler

academy.realm.io

 

 

 

 


실제 비동기 사용

 

위에 Handler 예시를 LiveData를 사용해서 바꿔보았다.

 

    private val stateText = MutableLiveData<String>()

    private fun observe() {
        stateText.observe(this, Observer {
            tv_main_state.text = it
        })
    }

    private fun initViews() {
        btn_main_update_ui.setOnClickListener { processAndUpdateUI() }
        stateText.value = "start progress"
    }

    private fun processAndUpdateUI() {
        Thread {
            for(i in 0..10) {
                Thread.sleep(1000L)
                stateText.postValue("progress ${i*10}%")
            }
            stateText.postValue("done")
        }.start()
    }

 

LiveData를 공부없이 써서 맞게 쓴것인지 모르겠지만..

대략 반으로 코드 라인이 줄었다.

 

실제 회사에서 Handler를 UI 변경을 위해 사용하는 일은 드물 것이라 생각한다.

비동기를 더 쉽게하기 위한 정말 많은 기술과 조합이 있기 때문이다.

 

대표적으로 사용하는 비동기 기술로 Coroutine과 Rx가 있다.

 

 

 

 

 

'Android > Common' 카테고리의 다른 글

수명주기; Lifecycle  (0) 2020.06.22
안드로이드 개발자 로드맵  (2) 2020.05.07
안드로이드 프로젝트 루틴  (0) 2020.02.20

+ Recent posts