728x90

프로세스(process)는 '실행중인 프로그램'이다. 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)를 할당받아 프로세스가 된다.

프로세스는 프로그램을 수행하는 데 필요한 데이터와 메모리 등의 자원 그리고 쓰레드로 구성되어 있으며, 프로세스 자원을 이용해서 실제로 작업을 수행하는 것이 바로 쓰레드이다.

그래서 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재한다.


안드로이드는 multi-threading 을 지원한다. 즉 하나의 애플리케이션은 동시에 여러가지 작업을 할 수 있다. 안드로이드는 자바의 multi-threading 을 그대로 지원한다.

개발자들은 하나의 애플리케이션 안에 동시에 실행되는 여러 Thread를 만들 수 있으며, 이 Thread 들은 자바 Virtual Machine 에 의해 동시에 실행된다.


애플리케이션이 구동되면 안드로이드 시스템은 애플리케이션을 위한 실행 Thread를 생성한다. 이 Thread는 Main Thread 라고 불리며, UI(user interface) Thread라고 불리기도 한다. Main Thread 는 사용자 인터페이스 위젯에 이벤트를 전달하거나 화면을 그리는 작업을 담당하고 있다.
동일한 Process 안에서 실행된느 모든 컴포넌트는
Main Thread 안에서 실행된다.

그리고 컴포넌트의 시스템 호출도 Main Thread 안에서 이루어진다.

안드로이드 UI는 기본적으로 싱글 쓰레드 모델로 작동한다.
안드로이드의 애플리케이션을 실행하면 시스템은 AndroidManifest.xml 파일에 기록된 최초의 액티비티를 메모리로 올려 프로세스를 만든다. 이때 Main Thread 가 자동으로 생성된다.


안드로이드 Thread 모델은 자바의 Thread 모델을 그대로 따른다.


쓰레드를 구현하는 방법은

Thread Class 를 상속받는 방법과 Runnable 인터페이스를 구현하는 방법이 있다.


Thread Class 를 상속받으면 다른 Class를 상속받을 수 없기 때문에, Runnable 인터페이스를 구현하는 방법이 일반적이다. Runnable 인터페이스를 구현하는 방법은 재사용성이 높고 코드의 일관성을 유지할 수 있어 보다 객체지향적인 방법이라고 할 수 있다.


Runnable 인터페이스를 구현한 경우, Runnable 인터페이스를 구현한 클래스의 인스턴스를 생성한 다음, 이 인스턴스를 Thread 클래스의 생성자의 매개변수로 제공해야 한다.

Runnable runnable = new Runnable();
Thread thread = new Thread(runnable);
thread.setDaemon(true); // 메인 쓰레드 종료시 같이 종료처리
thread.start();
 


쓰레드를 생성했다고 해서 자동으로 실행되는 것은 아니다. start()를 호출해야만 쓰레드가 실행된다.

하나의 쓰레드에 대해 start()가 한 번만 호출될 수 있다.

한번 실행이 종료된 쓰레드는 다시 실행할 수 없다.

실행중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.


Thread 안에서 직접적으로 UI(user interface) 위젯을 변경하면 안된다.

동기화 문제를 처리하기 위해 안드로이드에서는 Main Thread 에서만 UI 작업이 가능하도록 제한했다.

