[안드로이드 프로그래밍] 메시지큐, 핸들러, 스레드 사용법

     




프로그래밍 언어를 공부하면서 매번 등장하는 스레드.

안드로이드에서는 스레드를 어떻게 사용하는지 공부해 보자.



 스레드란?


스레드는 여러 개의 작업을 동시에 진행하기 위해 사용되는 작업의 단위이다. 안드로이드에 대입해보면 애플리케이션을 실행할 때, 실행되는 앱은 하나의 프로세스이다. 이 앱은 다른 앱과 자료를 주고받을 수 없는 독립된 객체이며, 안드로이드 OS를 통해 다른 프로세스와 동시에 처리될 수 있다. 


하나의 프로세스가 하나의 작업만을 하는 것은 아니다. 하나의 프로세스 안에서도 여러 개의 작업을 동시에 처리해야 하는 경우가 있다. 게임을 예로 들어보면, 주인공 캐릭터가 움직이는 동안 배경 음악이 나와야 하고 적들은 주인공과 별개로 움직여야 한다. 만약 하나의 프로세스가 하나의 작업만을 처리한다면, 주인공 캐릭터가 움직일 때 음악은 재생되지 않고 적 캐릭터도 움직이지 않을 것이다. 하나의 프로세스 안에서 여러 개의 작업이 동시 처리 가능하므로 우리는 주인공 캐릭터를 움직이면서 움직이는 적을 물리치며 앞으로 나아갈 수 있다. 


하나의 프로세스 안에서의 작업단위를 스레드라고 할 수 있다. 프로그래머는 여러 개의 스래드를 생성해서 여러 개의 작업을 동시에 처리할 수 있다. 하지만 스레드를 사용할 때 주의사항이 있다. 하나의 프로세스에서 여러 개의 스레드를 사용할 경우, 같은 자원에 대해 여러 스레드가 요청하는 경우가 생긴다. 이 경우 "데드락"이 발생하여 스레드가 강제 종료되거나 프로세스 자체가 종료된다. 이처럼 하나의 자원에 대해 동시접근에 대한 문제가 발생하기 때문에 이를 해결하는 방법으로 여러 가지 대안이 개발되고 있다.


안드로이드의 경우 메인 액티비티가 실행되면 메인 스레드가 이 작업을 처리한다. 만일 프로그래머가 메인 스레드를 제외한 다른 스레드를 만들어 사용한다고 했을 때, 다른 스레드는 안드로이드의 UI객체를 참조할 수 없다. 즉, 메인 액티비티에 있는 텍스트뷰나 이미지뷰를 메인 스레드가 아닌 다른 스레드에서 접근해 값을 변경할 수 없다. 이는 위에 설명했듯이 "데드락"이 발생하기 때문에 사전에 접근권한을 주지 않는 것이다. 


다른 스레드에서 UI객체를 참조하는 방법은 안드로이드에서 제공하는 핸들러를 사용하여 메세지큐를 주고받는 방법이 있다.




순서는 다음과 같다.


  1. 안드로이드에서 제공하는 handler 클래스를 상속하는 클래스를 만든다.
  2. 메시지 큐에 메모리 공간을 얻기위해 obtainMessage 메소드를 이용하여 메시지 공간을 만든다.
    Message msg = handler.ObtainMessage();
  3. 메시지 데이터를 넣기위해 Bundle 객체를 사용한다.
    Bundle bundle = new Bundle();
  4. bundle.putString 메소드를 사용해 입력값을 집어넣는다.
    bundle.putSting(key, text);
  5. 메시지에 번들데이터를 집어 넣는다.
    msg.setData(bundle);
  6. 메시지큐로 보낸다.
    handler.sendMessage(msg);
  7. 핸들러 클래스에서는 전송된 메시지를 받는다.
    bundle = msg.getData();
  8. bundle에서 전달된 데이터를 받는다.
    text = bundle.getString(key);

