상세 컨텐츠

본문 제목

안드로이드 UI 업데이트와 쓰레드 Handler

스마트기기개발관련/안드로이드 개발

by AlrepondTech 2011. 6. 30. 16:56

본문

반응형

 출처: http://maluchi.cafe24.com/xe/26518

1. Handler 클래스

 - Activity에서 쓰이며, View에서 사용하지 않음. 쓰레드간  View 화면 업데이트 시 사용

 - Activity는 UI쓰레드 하나만 가지며, 수행할 작업이 길다면 쓰레드나 핸들로로 처리해줘야 함.

 - Activity는 5초이내 응답이 없으면 OS에 의해 Hang으로 처리("응답없음" 이런식)

 

1.1 화면 업데이트 의뢰방법

 a)  Handler : post()

   - 약점: 작업에 대한 구분해야 됨(매번 Runnable 구현을 해야된다는 얘기), 동기화 문제 발생

  임의 Thread :

  void run()

                 {

                         handler.post(r);

                 }

  

 MainThread :

  Runnable  r = new Runnable()

 {

       void run() {

         // UI Update

       }

  }

 

b) Handler - sendMessage()

  - 제일 많이 쓰이는 구조

  임의 Thread :

  void run()

   {

        Message m = new Message();

         handler.sendMessage(m);

     }

 

Main Thread :

Handler handler = new Hanlder(){

 void handleMessage(Message m){

  .......

 }

}

 

c) AsyncTask 추상 클래스

 - 상속하여 추상메서드 구현

 - doInBackground() - run() 역할

 - onProgressUpdate() - ui update 역할

- 이후 execute() 실행

 

 

2. Handler 데모

 - 20개의 아아템을 1초마다 리스트에 추가하는 예

 

01.public class HandlerDemo extends ListActivity {
02. 
03.Handler handler;
04. 
05./** Called when the activity is first created. */
06.@Override
07.public void onCreate(Bundle savedInstanceState) {
08.super.onCreate(savedInstanceState);
09.//setContentView(R.layout.main);
10. 
11.final ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
12. 
13.handler = new Handler(){
14.public void handleMessage(Message msg)
15.{
16.if(msg.what==1)
17.{
18.HashMap<String,String>map = (HashMap<String, String>)msg.obj;
19.list.add(map);
20. 
21.SimpleAdapter ap = new SimpleAdapter(
22.HandlerDemo.this,
23.list,
24.android.R.layout.simple_list_item_2,
25.new String[]{"first", "second"},
26.new int[]{android.R.id.text1, android.R.id.text2}
27.);
28. 
29.setListAdapter(ap);
30.getListView().setSelection(list.size()-1);
31.}
32.else if(msg.what==2)
33.{
34.Toast t = Toast.makeText(HandlerDemo.this, "error....", Toast.LENGTH_SHORT);
35.t.show();
36.}
37. 
38.super.handleMessage(msg);
39.}
40.};
41. 
42.Thread t = new Thread(new LineThread());
43.t.start();
44.}
45. 
46.public class LineThread implements Runnable
47.{
48.public void run() {
49.// main thread에 넘기는 데이터 추상화 Object
50.// what-요구구분자, obj-넘기는 데이터
51.Message msg = null;
52.for(int i=0;i<20;i++)
53.{
54.HashMap<String,String> map= new HashMap<String, String>();
55.map.put("first","title: "+i);
56.map.put("second","content: "+i);
57. 
58.msg = new Message();
59.msg.what =1;
60.msg.obj = map;
61.// main thread 에게 작업 의뢰
62.handler.sendMessage(msg);
63. 
64.try {
65.Thread.sleep(1000);
66.} catch (Exception e) {
67.// TODO: handle exception
68.msg = new Message();
69.msg.what =2;
70.handler.sendMessage(msg);
71.}
72.}
73.}
74.}
75. 
76.}

 

 3. Activity Life cycle

  OnCreate() - 단 한번만 호출

  OnRestoreSaveInstance() - 데이터 복원 , Bundle 값이 없으면 호출되지 않음.

  OnStart()

  OnResume() - 활성상태, 화면 상단에 위치, User Event 가능, 앱의 스택버퍼 최상위에 위치

  OnSaveInstance() - 데이터 저장 

  OnPause() - 일시정지상태, 화면 상단위치, 다이얼로그창이 모달될 때, 스크린 오프될 때

  OnStop() - 비활성 상태, 화면 상단위치가 아니며 백그라운드에 위치(타 액티비티에 의해)

                     : Back key에 의해 상단의 액티비티가 내려가면 OnRestart()가 호출되며, OnStart() 호출 

  OnDestroy() - 종료 직전

                     : 명시적 명령 - 유저명령에 의한 Back key에 의해 사라지는 순간 (앱 스택 최상위에 위치된 경우)

                                              - 코드명령에 의한 finish() 호출 시

                    : 시스템 명령 - Activity를 위한 Resource 재할당, 화면 회전시 activity 종료후 재실행

                                             - 메모리 부족시(앱의 우선순위가 낮을 상태인 것부터)

                                             - 배터리 분리시 (OnDestroy를 호출 안될수 있는 극한상황)


 - 활성/비활성 상태에 따라 반복 작업을 할 시 OnResume()과 OnPause()에서 구현

   : 리소스의 절약의 이유

 - Activiy의 종료 가능성 때문에 데이터가 유실 될 수 있으면, OnRestoreSaveInstance(), OnSaveInstance()를 이용

 

3.1 Bundle : map 으로 구현돼 있으며, File(xml)로 관리 

  - Activity Life Cycle에서 Bundle 파라미터를 갖는 메서드, OnCreate, OnRestoreSaveInstance, OnSaveInstance.

  -  Bundle 이용시 문제점

   :  저장할 데이터 종류의 한계성 - 모든 종류의 데이터타입을 저장하지 못함.

      -- 만약 객체를 I/O하려 할 때, Serializable 인터페이스를 상속한 객체만 가능

      -- 개발자가 관리하지 않는 객체의 경우, 예) Socket

   :  File I/O가 수시로 이뤄지는 비효율적인 문제

      -- 일부 해결책 : 프로세스  메모리 영역 내에 저장, 하지만 프로세스가 종료되면 데이터는 모두 사라짐.

                                 즉각 시작한다는 보장이 될 경우 이런 방식을 사용

                                 (onRetainNonConfigurationInstance() - Destroy 직전에 호출됨, getLastNonConfigurationInstance()로 복원).

                                이 방식은 화면 회전시 유용하게 이용할 수 있음.

 3.2 화면 회전

  Manifest 파일에 다음과 같은 어트리뷰트를 추가해서 사용.

  android:configChanges="keyboardHidden|orientation"
  android:screenOrientation="portrait"
 

 

 4. Push Notification Service 기능

  - 안드로이드 2.2 부터 제공 - C2DM 이라 칭함

 

