안드로이드 Thread

이영준·2023년 4월 24일
1

📌 쓰레드

프로세스 내에서 순차적으로 실행되는 실행 흐름의 최소 단위

  • 프로세스
    • 운영체제에 의해 메모리에 적재되어 실행되는 프로그램
    • 프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성
    • 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행한다.
  • 메인 쓰레드
    • 최초의 실행 시작점부터 순차적으로 진행되는 실행 흐름
    • 새로운 스레드가 오직 메인 스레드에 의해서만 실행될 수 있는것은 아님.

🔑 기본적인 스레드 실행문 RUN

        val thread = ThreadClass()
        thread.start()
    }

    inner class ThreadClass : Thread() {
        override fun run() {
            while(isRunning) {
                sleep(100)
                Log.d(TAG, System.currentTimeMillis().toString())
//                binding.helloTextview.text = System.currentTimeMillis().toString()
            }
        }
    }

쓰레드는 run을 부르며 실행이 시작된다.
stop 메소드도 있지만 사용이 권장되지는 않는다.

위 처럼 Thread를 상속받는 inner class(혹은 anonymous nested class)를 선언하여 사용할 수도 있고

        Thread{
            
        }.start()

쓰레드클래스를 바로 선언하여 사용하거나

        thread{

        }

안드로이드 내의 thread라는 함수를 사용할 수도 있다.

🔑 메인 쓰레드에서 View를 생성한다.

내가 만든 쓰레드가

    inner class ThreadClass : Thread() {
        override fun run() {
            while(isRunning) {
                sleep(100)
//                Log.d(TAG, System.currentTimeMillis().toString())
                binding.helloTextview.text = System.currentTimeMillis().toString()
            }
        }
    }

이처럼 view를 바꾸려고 하면

위와 같이 메인 쓰레드가 아닌 다른 쓰레드가 뷰를 바꿀 수 없다는 에러가 발생한다.

만약

android:layout_width="match_parent"

뷰를 구성하는 xml이 match_parent라서 뷰의 크기가 고정적이라면 오류가 안나기도 한다. 새로운 뷰를 구성하지 않고 계속 안의 데이터만 바뀌기 때문에 뷰를 바꾸는 것으로 적용되지 않기 때문이다. 하지만 이 역시 결국엔 crash가 날 잘못된 코드이다.
뷰는 메인 쓰레드에서 구성하자!

🔑 메인 쓰레드의 메시지 큐로 POST

메인쓰레드는 메시지 큐 수신을 대기하는 Looper를 가지며, 사용자 입력과 시스템 이벤트, 화면 그리기 등의 메시지가 수신되면 각 메시지에 매핑된 핸들러의 메서드를 실행한다.

그러므로 메인이 아닌 쓰레드는 UI를 바꾸려면 Handler를 통해 메인 스레드로 메시지를 전송하고, 그 메시지를 받아 메인 쓰레드에서 UI 처리를 한다.

Looper가 큐에 메시지 호은 runnable 객체가 있으면, Handler로 전달하여 처리한다.
Handler의 Post 메서드를 사용한다

        val thread = ThreadClass()
        thread.start()
    }
    
    val handler = Handler(Looper.getMainLooper())
    inner class ThreadClass : Thread() {
        override fun run() {
            while(isRunning) {
                sleep(100)
                handler.post{
                    binding.helloTextview.text = System.currentTimeMillis().toString()
                }
            }
        }
    }

🔑 RunOnUiThread

수행하고자 하는 코드를 Main Thread가 처리하도록 하는 메서드
Handler와 마찬가지로 별도 생성한 스레드에서 UI작업이 필요하면 사용한다.
Runnable 객체를 메인 스레드에서 실행되도록 만드는 메서드로, 현재 스레드가 메인스레드인지 검사하면 메인이면 run(), 아니면 post() 메서드를 실행한다.

Activity 클래스의 runOnUiThread 구성

    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

runOnUiThread 사용 코드

        // period 마다 동작하는 timer객체 생성. 10ms 마다 실행
        timerTask = kotlin.concurrent.timer(period = 10) {

            time++ // period = 10 : 0.01초마다 time 을 1씩 증가
            val sec = time / 100    // 나눗셈의 몫 : 초 부분
            val milli = time % 100    // 나눗셈의 나머지 : 밀리초 부분

            // UI 조작을 위한 메서드
            runOnUiThread {
                binding.secText.text = sec.toString()
                binding.milliText.text = milli.toString()
            }
        }

timer (kotlin.concurrent.timer(period = 10)) 는 10밀리초마다 안의 구문을 실행하는 코드로 메인 스레드가 아닌 워커 스레드(백그라운드 스레드)에서 동작한다. 따라서 여기 안에서 UI를 바꾸려면 UI스레드로 바꾸는 코드를 넘겨줘야 하고, 이를 runOnUiThread로 구현하였다.

📌 AsyncTask

비동기 작업을 위해 수행
크기가 큰 파일복사, 쿼리, HTTP 서버 API 요청 등 작업에 사용
스레드, 핸들러, 메시지, runnable을 사용하지 않아도, 메인 쓰레드와 별개로 '비동기 실행'이 필요한 작업에 사용할 수 있다.

API 30에서 deprecated 되었다.

🔑 AsyncTask 과정

  1. 실행 : execute -> 시작
  2. 백그라운드 작업 : doInBackground -> 백그라운드에서 비동기 작업 실행
  3. 진행상황 업데이트 : onProgressUpdate -> 백그라운드 스레드 진행 상황을 메인 스레드로 전달
  4. 비동기 실행완료 후 처리 : onPostExecute -> 완료 후 완료 상태 메인스레드에 전달

2번의 donInBackground를 제외하면 모두 메이느레드에서 실행되는 함수들이다.
가장 먼저 onPreExecuter() 함수를 doInBackground 직전에 호출한다.

onProgressUpdate()는 doInBackground에서 publishProgress 함수를 호출하면 호출되는 함수로, 작업 스레드를 실행하는 도중에 UI 처리를 담당한다.

onCancelled()는 doinBackground작업이 중간에 종료되면 호출된다. 정상적으로 종료되면 onPostExecute()가 호출된다.

    inner class MyAsyncTask(textView: TextView) : AsyncTask<Void, Int, Boolean>() {
        //worker thread 에서 동작...
        override fun doInBackground(vararg params: Void?): Boolean {
            for(i in 0..500) {
                SystemClock.sleep(100)
                //publishProgress로 onProgressUpdate를 호출
                publishProgress(i)
            }
            return true
        }

        override fun onPreExecute() {
            super.onPreExecute()
        }

        override fun onPostExecute(result: Boolean?) {
            super.onPostExecute(result)
        }

        override fun onProgressUpdate(vararg values: Int?) {
            super.onProgressUpdate(*values)
            //vararg : 가변인자, "*"을 붙이면 가변인자로 취급
            binding.textView.text = values[0].toString()
        }

        override fun onCancelled(boolean: Boolean) {
            super.onCancelled()
        }
}

AsyncTask를 선언할 때 제네릭으로 타입을 넣는데,

AsyncTask<Void, Int, Boolean>
첫번째 타입은 Params로
override fun doInBackground(vararg params: Void?)의 파라미터와 연결,

두번째는 Progress로
override fun onProgressUpdate(vararg values: Int?)
세번째는 Result로
override fun onPostExecute(result: Boolean?)
override fun onCancelled(boolean: Boolean) { super.onCancelled() }
와 맞춰준다.

profile
컴퓨터와 교육 그사이 어딘가

0개의 댓글