위와 같은 순서로 코딩을 하면 메인 스레드가 아닌 스레드에서도 UI객체에 접근할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.example.mythread;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
 
public class MainActivity extends Activity {
 
    TextView text01;
    
    TextHandler handler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        handler = new TextHandler();
        
        text01 = (TextView)findViewById(R.id.text01);
        
        Button reqButton = (Button)findViewById(R.id.requestButton);
        
        reqButton.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                RequestThread rq = new RequestThread();
                rq.start();
            }
        });
        
        
        
        
    }
    class RequestThread extends Thread {
        public void run() {
            //text01.setText("서버에 요청했어요.");
            
        sendText("서버에 요청했어요");
            
            
            for (int i = 0; i<10; i++){
                try {
                    Thread.sleep(1000);
                }catch(Exception ex){
                    
                }
                
                //text01.setText("응답 대기중입니다. " + i);
                sendText("응답 대기중입니다." +i);
            }
            
            
            //text01.setText("서버에서 응답이 왔어요");
            sendText("서버에서 응답이 왔어요");
    
        }
        
        private void sendText(String text){
            Message msg = handler.obtainMessage();
            Bundle bundle = new Bundle();
            
            bundle.putString("text", text); 
            msg.setData(bundle);
            handler.sendMessage(msg);
        }
    }
    
    class TextHandler extends Handler{
 
        @Override
        public void handleMessage(Message msg) {
        
            Bundle bundle = msg.getData();
            String text = bundle.getString("text");
            
            text01.setText(text);
        }
        
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
}
 
 
 
 


Do it 안드로이드 앱 프로젝트에서 제공되는 스레드 예제.




간단한 스레드에 대한 예제소스를 만들어 보았는데 위처럼 생각보다 길게 작성되었습니다. 핸들러를 선언하고 번들, 메시지 객체까지 선언하다 보니 코드가 복잡해졌습니다. 안드로이드에서는 이렇게 복잡한 코드를 싫어하기 때문에 다른 방법으로 UI객체를 참조하는 방법이 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.example.mythread;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
 
public class MainActivity extends Activity {
 
    TextView text01;
    
    Handler handler;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        handler = new Handler();
        
        text01 = (TextView)findViewById(R.id.text01);
        
        Button reqButton = (Button)findViewById(R.id.requestButton);
        
        reqButton.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                RequestThread rq = new RequestThread();
                rq.start();
            }
        });
        
        
        
        
    }
    class RequestThread extends Thread {
        public void run() {
    
            handler.post(new Runnable() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    text01.setText("서버에 요청했어요");
                }
            });
            
            for (int i = 0; i<10; i++){
                try {
                    Thread.sleep(1000);
                }catch(Exception ex){
                    
                }
                
                final int index = i;
                handler.post(new Runnable() {
                    
                    @Override
                    public void run() {
                        // TODO Auto-generated method stub
                        text01.setText("응답 대기중입니다." + index);
                    }
                });
            }
            
            handler.post(new Runnable() {
                
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    text01.setText("서버에서 응답이 왔어요.");
                }
            });
    
        }
        
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
}
 
 
 
 


결과는 이전 코드와 같지만, 코드가 생각보다 간단해 보입니다. 


여기서 사용한 것은 Runnable이라는 클래스입니다. 핸들러 클래스를 새로 만들지 않고, 안드로이드에서 제공하는 핸들러 클래스를 사용해 post메소드를 통하여 UI객체에 접근하는 방법입니다. 사용 방법은 OnclickListerner와 비슷하며 handler.post(new Runnable())이라는 메소드를 사용해 Runnable()클래스 안에 있는 run()이라는 메소드안에 처리할 작업을 작성하면 됩니다.


이렇게 하면 따로 핸들러 클래스를 구현하지 않아도 바로바로 접근하고자 하는 UI객체에 접근이 가능합니다. 

반응형

댓글

Designed by JB FACTORY