5. Intent - IPC

 - 다른 Component(Activity, Service, Receiver, Provider)를 실행시키기 위한 일종의 Message

 - Intent로 실행되는 컴포넌트 - Activity, Service, Receiver이며, Provider는 예외

 - 인텐트 설정

 Intent  i = new Intent(this, B.class);

 i.PutExtra(key, value);

 

  a) Activity - startAcitivity(i)

    - startActivityForResult(i, requestCode) : 결과값을 받고자 할 때, requestCode는 요구 구분코드이며, 0 이상의 숫자값을 줘야 함.

                           되돌릴 경우는 그 코드에서 finish()를 해야하며, caller측 코드에서 onActivityResult 호출됨

             : onActivityResult(i, requestCode, resultCode) - resultCode는 결과코드로 OK, CANCEL 밖에 없음.

  b)  Service - startService(i)

  c) Receiver - sendBroadcast(i)

 

- 인텐트 가져오기

 Intent i = getIntent();

 i.getStringExtra(i);

 i.getIntExtra(i);

 

 5.1 인텐트의 종류

  - 명시적 Intent : 실행시키고자하는 클래스명이 들어갈 때이며, 자신의 내부 컴포넌트를 실행할 때.  new Intent(this, B.class)

  - 암시적 Intent : 클래스명을 주지 않을 경우이며 내/외부 모두 실행은 가능. 외부의 컴포넌트 실행 할 때 쓰임

                            : AndroidManifest 파일을 보고 판단하며 필터에 부가적인 정보가 필요하며 그 정보를 보고 판단을 하겠다는 의미 

                           <Activity name="a1">

                              <intent-filter>

                                <action name="AAA" />

                                <category name="BBB" />

                                <data schema="geo" host="" mimetype=''" />

                              </intent-filter>

                           </Activity>

 

   // 클래스명이 없어도 필터의 부가적인 정보로 보고 판단을 하게 됨.

    Intent i = new Intent();

    i.setAction("AAA");

    i.setCategory("BBB");

    i.setData(Uri.Parse("geo:")); 

 

5.2 인텐트 필터

 - 안드로이드는 부팅시에 모든 App의 인텐트필터 정보를 리스트화 함. 따라서 외부의 프로세스를 호출하는 구조됨.

 

 - Action : 이 컴포턴트가 무슨 일을 할수 있느냐를 표현하는 문자열

   : 동일 문자열이 없을 시 에러

   : 동일 문자열이 하나 일 때 실행 

   : 동일 문자열로 여러개가 있을 때, 팝업 창을 띄어 유저가 선택하게끔 함.

 - Category : 이런 능력을 위한 특수상황을 표현하는 문자열

   : 

 - Data : 필터링을 위해 쓰임

 

5.3 안드로이드가 인텐트 필터를 처리하는 방법

  - 설치된 패키지로부터 사용가능한 모든 인텐트 필터들의 리스트를 구성
인텐트 내의 액션 검사
      - 인텐트 필터가 지정된 액션을 포함하고 있거나, 인텐트 필터에 아무런 액션도 지정되지 않는 경우는 액션과 일치
      - 인텐트 필터가 하나 이상의 정의된 액션을 가지고 있지만 인텐트의 지정된 액션과 불일치시는 실패
- Category 검사
    - 인텐트 필터에 정의된 모든 Category 를 인텐트가 보유
- Data 검사
   - 데이터 값을 가지지 않은 인텐트는 성공
   - 데이터 타입/Schme/ Host/ Port/ Pat 검사

 

6. Receiver

  - 시스템에서 이벤트를 받기 위한 수단으로 사용

  - onReceive 수행시간이 10초 이내여야 함.

  - 부팅완료시에도 Broadcasting 메세지가 있음.

  - 객체 유지 하지 않음. 

 

class A extends BroadcastReceiver

{

   onReceive();

}

 

6.1 실행방법

Intent i = new Intent(this, A.class);

 sendBroadcast(i) 

  

 

 

 




/////////////////////////////////////////////////////////////////////////////////////
출처: http://blog.naver.com/cy78?Redirect=Log&logNo=140108604417

Android 어플리케이션에서 이미지를 다운로드 해온다거나, 시간이 걸리는 작업의 결과를 화면에 표시하는 등,

쓰레드를 통해 UI의 값들을 직접 바꿔주려다보면 이런 에러 메시지와 부딪히게 된다:

"android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views." 

'view 구조를 생성한 쓰레드에서만 그 view를 건드릴 수 있다!'는 내용의 오류 메시지.

 

그런데, 오류메시지 내용 대로, view 구조를 생성한 쓰레드에서만 그 view를 건드릴 수 있다면

시간이 걸리는 작업을 하는 동안에는 그 view가 얼어 있어야 하는건데?

 

대체 어떻게 해야하나???

 

그 때 바로 요 'android.os.Handler' class를 활용한다.

 (위 링크에 있는 설명에 의하면)

요 놈은 생성되는 순간부터 자기를 생성한 쓰레드의 job queue 에 bound 되어서

해당 job queue로 message와 runnable을 전달하고, 또 그 놈들을 execute 하게 된다.

즉, view 구조를 생성한 쓰레드인 UI 쓰레드에서 Handler를 생성하면

그때부터 그 Handler는 UI 쓰레드와 bound 되어 동일한 job queue를 통해 먹고자고 하게 되니,

 그 view를 건드려도 rule 상 아무 문제가 없게 된다.

 

까먹지 말아야할 것 하나는,

 (당연한거긴 한데) 요 Handler가 UI 쓰레드 말고 또 다른 쓰레드 내에서 생성되면 안된다는거다.

그러면

"java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()"

 같은 메시지를 내면서 죽어버릴 것이다.

 

대충 onCreate() 함수 내에서 생성하는게 답 아닐까 싶다.

 

출처:http://www.cyworld.com/thespeedofpain/2653453

 

해서 프로그레스바를 업데이트 하는 코드에서 UI관련 함수를 조작할때 Handler.post를 사용해서 메인 쓰레드에 어태치하는 것으로 추측되는듯 하다.