// Runnable 인터페이스를 구현한 클래스
class Runnable implements java.lang.Runnable {
    @Override
    public void run() {
        while(true){
            backValue++;  // 작업스레드 값 증가
            // 쓰레드 안에서 UI를 조작하면 에러가 발생한다.
            backText.setText("작업스레드 Value: " + backValue);
            try {
                Thread.sleep(10000); // 1000ms 단위로 실행(멈춤)
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
} // end class Runnable
 


UI Thread가 아닌 다른 Thread 에서 UI를 조작하면, UI 툴킷에 접근하지 말라는 원칙을 위배해서 오류를 발생시킨다.

이 문제를 해결하기 위해서 안드로이드는 다른 Thread(Sub Thread) 에서 UI Thread(Main Thread) 에 접근하는 많은 방법을 제공하고 있다.

- Handler : 일반적인 Thread 간 상호 작용을 위한 클래스

  work Thread가 여러개이고, 서로 비슷한 메시지를 보내야 한다면
  핸들러 클래스를 정의하는 것이 낫다.

- view.post(Runnable())  : UI Thread의 메시지큐에 작업을 추가. 해당 view 객체에 접근 가능한 경우에 사용

- View.postDelayed(Runnable())  : 일정 시간후에 작업처리

- Activity.runOnUiThread(Runnable())  : 핸들러 없이 UI Thread 의 메시지큐에 작업을 전달(post)

  runOnUiThread() 메소드는 Runnable 객체를 넘겨 받는데,
  넘겨 받는 코드가 백그라운드 Thread 이거나 UI Thread 인지 상관없이 무조건 UI 스레드에서 실행

- AsyncTask  : http://link2me.tistory.com/1031 참조



여러가지 방법중에 Handler 를 이용하는 방법에 대해 살펴보자.

UI 를 조작하기 위해서는 Handler와 Looper를 이용해 다른 Thread에서 Main Thread로 UI 처리 작업을 전달하던지, Main Thread 내에서 자체적으로 처리하던지 해야한다.

<이미지 출처 : http://itmining.tistory.com/5>


Handler

안드로이드에서는 Main Thread 를 통해서만 화면 UI를 변경할 수 있기 때문에 핸들러를 통해서 Main Thread 에 접근하여 UI를 수정할 수 있다.

- 핸들러를 생성하는 Thread 만이 다른 Thread 가 전송하는 Message와 Runnable 객체를 받을 수 있다.

- 핸들러가 처리하는 대상은 메시지(Message)와 런어블(Runnable) 객체이며 단일 쓰레드에 단 하나의 루퍼와 메세지 큐가 존재하지만, 핸들러는 다수 존재 가능하다.

- Handler 객체를 새로 생성하해당 쓰레드의 Message Queue에 바인드(bind)된다.

- 자료구조에서 메시지를 쌓아두는 공간이 MessageQueue 이다.

- Looper는 MessageQueue가 비어있는 동안은 아무 행동도 안하고 메시지가 들어오면 해당 메시지를 꺼내 적절한 Handler로 전달한다.

- 쓰레드 간의 통신을 위해서 메시지가 필요하다면 핸들러의 obtainMessage() 메서드를 사용할 수 있다.
obtainMessage() 메서드는 각 메서드에 따라 what, arg1, arg2, obj 등을 초기화할 수 있는 기능을 제공한다
-
Runnable 인터페이스는 run() 추상 메서드를 가지고 있으므로 상속받은 클래스는 run()코드를 반드시 구현해야 한다.


Handler 를 구현하는 방법은 http://netrance.blog.me/110130561171 에 잘 설명되어 있다.


1. 안드로이드는 핸들러를 구현할 수 있도록 Handler라는 베이스 클래스를 제공한다.
2. Handler 가 받을 메시지들의 종류를 결정한다.
3. 자식 Handler 클래스를 어디에 구현할지 결정한다.
   - Activity 등과 같은 특정 클래스의 내부
   - 메소드 내 (익명 클래스로 구현)
   - 별도의 자바 파일
4. 메시지의 종류가 무엇인지 식별한다.
   메시지 코드들을 정의한다.
   // Message Codes
   private static final int MSG_DAY_CHANGED = 1;
   private static final int MSG_MONTH_CHANGED = 2;
   private static final int MSG_YEAR_CHANGED = 3;

5. Message를 전달하는 방식
1. sendMessage() - 메시지를 바로 전달
2. sendMessageAtTime() - 메시지를 특정시간에 전달
3. sendMessageDelayed() - 메시지를 일정시간이 지난뒤에 전달
4. sendMessageAtFrontQueue() - 딜레이된 메시지가 있는경우 가장 앞에 전달


 Handler Class를 생성하여 workThread 내용을 갱신처리

 - 버튼을 클릭하면 Runnable 객체 생성한 Thread가 시작되어 workThread 값이 계속 증가된다.

 - 증가된 값은 Handler 객체를 통해 UI Thread에 값을 표시한다.

Thread_handler-01.zip

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    int mainValue = 0;
    int workValue = 0;
    TextView mainText;
    TextView workText;
    Button button;

    ProgressHandler progressHandler;

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

        mainText = (TextView)findViewById(R.id.mainvalue);
        workText = (TextView)findViewById(R.id.backvalue);
        button = (Button) findViewById(R.id.btn_increase);

        progressHandler = new ProgressHandler();
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startCount();
            }
        });
    } // end of onCreate

    private void startCount(){
        mainValue++;

        // 2. Runnable 객체  생성으로 구현
        Runnable runnable = new Runnable();
        Thread thread2 = new Thread(runnable);
        thread2.setDaemon(true);
        thread2.start();

        mainText.setText("메인스레드 값: " + mainValue );
    }

    class ProgressHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 0){
                // 메인 Thread의 UI 내용 변경
                workText.setText("BackValue:" + workValue);
            }
        }
    }

    // Runnable 인터페이스를 구현한 클래스
    class Runnable implements java.lang.Runnable {
        @Override
        public void run() {
            while(true){
                workValue++;  // 작업스레드 값 증가
                // 쓰레드 안에서 UI를 조작하면 에러가 발생한다.
                //workText.setText("작업스레드 Value: " + workValue);

                // 메인에서 생성된 Handler 객체의 sendEmptyMessage 를 통해 Message 전달
                progressHandler.sendEmptyMessage(0);

                try {
                    Thread.sleep(3000); // 1000ms(1초)이므로 3초 단위로 실행
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    } // end class Runnable

}


progressHandler.sendEmptyMessage(0); 대신에
Message msg = progressHandler.obtainMessage();
progressHandler.sendMessage(msg);
를 쓸 수 있다.


 익명 클래스를 이용한 Handler 객체 생성하여 workThread 내용을 갱신 처리 

Thread_handler-02.zip

import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    int mainValue = 0;
    int backValue = 0;
    TextView mainText;
    TextView backText;

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

        mainText = (TextView)findViewById(R.id.mainvalue);
        backText = (TextView)findViewById(R.id.backvalue);

        // Runnable 객체 생성으로 구현
        Runnable runnable = new Runnable();
        Thread thread = new Thread(runnable);
        thread.setDaemon(true);
        thread.start();
    } // end of onCreate

    public void OnClick(View v){
        mainValue++;
        mainText.setText("메인스레드 값: " + mainValue );
    }

    // Main Thread 에서 Handler 객체를 생성한다.

    // Handler 객체를 생성한 쓰레드 만이 다른 쓰레드가 전송하는 Message 나 Runnable 객체를 수신 가능

    // 익명클래스를 이용한 Handler 객체를 생성
    Handler handler = new Handler(){ //클래스 내부의 클래스, 재사용하지 않을 클래스를 만들때 주로 사용
        // Handler 객체는 handlerMessage() 를 오버라이딩 하여 Message 를 수신
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == 0){   // Message id 가 0 이면
                backText.setText("BackValue:" + backValue); // 메인스레드의 UI 내용 변경
            }
        }
    };

    // Runnable 인터페이스를 구현한 클래스
    class Runnable implements java.lang.Runnable {
        @Override
        public void run() {
            while(true){
                backValue++;  // 작업스레드 값 증가
                // 쓰레드 안에서 UI를 조작하면 에러가 발생한다.
                //backText.setText("작업스레드 Value: " + backValue);
                // 메인에서 생성된 Handler 객체의 sendEmptyMessage 를 통해 Message 전달
                handler.sendEmptyMessage(0);


                try {
                    Thread.sleep(1000); // 1000ms(1초) 단위로 실행
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    } // end class Runnable
}
 


Handler 와 WorkerThread 사용하는 방법


 import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class HandlerActivity extends AppCompatActivity {
    Button handlerStartButton;
    Button handlerStopButton;
    Handler mainThreadHandler;
    TextView handlerExampleTextView;
    int workValue = 0;
    boolean running = true;

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

        handlerStartButton = findViewById(R.id.btn_start);
        handlerStopButton = findViewById(R.id.btn_stop);
        handlerExampleTextView = findViewById(R.id.tv);
        mainThreadHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if(msg.what == 1){
                    handlerExampleTextView.setText("BackValue:" + workValue); // 메인스레드의 UI 내용 변경
                }
            }
        };

        handlerStartButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                running = true;
                startCount();
            }
        });
        handlerStopButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                running = false;
                workValue = 0;
            }
        });
    }

    private void startCount(){
        // Start a child thread when button is clicked.
        WorkerThread thread = new WorkerThread();
        thread.start();
    }

    private class WorkerThread extends Thread{
        @Override
        public void run() {
            super.run();
            while(running){
                workValue++;  // 작업스레드 값 증가
                try {
                    java.lang.Thread.sleep(1000); // 1000ms(1초)이므로 1초 단위로 실행
                    System.out.println(java.lang.Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Create a message in child thread.
                Message childThreadMessage = new Message();
                childThreadMessage.what = 1;
                childThreadMessage.arg1 = workValue;
                // Put the message in main thread message queue.
                mainThreadHandler.sendMessage(childThreadMessage);
            }

        }
    }
}


블로그 이미지

Link2Me

,