Android

[Android] 스레드(Thread)와 핸들러(Handler)

dev_zoe 2021. 1. 20. 23:06
반응형

비동기 처리

비동기 처리란?

비동기 처리 (동기적으로 처리하지않음) -> 즉, 동기적으로 처리할 경우에 코드의 응답 결과를 기다렸다가 받으면 다음 동작을 실행하는 처리가 아니라 응답에 상관없이 동작을 실행하는 처리방식을 의미한다. 

 

비동기 처리가 왜 필요한가?

 

작업시간이 긴 작업 또는 기기 외부 요소와 상호작용할 경우 시간 지연이 발생할 수 있음

-> 사용자 UI를 처리하는 메인 쓰레드에서 이 경우가 발생한 경우에 ANR(Application Not Responding)이 발생함

(ex: 대용량 DB 접근, 네트워크 사용 등)

 

따라서 API 11부터 UI 쓰레드(Activity, Main Thread)에서 위와 같은 시간지연이 발생하는 작업이 금지됨

=> 비동기 처리방식을 이용하여 별도의 쓰레드에서 작업시간이 긴 작업을 수행해야함 (Multi-Threading)

 

  • Thread
  • AsyncTask 클래스

Thread

Thread란?

동시 수행이 가능한 별도의 실행 흐름, 작업 단위

 

Runnable 인터페이스 구현을 통한 Thread 사용

class MyRunnable implements Runnable{
	public void run(){
    	//다중 작업할 내용
    }
}

MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); //여기서 새로운 실행흐름이 발생

 

Thread_test 예제 - Context Switching

(소스코드 링크 - Kotlin)

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

        var r = Runnable{
            for (i in 0 until 1001){
                println(i)
            }
        }
        Thread(r).start()

        var s = Runnable{
            for (i in 1000 downTo 0){
                println(i)
            }
        }
        Thread(s).start()
    }
}

38에서 갑자기 1000이 되거나, 57에서 갑자기 996이 되는 현상 -> Context Switching

 

Context Switching

(Process -> 프로그램이 실행되고 있는 상태)

Running상태인 A프로세스 Ready 상태인 B프로세스가 있다고 했을 때,

이 둘의 프로세스는 인터럽트 요청에 의해 서로 상태가 전이됨 -> A프로세스가 Ready, B프로세스는 Running 상태


실행중인 A 프로세스에 대한 데이터는 PCB(Process Control Block, 특정한 프로세스를 관리할 필요가 있는 정보를 포함하는, 운영체제 커널의 자료구조)에 저장되어 있음

하지만 이제 B 프로세스가 실행되어야 하기 때문에 A는 레지스터를 B에 양보해주어야함.

A의 프로세스 데이터는 B가 실행을 마친 후에 A가 실행될 수 있으므로 계속 가지고 있음(레지스터에 존재하는 A에 대한 데이터를 메모리에 데이터 저장)

 

즉, Context Switching은 프로세스 상태가 전이되면서 프로세스 데이터를 레지스터와 메모리 사이를 왔다갔다 하며 복사하는 과정

Handler

Handler란?

별도의 쓰레드에서 Main Thread(UI Thread)에 직접 접근할 수 없으므로 데이터를 통신할 방법이 필요함 -> 이때 사용하는게 Handler

즉, 쓰레드 사이에 메시지(Message 클래스)와 Runnable 객체를 주고받을 수 있는 통신 메커니즘

 

 

Message 클래스

Handler를 통해 데이터를 전달하는 용도의 클래스

생성 -> new Message(), Message.obtain(), Handler.obtainMessage() 등 사용

 

멤버 변수명 용도
int what 메시지의 의미를 지정할 때 (0=처리 실패, 1= 처리성공)
int arg1 메시지의 정수형 정보
int arg2 메시지의 정수형 정보
Object obj 정수형 이외의 객체를 결과로 보낼때

 

Thread와 Message 사용 예제

(소스코드 링크)

public class MainActivity extends AppCompatActivity {

