post list

2013년 12월 21일

[Android] Thread Handler 다루기


 Thread에 대한 기본적인 지식은 Java 기본서를 참고하거나 나의 이전 포스트인 [Java] Thread의 개념을 참고하자. 지금 Android Thread를 공부하는 이유는 Handler 사용방식에 대해서 깊게 파기 위해서이다. 

 Android Handler가 어떤 역할을 하는지 잘 그려놓은 사이트가 있어서 가져왔다. 이 포스트를 모두 보고 나면 어느 정도 이해가 갈 것이다. 여기서 Looper에 관해서는 다루지 않는데 궁금하신 분은 출처를 남겨놨으니 가서 보시면 된다 :D

출처 : http://ensider.tistory.com/5


 Handler는 왜 필요할까? 바로 쓰레드간의 커뮤니케이션을 위해서다. 예를 들어 생각해보면, 화면 UI를 담당하는 쓰레드가 존재하고, 연산 작업을 진행중인 쓰레드가 존재한다고 하자. 이때 연산 작업을 끝낸 쓰레드가 그 결과 값을 UI에 적용시키려고 UI를 건드리게 된다. 그러면 이미 UI를 담당하는 쓰레드가 있는데 그 녀석이 무엇을 하는지도 모르는데 그 UI를 건드리게 되는 것이다. 어떤 상황이 될지 알 수 없게 된다. 그래서 android는 그러한 상황을 Sorry! 로 표현하며 에러를 뿜어낸다. 그래서 Handler가 필요하다. 쓰레드 간에 커뮤니케이션을 위해!

  핸들러는 스레드간에 메시지나 Runnable 객체를 통해 메시지를 주고 받는다. 핸들러는 항상 하나의 스레드에 소속된다. 자신을 생성하는 스레드에 결합되어 그 쓰레드의 메시지 큐를 통해 다른 쓰레드와 통신하게 된다. 보통은 다른 쓰레드로부터 전달되는 메시지를 수신하지만 자기 자신이 보낸 메시지도 물론 받을 수 있다. 만약 메시지가 핸들러에게 넘어오게 되면 handleMessage (Message msg)메서드가 호출된다. 여기서 인수로 msg를 받게 되는데 이놈은 통신 내용을 저장하고 있는 하나의 객체다. 여러 개의 필드를 가지는게 특징이다.  


int what : 메시지의 의미를 설명한다.
int arg1, int arg2 : 메시지의 추가 정보
Object obj : 정수만으로 메시지를 기술 할 수 없을 때 쓰는 필드다.
Messenger replyTo : 메시지에 대한 응답을 받을 객체를 지정한다.


 메시지를 보내는 쪽에서는 내용을 Message 객체에 저장하여 핸들러로 전송하는데, 다음의 메서드를 살펴보자.

boolean Handler.sendEmptyMessage (int what) 
위에서 말한 what값만을 전달할 때 ‘간단’하게 사용한다.

boolean Handler.sendMessage(Message msg)
좀 더 복잡한 정보를 보낼 때 사용한다. 메시지는 큐에 순서대로 쌓여 처리된다.

boolean sendMessageAtFrontOfQueue (Message msg)
위의 메서드와 동일하지만 이 메시드를 통해 보내는 메시지는 Queue의 가장 앞에 위치하게 되어 먼저 처리되게 된다.

 메시지를 boolean post (Runnable r) 메서드를 이용해서 보낼 수 있다. 이 Runnable이라는 객체는 작업을 직접 수행하는 객체다. 핸들러로 Runnable 객체를 보내면 run() 메서드가 실행된다. 간단한 작업의 경우 메시지를 보내는 것보다 Runnable을 보내는 것이 더욱 간단하다. 이렇게 쓰이게 되면 handler 쪽에서 수신된 메시지를 처리하는 handleMessage를 굳이 만들 필요가 없다. 

 문제는 Activity와 Thread가 분리 될 경우다. 쓰레드에게 보낼 핸들러를 전달해야 하고, 멤버들을 공유할 수 없기에 핸들러에게 메시지를 보낼 때도 작업 내용을 좀 더 구체적으로 기술해야한다. 이 때에는 Thread에 생성자를 만들어서 핸들러를 받아와야한다. 그런 후에 핸들러에게 변경된 값을 전달 해줘야 한다. 다음의 예제는 아주 전형적인 사용방법에 대해서 기술하고 있으므로 필요할 때 가져다 쓰자.

public class MainActivity extends Activity {
int mMainValue = 0;
TextView mMainText;
TextView mBackText;
BackThread mThread;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        mMainText = (TextView) findViewById(R.id.mainvalue);
        mBackText = (TextView) findViewById(R.id.backvalue);
        Button btn = (Button) findViewById(R.id.increase);
        btn.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
mMainValue ++;
mMainText.setText("MainValue : "+mMainValue);
}
});
        mThread = new BackThread(mHandler);
        mThread.setDaemon(true);
        mThread.start();
    }
    Handler mHandler = new Handler(){
    public void handleMessage(android.os.Message msg) {
    if(msg.what==0){
    mBackText.setText("BackValue : "+msg.arg1);
    }
    };
    };
}
class BackThread extends Thread {
int mBackValue = 0;
Handler mHandler;
public BackThread(Handler handler) {
mHandler=handler;
}
@Override
public void run() {
while(true){
mBackValue ++;
Message msg = Message.obtain();
msg.what = 0;
msg.arg1 = mBackValue;
mHandler.sendMessage(msg);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

 여기서 obtain 이라는 메서드가 나왔는데 쓰레드간에 메시지를 주고 받기 위해 매번 new를 이용해서 메시지를 생성하게 되면 메모리가 많이 소모되고 느려진다.(Message 객체는 static이다) 그래서 시스템에 메시지 풀이라는 것이 있다. 시스템의 메시지 풀에서 메시지 객체를 꺼낼 때는 다음 메서드를 사용한다.

static Message obtain([Message orig]);
static Message obtain(Handler h, int what, int arg1, int arg2, Object obj);
void recycle();

 obtain 메서드는 메시지 풀에서 비슷한 메시지를 꺼내 재사용하게 해준다. 위의 예제에서는 인수 없이 obtain()을 사용했는데 빈 메시지 객체 하나를 꺼내주게 된다. 이것은 new 연산자로 생성하는 것보다 빠른 방법이다. 꺼낼때 인수들을 (2번) 지정해주게 되면 메시지 객체를 꺼내서 값을 대입해 주기도 한다. 

 반면 recycle() 메서드는 사용한 메시지를 풀에 다시 집어넣는데 이후 부터는 시스템에서 관리하게 되므로 더 이상 건드려서는 안된다.





댓글 없음:

댓글 쓰기