public class ProgressBarExam extends Activity {
 private int progress = 0;
 private int mProgress3Status = 0;
 private Handler mHandler = new Handler();

...

 

final ProgressBar progressBar = (ProgressBar)findViewById(R.id.ProgressBar01);

new Thread(new Runnable() {

   @Override
   public void run() {
    // TODO Auto-generated method stub
    while(progressBar.getProgress() < 100) {
     try {
      Thread.sleep(50);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
     mProgress3Status = progressBar.getProgress();
     mProgress3Status++;
     mHandler.post(new Runnable() {
      public void run() {
       progressBar.setProgress(mProgress3Status);
       if(progressBar.getProgress() == progressBar.getMax()) {
        final ProgressBar prog_stop = (ProgressBar)findViewById(R.id.ProgressBar03);
        prog_stop.setVisibility(ProgressBar.GONE);
       }
      }
     });
    }
   }

 }).start();


/////////////////////////////////////////////////////////////////////////////////////////

출처: http://blog.naver.com/kkamci25?Redirect=Log&logNo=10111345856

Handler 핸들러 설명

핸들러란?

 안드로이드의 시스템은 사용자가 작성한 UI에서 빠른 응답을 요구하고 있다.

만약 5초 이상의 응당이 없을 경우 최악의 경우 작성된 프로그램이 강제로 종료가 되는 경우도 발생할 수 있다.

이런 상황을 방지하기 위해서 시간이 오래걸리는 작업이 필요한 경우 두가지의 방법으로 해결을 할 수 있다.

첫번째는 시간이 오래걸리는 작업은 서비스로 만들어서 처리하는 방법

두번째는 새로운 쓰레드로 처리를 하는 방법 

두번째 방법으로 쓰레드를 생성해서 데이터 처리등의 시간이 오래걸리는 작업을 지원하기 위한 클래스가 존재하는데 그것이 핸들러(Handler)이다. 

 

간략하게 요약을 해보면

  • 백그라운드 쓰레드 생성을 위한 가장 유연한 방법이다.
  • 인스턴스 생성시 자동으로 안드로이드가 실행 관리한다.
  • 메시지를 전달받게 되면 호출 되는 handlerMessage()에 실제 처리내용을 구현한다.
  • post(), postDelayed()를 통해서 인자로 실행하고 자하는 Runnable객체를 전달할 수 있다.
  • View단에도 Runnable객처를 인자로 전달가능하고 이럴경우 코드가 심플해지는 경우도 있을수 있지만 Handler를 추천한다.

Handler Thread

아직까지는 이 세개의 관계가 정리가 안된다.

http://codinghard.wordpress.com/2009/05/16/android-thread-messaging에서는 Handler와 Looper의 역할에 대하여 아래와 같이 예를들어서 설명한다.

안 드로이드는 Thread간에 통신을 하기 위해서 Handler와 Looper를 제공하고 있다. Child Thread가 웹으로 부터 이미지를 가져와 생성하려고 할때의 예를 제시하고 있다. 생성이 끝나고(아마도 ImageView형태로 바꿔서) Main Thread의 Message Queue와 연동된 Handler를 이용하여 Message를 송신하는것으로 Main Thread에 알려준다. 물론 Child Thread에서 생성된 데이터(ImageView)는 Message에 포함되어 있다.

그리고 간단한 Coding 예를 보여주는데 일단 두개의 Thread를 생성하고 (여기서 이미 Main은 기본적으로 Application이 start될때 Run됨으로 Child Thread만 생성한다.)

  1. new ChildThread().start ();

또 한 들어오는 Message를 Listening하면서 Looper가 받아 먹어야 되는데 Main은 기본적으로 항상 Listening상태이기 때문에 별도의 Loop가 필요없으며 단지 들어온 Message를 처리할수 있는 Handler만 생성한다.

  1. mMainHandler = new Handler () {
  2. public void handlerMessage (Message msg) {
  3. mMainReceiveView.setText ((String)msg.obj);
  4. }
  5. };

이와는 반대로 Child Thread는 계속 Running하면서 들어오는  Message를 받아 먹고 이를 Handler 하여금 처리해야 하기 때문에 아래의 코드처럼 전형적인 코딩한다.

  1.      class ChildThread extends Thread {
             public void run() { /* * You have to prepare the looper before creating the handler. */
                Looper.prepare();
              /* * Create the child handler on the child thread so it is bound to the
               * child thread's message queue. */
                  mChildHandler = new Handler() {
                      public void handleMessage(Message msg) {
                          /* * Do some expensive operations there. */ } }
                          /* * Start looping the message queue of this thread. */
                      Looper.loop(); }
              }

위의 코드의 Full-source는 위의 링크를 참조하라.

  1. public class MyActivity extends Activity {
         private static final int PROGRESS = 0x1;
         private ProgressBar mProgress;
         private int mProgressStatus = 0;
         private Handler mHandler = new Handler();

         protected void onCreate(Bundle icicle) {
             super.onCreate(icicle);
             setContentView(R.layout.progressbar_activity);
             mProgress = (ProgressBar) findViewById(R.id.progress_bar);

             // Start lengthy operation in a background thread
             new Thread(new Runnable() {
                 public void run() {
                     while (mProgressStatus < 100) {
                         mProgressStatus = doWork();
                         // Update the progress bar
                         mHandler.post(new Runnable() {
                             public void run() {
                                 mProgress.setProgress(mProgressStatus);
                             }
                         });
                     }
                 }
             }).start();
         }
     }
     

위의 소스는 developer.android.com의 progressbar클래스에 있는 예제 코드이다. ChildThread와  MainThread간의 통신을 위해 Handler가 쓰이고 있다.

 

//////////////////////////////////////////////////////////////////////////////////////////////////


출처: http://hatti.tistory.com/category/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D/Android


[안드로이드] handler의 사용


시도 :

화면에 Textview를 이용하여 타이머를 설정하려고 시도.
생각 : 
1. 화면은 화면의 흐름을 따르고, 타이머는 화면과 별도로 동작해야한다.
2. 그럼 타이머는 따로 놀아야한다.
3. Thread라는 개념이 있다. Thread는 동시작업하기에 용이하다.
(Thread의 개념은 대략 생각이 안나는데.. 그냥 멀티테스킹이라는 개념만이 생각날뿐..)
4. Thread를 사용하자.
5. 안드로이드에서는 어떻게 사용하면되지.
6. Thread는 생성, 시작, 내용이 필요하다.
구현 :
       Thread
        - Thread thread = new Thread();  // Thread의 생성
        - thread.start()                           // Thread의 시작
        - public void run(){}                   // Thread의 내용
       
내용 :
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Thread pThread = new Thread(new Runnable(){
    @Override
    public void run(){
        try{
            while(!clicked_reset_btn){
                //Toast.makeText(sun_handler.this, "blink", 1000).show();
                textv.setText(Integer.toString(tSecond));
                tSecond = tSecond +1;
                Thread.sleep(1000);
            }
  
            if(clicked_reset_btn){
                tSecond = 0;
            }
        }catch (Throwable t) {
            //  Toast.makeText(sun_handler.this, "D", Toast.LENGTH_SHORT).show();
            //  TODO: handle exception
        }
    }
});

하니까 안되더라.
쓰레드 진입은 하는데 한번만 하고 바로 catch문으로 들어가 버렸다!!!
그래서 물어봤다. (androidside http://www.androidside.com)에

다른쓰레드에서 UI쓰레드로 접근하는 부분은 임계영역입니다.
textv.setText(Integer.toString(tSecond));
여기서 하지 말고 UI쪽으로 메시지를 보내서 핸들링하는 구조로 변경을 하세요.


라고 하셨다.
내용은 메인쓰레드에서 하지말고 서브쓰레드를 생성해서 작성하라 하셨다.

즉 메인 쓰레드(UI 쓰레드)에서 자식쓰레드를 생성하고 그걸 run시키는 방법.
도구는 handlermessage를 이용하여서.

수정 :

?
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
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
     
    Thread time_thread = new Thread(this); //자식쓰레드 생성
    time_thread.start(); // 쓰레드 시작
}
 
public void run(){
    while(!STARTED & !CLICKED_RESET_BUTTON){
        try{
            Thread.sleep(1000); // 1초의 딜레이
        }
        catch (InterruptedException e) {
            e.printStackTrace();
            // TODO: handle exception
        }
        mHandler.sendMessage(Message.obtain(mHandler, 1)); // handler로 메시지를 보내서
            // 처리함. 처리번호는 1.  메시지 처리부는 Message.obtain()의 두번째 인자를 switch문을
            // 이용하여 처리한다.
        }
    }
 
public Handler mHandler = new Handler(){ // 핸들러 처리부분
    public void handleMessage(Message msg){ // 메시지를 받는부분
        switch(msg.what){ // 메시지 처리
            case 1:
                set_timer(); // 나는 함수를 호출하였다.
                break;
            case 2// 기타 전달인자로 인한 처리도 가능
                break;
            case 3:
                break;
        }
    };
};
고찰 :
handler의 message처리

1. 자식쓰레드생성

- Activity는 하나의 Thread를 가진다.
2. 쓰레드 내용 작성
- run()함수의 작성
3. 핸들러를 이용하여 UI에 메시지 전달(run()의
mHandler.sendMessage(Message.obtain(mHandler, 1));
4. 핸들러 처리
- handleMessage(Message msg)로 처리


성공적으로 타이머를 완성시켰다. 메인쓰레드와는 별도로 서브 스레드를 생성하여 작성하였다.

아.. 질문하신 내용은 사실 내용이 상당히 많은 데요. 중요한 것만 요약해서 답변 드릴께요. ^^
UI는 Activity를 상속한 클래스가 인스턴스화되면서 하나의 쓰레드가 생기는데요.
그곳에서 다른 쓰레드를 생성하면 자식쓰레드가 되겠죠.
UI가 돌고 있는 쓰레드에 속하거나 힙에 인스턴스화된 공간에 다른(자식) 쓰레드에서 접근하는 것은 문제가 생기죠.
두개이상의 쓰레드에서 같은 공간을 동시에 접근할 가능성이 있기 때문입니다.(임계영역)

그래서 메시지큐를 이용합니다.

메인쓰레드(UI)에 핸들러를 구현하고 자식쓰레드에 인자로 넘깁니다.
자식쓰레드는 핸들러에 sendMessage...()합니다.
그러면 안드로이드 운영체제에서 스케쥴링중에 메인쓰레드의 메시지큐에 넣어 줍니다.
UI쪽에선 큐에 있는 내용을 하나씩 꺼내어 작업을 하죠.

결국 사용자가 버튼을 터치하는 등의 작업을 할 때도 똑같은 상황이 되어서
임계영역이 생기지 않습니다. 순차적으로 동작이 된다는 것이지요.

위는 서브스레드의 생성과 핸들러의 메시지 처리에 대한 안드로사이드의 히스껌님의 답변내용^^ 이해하는데 있어서 상당한 도움을 받았다! 감사합니다 ^^

 /////////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////


출처: http://tigerwoods.tistory.com/26


예제 프로젝트 다운 받기

 


 


1. 스레드(Thread) 사용의 필요성

 

A. UI 스레드 (Thread)의 중요성

 

안드로이드에서 어플리케이션이 구동되면 main이라 불리는 스레드가 하나 생성된다. main 스레드UI 스레드(앞으로 이렇게 표기)라고도 불리며, 어플리케이션의 로직과 UI간의 상호작용을 돕고, 발생한 이벤트들을 위젯에게 전달해 처리 할 수 있게 하는 등 매우 중요한 역할을 담당한다.

 

 

예를 들면, 사용자가 버튼을 터치 했을 때 다음과 같은 일이 발생한다.

  • UI 스레드가 터치 이벤트를 버튼 위젯에게 전달한다.
  • 버튼 위젯은 자기 자신의 상태를 눌림(press)상태로 바꾸고 필요한 작업을 진행한다.
  • 버튼 위젯은 자기 자신을 다시 그리라는 메시지(invalidate)를 이벤트 큐(queue)에 등록한다.
  • UI 스레드는 이벤트 큐에 등록된 request를 버튼 위젯에게 전달한다.
  • 버튼 위젯은 자기 자신의 영역을 버튼 눌림 상태로 다시 그린다.

 

 

지금까지의 포스트 전에 사용한 모든 예제는 UI 스레드에서 모든 작업을 수행해왔다.

아주 간단한 작업들 이였기 때문에 별 문제가 없었지만 복잡한 작업(시간이 오래 걸리는)을 UI 스레드에서 수행하는 경우 어떤 상황이 발생하는지 보자.

 

 

 

B. UI 스레드에서 시간이 오래 걸리는 작업의 수행

 

어 플리케이션을 개발하다 보면 어떤 작업들은 계산의 복잡도 때문에 또는 작업에 필요한 리소스가 늦게 준비되는 등의 이유로 리턴 시간이 오래 걸리는 경우가 많이 있다. 수학적으로 아주 복잡한 계산이 필요하거나, DB로부터 많은 정보를 끌어 올 때 등과 같은 경우에 수초에서 수분이 걸리는 작업을 수행해야 될 경우가 있다.

 

예를 들어 인터넷 사이트 상에서 용량이 적은 이미지를 다운 받아 화면에 표시하는 작업을 UI 스레드에 서 구현 했다. 현재 네트워크 상황이 좋지 않아 이미지를 다운 받는데 20초 정도가 걸린다고 가정해 보자. 이미지를 다운받기 시작한 후 10초 정도 경과했을 때 기다리다 지친 사용자는 '취소' 버튼을 눌렀다. 하지만 사용자가 버튼을 눌렀을 때 발생하는 버튼 클릭 메시지를 취소 버튼에 전달하는 역할을 하는 UI 스레드는 이미지 다운로드 루틴에서 아직 리턴 하지 않았으므로 사용자가 새로 발생시킨 메시지를 처리 할 수 없는 상황이 발생해 버린다. 결과적으로 사용자는 중간에 취소 하고 싶어도 취소 할 수 없을뿐더러 20초 동안 아무 반응도 하지 않는 화면을 바라보고 있어야만 한다. 심지어는 이 시간 동안 전화가 온다고 하더라도 전화를 받지 못할 수도 있다.

 

안드로이드에서는 이런 상황을 방지 하기 위해 UI 스레드가 5초 이상 반응을 하지 않는 어플리케이션의 경우 'Application Not Responding(ANR)' 다이얼로그를 팝업 시켜 사용자가 어플리케이션을 강제 종료 해 버릴 수 있도록 하고 있다.

 

이 런 극단적인 방법을 쓰는 이유는 UI가 사용자와 디바이스간의 유일한 소통 창구이며 특정 어플리케이션의 문제가 디바이스의 전반적인 운영(전화, 문자 수신 등등)에 큰 악영향을 미칠 수 있기 때문이다. 사용자가 마음대로 작동할 수 없는 디바이스는 고장 난 회로 덩어리에 불과할 뿐이다.

 

예제를 보면서 이런 경우를 한번 재현 시켜 보자.

이 번 포스트의 예제는 Progress bar 한 개와 몇 개의 버튼으로 이루어 져 있는데, 버튼을 누르면 약 6초 정도가 걸리는 작업을 UI 스레드에서 실행해 문제를 일으키거나 다양한 스레드 기법을 사용하여 문제를 해결하기도 하는 것을 보여준다. 실재 어플리케이션에서는 worker 스레드 내부는 복잡한 계산이나 DB querying, 자원 다운로드 같은 것을 수행하겠지만 본 예제에서는 SystemClock.sleep(float) 메소드로 0.6초간 시간을 때우다 progress bar를 10% 채우는 것을 100%가 될 때까지 반복한다.

 

예제는 다음과 같은 초기 화면 구성을 하고 있다.


'01. Thread 사용안함' 버튼을 누르면 두 번째 그림처럼 버튼이 클릭된 상태로 6초간 디바이스의 모든 동작이 정지한것 처럼 보인다 (UI 스레드가 block 되었다고 표현한다). 이 동안은 현재 실행중인 작업을 중단하거나, 어플리케이션을 종료하거나, 심지어 전화를 받는 작업조차 불가능 하며, 6초 정도가 지나면 block이 풀리면서 Progress Bar가 한번에 100%로 변해 버린다.

 

이 6초 동안 다른 버튼을 클릭하는 등 UI를 사용하면 메시지를 바로 해당 UI 위젯에 전달 할 수 없음으로(복잡한 계산을 수행 중) 메시지 queue에 보관된다. UI 스레드가 복잡한 작업을 완료 후 메시지 queue에 있는 다음 메시지를 처리할 때 5초 이상 대기한 메시지가 있다면 안드로이드는 다음과 같은 ANR 창을 팝업 시켜 사용자에게 문제가 있는 어플리케이션임을 알리고 강제종료 시킬지 계속 기다릴지 선택할 수 있게 한다. (ANR 상황을 재현하기 위해서는 예제에서 1번 버튼을 클릭하자 마자 2번 버튼을 클릭하고 작업이 완료되기를 기다린다. 1번 버튼만 클릭하고 아무것도 안하고 기다리면 ANR창이 안 나타나니 참고)


 

 

이와 같이 시간이 오래 걸리는 작업은 UI thread가 아닌, worker / background 라고 불리는 별도의 스레드에서 실행 함으로 UI 스레드의 block을 해결할 수 있다.


이제부터 안드로이드에서는 어떻게 스레드(thread)를 구현하는지 살펴 보자.

 

 

 

 

2. 스레드(Thread) 구현에 필요한 것들

 

A. Thread 객체

 


Thread 는 어떤 작업들을 병렬로 실행 가능하게 하는 자바 객체이다. 각 Thread는 CPU로부터 타임퀀텀이라는 사람이 느낄 수 없는 정도로 짧은 CPU 점유 시간을 각각 번갈아 가면서 할당 받아 자신에게 할당된 작업을 진행함으로 엄밀한 의미에서는 진짜 병렬수행은 아니지만 인간의 관점에서 보면 너무 빨리 이런 일이 진행 되기 때문에 2개 이상의 작업이 동시에 일어나는 것처럼 보인다. Thread는 자신이 실행할 메소드, 인자, local 변수를 위한 별도의 callstack을 가진다. 같은 VM내부(안드로이드에서 각각의 어플리케이션은 별도의 VM을 할당 받는다)의 Thread들은 상호작용(interact)이 가능하며, Thread 자신이 제공하는 여러 메소드나 Object로부터 상속하는 여러 객체를 사용해 Thread간의 동기(Synchronization)를 유지할 수 있다. (스레드의 동기화는 안드로이드만의 주제가 아님으로 깊은 설명은 추후 기회가 된다면 다루려고 한다)

 

안드로이드에서 Thread 객체를 사용하는 방법은 2가지가 있는데,

첫째는 Thread 클래스를 subclassing하는 새 객체를 정의해고 Thread:run() 메소드를 오버라이딩 하는 방법이고,

둘째는 new 연산자로 새로운 Thread 객체를 생성하면서 생성인자로 Runnable 인터페이스(스레드에서 실행될 logic포함)를 전달하는 방식이다.

 

데모에서는 두 번째 Runnable 인터페이스를 사용하는 방식을 사용했다.

 

 

 

B. Runnable 인터페이스

 


 

Runnable 인터페이스는 위와 같은 상속 구조를 가지고 있으며, 딱 하나의 abstract 메소드를 제공 하는데, 바로 run()이란 메소드이다.

새 Work 스레드 생성시 Thread객체의 생성인자로 전달되는  Runnable 인터페이스의 run() 메소드는 새 work 스레드가 실행할 작업을 포함해야하며, run()은 생성된 새 work 스레드가 시작되면 자동으로 호출된다.

 

 

 

 

3. 스레드(Thread)에서의 UI 업데이트

 

위에 설명한 Thread와 Runnable을 사용해 다음과 같이 별도의 work 스레드를 구현 한다면 UI blcking 문제를 해결 할 수 있다. 하지만 한가지 주의할 점이 있으니 살펴보자.

 

잠재적 문제를 가지는 Thread 구현

접기

01 public void onClick(View v) {
02     ......
03  
04     if else (v == btnThread01) {
05         // 새로운 스래드와 내부에서 실행될 Logic을 Runnable로 객체화 함.
06         Runnable increaseProgress = new Runnable() {
07              
08             // Runnable interface의 유일한 abstract method 구현.
09             // run() 메소드는 본 Runnable객체가 공급된 Thread가 시작할때 자동으로 호출.
10             public void run() {
11                 for(nProgress = 0; nProgress<=100; nProgress+=10) {
12                     try {
13                         bar.setProgress(nProgress);
14                     }
15                     catch(Exception e) {
16                         Log.w("h-exception", e.toString());                        
17                     }
18                     SystemClock.sleep(600);
19                 }
20             }
21         };
22          
23          
24         // 위에 선언한 Runnable객체에 구현된 logic을 실행할 새로운 Thread 객체 생성
25         Thread worker = new Thread(increaseProgress);
26          
27         // 새로운 스레드 시작됨 -> Runnable interface의 run()메소드가
28         // 새로운 스레드에서 실행됨.
29         worker.start();
30     }
31  
32     ......
33 }

접기

 

 

위 의 코드를 실행 시켜 보면 6초간에 걸쳐 10%씩 순차적으로 100%까지 증가하는 정상적인 progress bar가 구현된 것을 볼 수 있다. 또 전 예제와 다르게 UI가 block되지 않아 다른 UI이벤트에 대해서도 즉각 반응함을 볼 수 있다. 하지만 위의 예제는 잠재적으로 심각한 오류를 일으킬 소지를 가지고 있다.

 

안드로이드 UI 위젯들은 스레드 세이프 (Threadsafe)하게 디자인 되지 않았기 때문이다.

 

만 약 두 개 이상의 스레드가 동시에 UI위젯 자원에 접근하여 이를 조작 하려고 한다면 개발자가 예상하지 못한 결과가 일어날 수도 있다. 여기서 사용된 ProgressBar객체는 별 문제 없이 작동하지만, UI스레드에서 생성된 TextView가 worker 스레드에서 직접 컨트롤 되면, run-time exception이 발생하며 exception 처리가 안되어 있을 경우 어플리케이션이 강제 종료 될 수도 있다.

 

사용자에게 좀 더 낳은 UX(User eXperience)를 제공하기 위해 사용된 Thread가 자칫 잘못 사용되면 어플리케이션의 안정성을 해치기 때문에 올바르게 구현하는 것이 중요하다.

 

Worker 스레드에서 실행된 결과가 UI 위젯에 안전하게 반영되기 위해서는 어떻게 코드를 작성해야 하는지 다음 단락에서 살펴보자.

 

 

 

 

4. 올바른 스레드(Thread) 구현 방법

 

안드로이드는 복수의 스레드가 하나의 UI 위젯에 동시에 접근해서 일어날 수 있는 잠재적인 문제를 해결하기 위해 다음과 같은 절차로 work스레드가 UI위젯에 접근하게 끔 어플리케이션을 구성한다.

  • worker 스레드가 UI 위젯에 적용할 작업을 메시지 queue에 추가 함
  • UI 스레드가 메시지를 dispatch해 해당 UI위젯에 메시지를 전달 함
  • UI 위젯은 메시지 대로 자신의 상태를 update함.

 

정리하면 하나의 자원에 동시에 접근할 수 있는 스레드를 하나로 제한해서 혼선을 줄이는 것이 핵심이다. 이를 그림으로 표현하면 다음과 같다.

(물론 일반적인 스레드 동기화 방법 mutex, semaphore, critical section등을 사용할 수도 있다).

 


 

 

안드로이드에서는 위와 같이 UI스레드만 UI위젯에 접근하게 하기 위해 3가지 방법을 사용 할 수 있는데 각각 다음과 같다.

 

 

 

A. Thread 구현 예 01: View:post(…) 이용

 

View 클래스는 post() 메소드를 제공 하는데, 메소드의 설명은 다음과 같다.


public boolean post (Runnable action)

parameter

action: 메시지 queue에 queue될 Runnable 객체.

return

true: Runnable객체가 메시지 queue에 성공적으로 queue됨.

false: Runnable 객체를 메시지 queue에 queue하는데 실패함.

 

 

View:post(…) 메소드는 다음과 같이 사용 가능하다.

접기

01 public void onClick(View v) {
02     // ......
03     else if (v == btnThread02) {
04         new Thread(new Runnable() {
05             @Override
06             public void run() {
07                 for(nProgress = 0; nProgress<=100; nProgress+=10) {
08                      
09                     // ProgressBar 객체는 View로 부터 상속 함.
10                     // post 메소드는 UI 스레드의 메시지 큐에 bar객체에게 전달할
11                     // Runnable을 queue함.
12                     bar.post(new Runnable() {
13                         public void run() {
14                             // UI 스레드 메시지 큐에 저장될 메시지의 핵심
15                             bar.setProgress(nProgress);
16                         }
17                     });
18                     // 실제로는 복잡한 계산등 시간이 소모되는 작업 수행 되겠지만
19                     // 데모에서는 0.6초간 sleep함  
20                     SystemClock.sleep(600);
21                 }
22             }
23         }).start(); // 스레드 실행  
24     }
25     //......
26 }

접기

 

 

위의 코드에서 보면 2개의 Runnable 객체가 사용되는데 목적을 정리하면 다음과 같다.

새 Thread의 생성인자로 전달되는 Runnable: Thread가 start() 메소드에 의해 시작되면 자동 실행됨. 이 Runnable 내부에서 bar.post()와 sleep을 실행함.

bar.post(…)의 인자로 전달되는 Runnable: UI 스레드의 메시지 큐에 메시지(ProgressBar인스턴스 bar를 증가 시킴)를 등록 함.

 

결과적으로, 새로 생성된 work 스레드의 역할은 긴 시간이 필요한 작업을 처리 후 특정자원(UI 위젯 등)의 상태 update 명령을 메시지 큐에 등록하는 것까지이고, 저장된 메시지가 UI위젯에 전달 되는 시기는 전적으로 UI스레드의 상태에 따라 결정되게 된다.

 

우선 순위가 높은 작업(전화, 문자 수신 등)의 메시지가 갑자기 발행하면 저장된 메시지(UI위젯 update)의 앞으로 등록(세치기) 시켜 우선 적으로 처리 함으로 디바이스의 신뢰성을 높일 수 있다.

 

 

 

B. Thread 구현 예 02: Activity:runOnUiThread(…) 이용

 

또 다른 방법은 Activity가 제공하는 runOnUiThread(…) 메소드를 사용하는 방법이다.

우선 runOnUiThread(…) 메소드의 설명을 살펴보자.


public final void runOnUiThread (Runnable action)

Parameter

action: 바로 실행되거나 메시지 큐에 등록될 메시지 (runOnUiThread()가 call 되는 위치에 따라 다름)


 

코드 내부에서는 다음과 같이 사용 가능하다.

접기

01 public void onClick(View v) {
02     //......
03     else if (v == btnThread03) {
04         new Thread(new Runnable() {
05             @Override
06             public void run() {
07                 for(nProgress = 0; nProgress<=100; nProgress+=10) {
08                     // 현재 UI 스레드가 아님으로 메시지 큐에 Runnable을 등록 함
09                     runOnUiThread(new Runnable() {
10                         public void run() {
11                             // 메시지 큐에 저장될 메시지의 핵심
12                             bar.setProgress(nProgress);
13                         }
14                     });
15                     // 복잡한 작업 simulating   
16                     SystemClock.sleep(600);
17                 }
18             }
19         }).start();
20     }  
21     //......
22 }

접기

 

 

runOnUiThread 메소드의 특징은 자신이 어디서 call 되었는지에 따라 처리 방법이 다른 것인데, UI스레드 내부에서 call되었으면 인자로 제공된 Runnable이 바로 실행되고, UI 스레드가 아니라면 메시지 큐에 등록 시켜서 UI스레드의 스케쥴에 맞춰 실행 할 수 있게 처리한다. 예제에서는 별도의 work 스레드 안에서 사용되었으므로 바로 실행되지 않고 메시지 큐에 등록된다.

 

 

 

C. Thread 구현 예 03: Handler객체 이용

 

마지막으로 android.os.Handler객체를 사용한 Thread구현 방법이 있다. 우선 Handler의 상속 구조는 다음과 같다.

 

 

Handler객체에서 메시지 큐에 메시지를 추가하는 메소드는 다음 두 가지 이다.


final boolean post(Runnable r)

parameter

r: 메시지 큐에 추가 할 Runnable.

return

true: 메시지 큐에 성공적으로 r 추가

false: r을 메시지 큐에 추가 실패

 

 

코드 내부에서 사용하는 방법은 다음과 같다.

Handler객체를 사용한 스레드구현 01 – post(…) 이용

접기

01 public class MyActivity extends Activity {
02  
03     private ProgressBar mProgress;
04     private int mProgressStatus = 0;
05  
06     private Handler mHandler = new Handler();
07  
08     protected void onCreate(Bundle icicle) {
09         super.onCreate(icicle);
10  
11         setContentView(R.layout.progressbar_activity);
12  
13         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
14  
15         // 오래 걸리는 작업을 work 스레드에서 실행 함
16         new Thread(new Runnable() {
17             public void run() {
18                 while (mProgressStatus < 100) {
19                     mProgressStatus += 10;
20  
21                     // PorgressBar인스턴스 update 메시지를 메시지 큐에 등록
22                     mHandler.post(new Runnable() {
23                         public void run() {
24                             // work 스레드 내부에서 명령이 실행되는 것이 아니라
25                             // post(..)에 의해 메시지 큐에 추가됨.
26                             mProgress.setProgress(mProgressStatus);
27                         }
28                     });
29                 }
30             }
31         }).start();
32     }
33 }

접기

 

 

 

다음은 두 번째 방법이다.

 

final boolean sendMessage(Message msg)

parameter

msg: 메시지 큐에 추가될 메시지.

return

true: msg가 성공적으로 메시지 큐에 추가 됨

false: msg가 메시지 큐에 추가 되지 않음

 

 

sendMessage(Message)를 사용할 경우는 다음과 같이 구현한다.

Handler객체를 사용한 스레드구현 02 – sendMessage(…) 이용

접기

01 public class MyActivity extends Activity {
02  
03     private ProgressBar mProgress;
04     private int mProgressStatus = 0;
05  
06     // Handler 객체를 생성하고
07     // handleMessage(Message)를 오버라이딩하여
08     // mHandler 인스턴스에게 전달 되는 모든 메시지를 처리함.
09     private Handler mHandler = new Handler(){
10         @Override
11         // 어떤 메시지가 전달되어도 progress bar를 설정함.
12         public void handleMessage(Message msg) {
13             mProgress.setProgress(mProgressStatus)
14         }
15     };
16  
17     protected void onCreate(Bundle icicle) {
18         super.onCreate(icicle);
19  
20         setContentView(R.layout.progressbar_activity);
21  
22         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
23  
24         // 오래 걸리는 작업을 work 스레드에서 실행 함
25         new Thread(new Runnable() {
26             public void run() {
27                 while (mProgressStatus < 100) {
28                     mProgressStatus += 10;
29  
30                     // 메시지를 발생시킴. 메시지 큐에 저장되며
31                     // UI 스레드는 메시지를 mHandler 객체로 전달함.
32                     // 결과적으로 UI 스레드에서 
33                     // mHnadler의 handleMessage(Message)에 의해 처리됨.
34                     mHandler.sendMessage(mHandler.obtainMessage());          
35                 }
36             }
37         }).start();
38     }
39 }

접기

 

 

 

 

지금까지 스레드의 필요성과, 스레드 구현 시 주의할 점, 안전한 스레드 구현 방법 등을 살펴 보았다.

크게 문제는 없지만 코드가 상당히 복잡해 지는 단점이 있었다.

다음 포스트에서는 AsyncTask 객체를 통해 좀더 간단한 코드로 스레드를 구현하는 방법에 대해 살펴 보려고 한다.


/////////////////////////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////

출처: http://tigerwoods.tistory.com/28

 

예제 프로젝트 다운 받기


 

 

전 포스트에 서 설명했던 여러 스레드 구현방법들은 비록 아무 문제가 없지만 구현방법이 복잡해서 코드를 읽기 힘들게 만드는 경향이 있었다. Background작업에 관한 모든 사항(스레드 객체 생성, 사용, UI스레드와 통신 등)이 Activity 코드에 포함 되고 특히 background 스레드가 UI위젯과 빈번한 통신을 할수록 Activity 코드의 복잡함은 점점 배가 된다.

 

안드로이드에서는 이런 문제를 해결하기 위해 API level 3 (1.5 version) 부터 AsyncTask라는 클래스를 제공하고 있다.


AsyncTask 클래스는 background작업을 위한 모든 일(스레드생성, 작업실행, UI와 통신 등)을 추상화 함으로 각각의 background작업을 객체 단위로 구현/관리 할 수 있게 하는것이 목적이다. 그림으로 표현하면 다음과 같다.





참고로 1.0과 1.1 version의 API를 사용하는 디바이스에서는 구글 code에 공개되어 있는 UserTask 라는 클래스를 어플리케이션 프로젝트에 복사해 넣어 사용할 수 있다. 기능과 사용법은 AsyncTask와 완전히 동일하다.

 

그럼 AsyncTask에 관해 자세히 살펴보자.


 

 

1. AsyncTask 클래스 소개

 

AsyncTask 라는 클래스 이름은 Asynchronous Task의 줄임이며, UI스레드의 입장에서 볼 때 비동기적으로 작업이 수행되기 때문에 붙여진 이름이다. (Ajax: Asynchronous javascript and XML 에서 사용된 의미와 같다)

 

AsyncTask의 상속관계는 다음과 같다.


 

Object로부터 상속하는 AsyncTask는 Generic Class이기 때문에 사용하고자 하는 type을 지정해야 한다.


AsyncTask클래스의 사용시 지정해야 하는 generic type은 각각 다음의 용도로 사용된다.

  • Params: background작업 시 필요한 data의 type 지정
  • Progress: background 작업 중 진행상황을 표현하는데 사용되는 data를 위한 type 지정
  • Result: background 작업 완료 후 리턴 할 data 의 type 지정

 

 

그림으로 각 generic type이 결정하는 것들을 표현하면 다음과 같다.

(각 메소드의 자세한 정보는 다음 단락의 예제 코드와 설명 참조)


 

 

만약 type을 정할 필요가 없는 generic이 있다면 void를 전달하면 된다.

예. …AsyncTask<void, void, void> {…}

 

 

 

 

2. AsyncTask의 사용

 

우선 AsyncTask가 어떻게 사용되는지 예제 소스를 보자.

AsyncTask 클래스의 사용 예

접기

001 package com.holim.test;
002  
003 import android.app.Activity;
004 import android.os.AsyncTask;
005 import android.os.Bundle;
006 import android.os.SystemClock;
007 import android.view.View;
008 import android.widget.Button;
009 import android.widget.ProgressBar;
010 import android.widget.TextView;
011  
012 public class AsyncTaskDemo extends Activity
013                 implements View.OnClickListener {
014      
015     ProgressBar progressBar;
016     TextView textResult;
017     Button btnExecuteTask; 
018      
019     /** Called when the activity is first created. */
020     @Override
021     public void onCreate(Bundle savedInstanceState) {
022         super.onCreate(savedInstanceState);
023         setContentView(R.layout.main);
024      
025         progressBar = (ProgressBar)findViewById(R.id.progressBar);
026         textResult = (TextView)findViewById(R.id.textResult);
027         btnExecuteTask = (Button)findViewById(R.id.btnExecuteTask);
028          
029         btnExecuteTask.setOnClickListener(this);
030     }
031      
032     public void onClick(View v) {
033          
034         // AsynchTask를 상속하는 DoComplecatedJob 클래스를 생성하고
035         // execute(...) 명령으로 background작업을 시작함.
036         // (예제에 구현된 AsynchTask는 String 형의 인자를 받음)
037         new DoComplecatedJob().execute("987",
038                                         "1589",
039                                         "687",
040                                         "399",
041                                         "1722",
042                                         "50");     
043     }
044      
045      
046     // AsyncTask클래스는 항상 Subclassing 해서 사용 해야 함.
047     // 사용 자료형은
048     // background 작업에 사용할 data의 자료형: String 형
049     // background 작업 진행 표시를 위해 사용할 인자: Integer형
050     // background 작업의 결과를 표현할 자료형: Long
051     private class DoComplecatedJob extends AsyncTask<String, Integer, Long> {      
052     
053      
054         // 이곳에 포함된 code는 AsyncTask가 execute 되자 마자 UI 스레드에서 실행됨.
055         // 작업 시작을 UI에 표현하거나
056         // background 작업을 위한 ProgressBar를 보여 주는 등의 코드를 작성.
057         @Override
058         protected void onPreExecute() {
059             textResult.setText("Background 작업 시작 ");           
060             super.onPreExecute();
061         }
062  
063         // UI 스레드에서 AsynchTask객체.execute(...) 명령으로 실행되는 callback
064         @Override
065         protected Long doInBackground(String... strData) {
066             long totalTimeSpent = 0;
067              
068             // 가변인자의 갯수 파악 (이 예제에서는 5개)
069             int numberOfParams = strData.length;
070              
071             // 인자들을 이용한 어떤 작업을 처리를 함
072             for(int i=0; i<numberOfParams; i++) {              
073                  
074                 // 각 인자를 이용한 복잡한 Task 실행함.
075                 // 예제에서는 인자로 전달된 시간만큼 sleep
076                 SystemClock.sleep(new Integer(strData[i]));
077                  
078                 // background 작업에 걸린시간을 누산해 리턴함
079                 totalTimeSpent += new Long(strData[i]);
080                  
081                 // onProgressUpdate callback을 호출 해
082                 // background작업의 실행경과를 UI에 표현함
083                 publishProgress((int)(((i+1)/(float)numberOfParams)*100));
084             }          
085             return totalTimeSpent;
086         }
087          
088         // onInBackground(...)에서 publishProgress(...)를 사용하면
089         // 자동 호출되는 callback으로
090         // 이곳에서 ProgressBar를 증가 시키고, text 정보를 update하는 등의
091         // background 작업 진행 상황을 UI에 표현함.
092         // (예제에서는 UI스레드의 ProgressBar를 update 함)
093         @Override
094         protected void onProgressUpdate(Integer... progress) {
095             progressBar.setProgress(progress[0]);
096         }
097          
098         // onInBackground(...)가 완료되면 자동으로 실행되는 callback
099         // 이곳에서 onInBackground가 리턴한 정보를 UI위젯에 표시 하는 등의 작업을 수행함.
100         // (예제에서는 작업에 걸린 총 시간을 UI위젯 중 TextView에 표시함)
101         @Override
102         protected void onPostExecute(Long result) {
103             textResult.setText("Background 작업에 걸린 총 시간: "
104                             + new Long(result).toString()
105                             + "m초");   
106         }
107          
108         // AsyncTask.cancel(boolean) 메소드가 true 인자로
109         // 실행되면 호출되는 콜백.
110         // background 작업이 취소될때 꼭 해야될 작업은  여기에 구현.
111         @Override
112         protected void onCancelled() {
113             // TODO Auto-generated method stub
114             super.onCancelled();
115         }      
116     }
117 }

접기

 

 

AsyncTask 클래스는 다음과 같이 중요한 callback들을 제공 함으로 상황에 맞게 오버라이딩 해야 한다.

  • protected void onPreExecute(): Background 작업이 시작되자마자 UI스레드에서 실행될 코드를 구현해야 함. (예. background 작업의 시작을 알리는 text표현, background 작업을 위한 ProgressBar popup등)
  • protected abstract Result doInBackground(Params… params): Background에서 수행할 작업을 구현해야 함. execute(…) 메소드에 입력된 인자들을 전달 받음.
  • void onProgressUpdate(Progress... values): publishProgress(…) 메소드 호출의 callback으로 UI스레드에서 보여지는 background 작업 진행 상황을 update하도록 구현함. (예. ProgressBar 증가 등)
  • void onPostExecute(Result result): doInBackground(…)가 리턴하는 값을 바탕으로 UI스레드에 background 작업 결과를 표현하도록 구현 함. (예. background작업을 계산한 복잡한 산술식에 대한 답을 UI 위젯에 표현함 등)
  • void onCancelled(): AsyncTask:cancel(Boolean) 메소드를 사용해 AsyncTask인스턴스의 background작업을 정지 또는 실행금지 시켰을 때 실행되는 callback. background작업의 실행정지에 따른 리소스복구/정리 등이 구현될 수 있다.

 

 

또, AsyncTask 클래스는 background 작업의 시작과 background 작업 중 진행정보의 UI스레드 표현을 위해 다음과 같은 메소드를 제공한다.

  • final AsyncTask<…> execute(Params… params): Background 작업을 시작한다. 꼭 UI스레드에서 호출하여야 함. 가변인자를 받아들임으로 임의의 개수의 인자를 전달할 수 있으며, 인자들은 doInBackground(…) 메소드로 전달된다.
  • final void publishProgress(Progress... values): Background 작업 수행 중 작업의 진행도를 UI 스레드에 전달 함. doInBackground(…)메소드 내부에서만 호출.

 

 

위의 메소드들은 AsyncTask 클래스를 이용해 구현된 background 작업 시 다음과 같은 형태로 사용된다.

 


 

위 의 그림에서 처럼AsyncTask인스턴스는 자기 자신을 pending, running, finished 이렇게 세 가지 상태(status)로 구분하는데 각각 AsyncTask:Status 클래스에 상수 PENDING, RUNNING, FINISHED로 표현 될 수 있다.

현재 AsyncTask인스턴스의 상태는 다음 메소드를 호출해서 얻을 수 있다.


public final AsyncTask.Status getStatus ()

return

AsyncTask인스턴스의 상태정보를 AsyncTask.Status 객체의 상수 값 PENDING, RUNNING, FINISHED 중에서 리턴.

 

 

또, AsyncTask클래스는 background 작업을 정지, 또는 시작금지 시키기 위해 다음 메소드를 제공한다. 이 메소드가 성공적으로 호출되면 onCacelled() callback이 호출되니 onCacelled()에 적절한 뒤처리를 해주어야 한다.


final boolean cancel (boolean mayInterruptIfRunning)

parameter

mayInterruptIfRunning: true값을 제공했을 때 background작업이 실행 중일 경우(running 상태) 작업을 중단 시키고, 준비 중(pending 상태) 일 경우 작업을 실행 금지 시킴. (execute() 명령 사용 불가. 사용하면 exception 발생)

return

true: background작업을 성공적으로 중지하거나 실행 금지 시킴

false: 벌써 작업이 완료된 상태(finished 상태) 일 경우 리턴

 

 

마지막으로 AsyncTask 사용해 background작업을 구현 시 꼭 지켜야 하는 사항들이다.

  • AsyncTask클래스는 항상 subclassing 하여 사용하여야 한다.
  • AsyncTask 인스턴스는 항상 UI 스레드에서 생성한다.
  • AsyncTask:execute(…) 메소드는 항상 UI 스레드에서 호출한다.
  • AsyncTask:execute(…) 메소드는 생성된 AsyncTask 인스턴스 별로 꼭 한번만 사용 가능하다. 같은 인스턴스가 또 execute(…)를 실행하면 exception이 발생하며, 이는 AsyncTask:cancel(…) 메소드에 의해 작업완료 되기 전 취소된 AsyncTask 인스턴스라도 마찬가지이다. 그럼으로 background 작업이 필요할 때마다 new 연산자를 이용해 해당 작업에 대한 AsyncTask 인스턴스를 새로 생성해야 한다.
  • AsyncTask의 callback 함수 onPreExecute(), doInBackground(…), onProgressUpdate(…), onPostExecute(…)는 직접 호출 하면 안 된다. (꼭 callback으로만 사용)

 


 


반응형


관련글 더보기

댓글 영역