    TextView textView;
    ToyThread thread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = findViewById(R.id.textView);
        textView.setText("Output");
    }

    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.button:
        /* 오류 부분 -> 아래 sum에 i를 더하는 작업을 수행하느라 UI처리를 수행하지 못함.
        별도의 처리를 해주지 않으면 실행흐름이 하나이기 때문에, UI 처리를 시도하면 ANR 발생
                int sum = 0;
                for (int i = 1; i <= 100; i++) {
                    sum += i;
                    Log.d("test", "value: " + i);
                    try {
                        Thread.sleep(50); //더하는 작업마다 0.05초 멈춤
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        break;
                    }
                }

        */
//              쓰레드 생성 후 실행 -> Handler 객체를 매개변수로 전달
                thread = new ToyThread(handler, "hi");
                thread.setDaemon(true);     // 실행중 메인스레드가 종료되면 같이 종료
                thread.start();     // 쓰레드 실행 -> 원래의 쓰레드에서 새로운 실행 흐름(쓰레드) 생성
                Toast.makeText(this, "Thread Start!", Toast.LENGTH_SHORT).show();

                break;
            case R.id.button2:
//                thread 중간에 멈추고싶을 때
                if (thread != null) thread.interrupt();     // InterruptedException 생성 => 실행하고 있는 도중 현재의 결과 출력하고 멈춤
                break;
            case R.id.button3:
                Toast.makeText(this, "Toast!", Toast.LENGTH_SHORT).show();
                break;
        }
    }

//    쓰레드에서 전달하는 메시지를 처리하기 위한 핸들러
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {        // msg 는 쓰레드에서 생성하여 전달한 Message
            if (msg.what == 1) {
                textView.setText(textView.getText() + "\narg1: " + msg.arg1);
                textView.setText(textView.getText() + "\nObj: " + msg.obj);
            }
        }
    };

//   Thread 클래스 상속
    class ToyThread extends Thread {
        final static String TAG = "ToyThread";

        String data;
        Handler handler;

        /*Thread 에서 필요한 매개변수가 있을 경우 보통 생성자로 전달
        Handler : 쓰레드의 결과를 전달하기 위해 사용*/
        public ToyThread(Handler handler, String data) {
            this.data = data;
            this.handler = handler;
        }

//        다중 작업할 내용을 run() 메소드에 기록
        public void run() {
            Log.d(TAG, data + " thread start!");

            // Thread에서 해야할 작업
            int sum = 0;
            for (int i=1; i <= 100; i++) {
                sum += i;
                Log.d(TAG, "value: " + i);
                Log.d(TAG, "sum: " + sum);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
            }

//            작업을 끝낸 후 결과를 Thread 실행 부분으로 전달
            Message msg = handler.obtainMessage();  // Message message = new Message(); 와 달리 Message 객체  재사용
//            Message 에 실행 결과 저장
            msg.what = 1;       // 쓰레드 수행 후의 결과 상태를 정수로 지정
            msg.arg1 = sum;         // msg.arg2 도 동일하게 정수 저장
            msg.obj = new Integer(sum);     // 객체 저장 시, Serializable 인터페이스 구현 객체
//                                        Message.obj 에는 Serializable 인터페이스를 구현한 객체만 저장 가능
//           Handler를 사용하여 Message 전송
            handler.sendMessage(msg);       // Message 없이 결과만을 알릴 때 handler.sendEmptyMessage(int what) 사용

            Log.d(TAG, data + " thread stop!");
        }
    }
}

 

실행결과

[처음 실행시]

 

[실행이 끝난후]

 

hi는 스레드 생성시 전달한 data

 

 

[중간에 Stop 버튼을 누를시]

 

Reference

jinshine.github.io/2018/07/10/%EC%BB%B4%ED%93%A8%ED%84%B0%20%EA%B8%B0%EC%B4%88/Thread,Process,Context%20Switching/

반응형

'Android' 카테고리의 다른 글

[Android] 팔레트 항목 뜯어보기  (0) 2021.01.21
[Android] AsyncTask 클래스  (0) 2021.01.20
[Android] Intent(인텐트)  (0) 2021.01.20
[Android] LifeCycle  (0) 2021.01.07
[Android] Manifest란?  (0) 2020.12.31