본문 바로가기

Android

[Android] 비동기 처리를 위한 방법

728x90

동기와 비동기란?

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

비동기(Asynchronous) : 어떤 작업을 수행하지만 완료와 상관없이 계속해서 작업을 할 수 있는 방식

 

비동기가 필요한 이유?

애플리케이션이 실행되면 메인액티비티가 메모리에 로드되고, Main Thread를 포함한 프로세스가 생성되게 됩니다.

 

Main Thread에서 5초 이상의 작업을 실행하면 ANR(애플리케이션 응답 없음)이 발생하며, 안드로이드에서 초당 60프레임을 지원하는데, UI를 그리는 작업 중 한 프레임을 16ms 내에 그려내지 못하면 jank(프레임 누락으로 인해 끊겨보이는 현상)가 발생합니다.

 

즉, 안드로이드에서는 Main Thread는 중요하며 원활한 작업을 위해선 비동기가 꼭 필요하다는 것 입니다.

 

새로운 Thread를 생성해, 무거운 작업을 진행하고 해당 결과를 UI를 그리는 작업에 사용하고 싶다면, Handler가 필요한데, 이유는 UI 관련 작업은 Main Thread에서만 가능하기 때문입니다.

 

Thread란?

프로세스 내에서 "순차적으로 실행되는 실행 흐름"의 최소 단위를 의미합니다. main() 함수로부터 시작되는 최초 실행 흐름 또한 하나의 스레드이며, 이를 메인 스레드라고 합니다.

안드로이드 앱에서 메인 스레드는 Message Queue 수신을 대기하는 루프를 실행하며, 사용자 입력과 시스템 이벤트, 화면 그리기 등의 message가 수신되면 각 메시지에 매핑된 핸들러의 메서드를 실행합니다.

 

 

Handler란?

Thread의 작업을 관리하고, Thread간 작업을 전달는 역할을 하는 클래스입니다.

 

활용에 대한 이야기

화면의 버튼을 클릭하면 MAIN THREAD에서 NEW THREAD가 실행되도록 만들면 될 것입니다. NEW THREAD의 작업 상황을 프로그레스바를 통해 확인하고 싶어서, NEW THREAD에서 진행 상황을 프로그레스바에 나타내게되면, 에러가 발생합니다.

안드로이드에선 화면 UI에 접근하려면 반드시 MAIN THREAD에서 실행되어야 하기때문에 NEW THREAD의 작업 진행 상황을 MAIN THREAD로 전달하여 UI에 표시하기위해 Handler를 통해 스레드 통신을 수행하여 해결합니다.

 

아래에서 스레드 통신에서 Handler의 동작을 확인해보겠습니다.

 

Handler, MessageQueue, Looper 역할

  1.  Handler를 통해 스레드의 외부 or 내부로부터 작업을 받습니다. 
  2. MessageQueue에 들어온 처리해야할 작업을 enqueue합니다. 
  3. Looper는 처리해야할 작업이 있는지 계속해서 확인하고 Handler에게 처리를 맡긴다.
  4. Handler는 handlerMessage() 메서드를 이용해 Looper에게서 받은 message 또는 runnable을 처리를 한다.

Main Thread는 위와 같이 동작합니다.

 

class MainActivity : AppCompatActivity() {
    // 메시지 종류를 식별하기 위해, what 변수에 전달할 값을 상수로 정의.
    private val MSG_A = 0
    private val MSG_B = 1
    private val MSG_R = -1
    private var sum = 0
    private lateinit var mhandler : Handler
    private lateinit var nhandler : Handler
    private lateinit var newThread : Thread

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val progressBar = findViewById<ProgressBar>(R.id.progressBar)
        val textView = findViewById<TextView>(R.id.textView)

        mhandler = object : Handler(mainLooper){
            override fun handleMessage(msg: Message) {
                when(msg.what){
                    MSG_A -> {
                        progressBar.setProgress(msg.arg1, true)
                        textView.text = "${msg.arg1} 짝수 입니다."
                    }
                    MSG_B -> {
                        progressBar.setProgress(msg.arg1, true)
                        textView.text = "${msg.arg1} 홀수 입니다."
                    }
                    MSG_R -> {
                        progressBar.setProgress(msg.arg1, true)
                        textView.text = "${msg.arg1} 초기화"
                    }
                }
            }
        }

        runTestThread()
    }

    private fun runTestThread() {

        newThread = Thread(object : Runnable{
            override fun run() {
                try {
                    while (!Thread.currentThread().isInterrupted()){
                        val message = mhandler.obtainMessage()

                        sum++

                        Thread.sleep(200)

                        if (sum == 100) {
                            sum = 0
                            message.what = MSG_R
                            message.arg1 = 0
                        }else if (sum % 2 == 1){
                            message.what = MSG_B
                            message.arg1 = sum
                        }else if(sum % 2 == 0){
                            message.what = MSG_A
                            message.arg1 = sum
                        }

                        mhandler.sendMessage(message)
                    }
                }catch (e : InterruptedException){
                    e.printStackTrace()
                    Thread.currentThread().interrupt()
                }finally {
                    println("종료")
                }

            }

        })

        newThread.start()
    }

    /*
    override fun onStop() {
        if (newThread.isAlive){
            newThread.interrupt()
        }

        super.onStop()
    }

    override fun onRestart() {
        super.onRestart()
        newThread.run()
    }
     */

    override fun onDestroy() {
        if (!newThread.isInterrupted) {
            println("종료 시작")
            newThread.interrupt()
        }

        super.onDestroy()
    }
}

 

 

 

jhpop.tistory.com/123

 

비동기

관련 키워드 Thread, UiThread, MainThread, Jank, ANR, Asynchronous, Synchronous, Blocking, Non-Blocking, Concurrency, Parallelism, Handler, Looper, MessageQueue, AsyncTask 왜 비동기가 필요한가? 안드..

jhpop.tistory.com

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

 

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

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

academy.realm.io

salix97.tistory.com/82

 

[Android] 안드로이드 - 핸들러란?

1. handler 의 사전적 의미 handler - a person who trains and is in charge of animals, especially dogs - someone who advises someone important - someone who carries or moves things as..

salix97.tistory.com