티스토리 뷰
여기에서 정리하는 것을 요약해서 말하자면 별도의 Thread를 생성하고 사용 할 경우, 그 안에서는 UI 관련 객체(Button, TextView 등)에 대해서는 변경을 가할 수 없기 때문에 이를 해결 할 수 있는 방법을 정리하는 것이다.
- Thread 사용시 Handler의 필요성
안드로이드 어플리케이션이 실행되면 안드로이드에선 UI의 처리를 위해 사용되는 기본 쓰레드를 생성하는데 이것을 '메인쓰레드'라고 한다.
그런데 어떤 작업을 백그라운드로 실행 시키면서 그 작업의 상황을 중간중간 보고 받을 필요가 있는데, 백그라운드를 만드는 작업은 새로운 Thread를 만들어서 할 수 있다.
이를테면 <ProgressBar>의 진행상황을 TextView에서 표현할 상황이 생길 수 있다.
새로운 Thread를 이용하여 ProgressBar의 진행률을 시간 별로 캐치하여 변경할 수 있지만, 그 새로운 Thread에서 TextView 진행률을 표시해야 하기 때문에 이 객체에 접근해야 하는데.. 결과적으로 이 UI객체인 TextView에 접근할 수가 없다.
이유는,
안드로이드에서는 UI객체는 '메인쓰레드'가 아닌 쓰레드에서는 변경을 가할 수 없도록 하기 때문이다.
새로운 Thread를 이용해 ProgressBar의 진행률을 표시하면서 TextView에 변경을 가하려고 했을 때의 코드이다.
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 | public class TestActivity extends AppCompatActivity { ProgressBar bar; boolean isRunning; Button btn; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); //레이아웃 XML에 선언한 뷰 객체를 가져온다. bar = (ProgressBar) findViewById(R.id.testProgressBar); btn = (Button) findViewById(R.id.testBtn); //ProgressThread 생성 Runnable r = new ProgressThread(); Thread t = new Thread(r); //ProgressThread 시작 t.start(); btn.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View view) { btn.setText("작업량"+ bar.getProgress()); // Toast.makeText(TestActivity.this, , Toast.LENGTH_SHORT).show(); } }); } public class ProgressThread implements Runnable{ @Override public void run() { while(!isRunning){ try { Thread.sleep(100); bar.incrementProgressBy(1); btn.setText("작업량.."+ bar.getProgress() + "%"); if(bar.getProgress() == 100){ break; } } catch (InterruptedException e) { e.printStackTrace(); } } } } } | cs |
위 코드는 44라인에서 다음과 같은 에러를 내뱉는다.
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
잘못된 쓰레드에서 이 객체를 호출 하였다는 에러다.
더 상세히 읽어보면 이 뷰(버튼)가 만들어진 계층의 쓰레드에서만 이 뷰를 건들 수 있다는 것이다.
안드로이드에서 UI관련 객체는 '메인쓰레드'에서만 접근이 가능하다고 했다. 그러니까 에러 문구에서 말하는 original Thread는 메인쓰레드를 말하고, 메인쓰레드에서만이 이 뷰(버튼)에 접근할 수 있다는 얘기다.
이 같은 문제를 해결하기 위해 Handler가 있다.
안드로이드 애플리케이션이 실행될 때 '메인쓰레드'는 메세지 큐(Message Queue)라는 것을 실행하게 되는데, 이 메세지 큐를 이용해서 사용자 정의 쓰레드에서 처리할 작업을 메인쓰레드에서 처리할 수 있도록 할 수 있다.
Hadler의 sendMessage(Message msg)메서드를 이용하여 메세지 큐에 Message를 넣을 수 있다. 매개변수 파라미터로 들어가는 Message타입은 Handler타입의 메서드인 obtainMessge()를 호출하면 참조할 수 있다.
sendMessage(..)로 인해 메세지 큐에 들어간 Message는 차례대로 Handler가 처리하게 되며, 처리는 Handler를 확장한 클래스의 오버라이딩 된 handleMessage()가 담당한다.
+ 레이아웃 XML
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 | <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.choonie.threadapp.MainActivity"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="작업중..." /> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:progress="0" android:max="100" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" /> </LinearLayout> | cs |
+ MainActivity
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 | public class MainActivity extends AppCompatActivity { Handler handler; ProgressBar bar; TextView tv; boolean running; ProgressThread t; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bar = (ProgressBar) findViewById(R.id.progressBar); tv = (TextView) findViewById(R.id.textView); //Handler의 handleMessage(Message msg)를 오버라이딩한 인스턴스를 생성 handler = new Handler() { //이 곳에서 UI와 관련된 객체의 작업을 호출 //Thread안에서 UI 관련된 객체에 변경을 가할 때는 에러를 내뱉었지만 여기서는 정상적으로 된다. @Override public void handleMessage(Message msg) { //프로그래스바의 값을 1증가 시킨다. bar.incrementProgressBy(1); //프로그래스 값이 100이라면 작업완료 if(bar.getProgress() == 100){ running = true; tv.setText("작업완료!"); }else { tv.setText("작업중... " + bar.getProgress() + "%"); } } }; } @Override protected void onStart() { super.onStart(); bar.setProgress(0); t = new ProgressThread(); t.start(); } @Override protected void onPause() { super.onPause(); t = null; running = false; } public class ProgressThread extends Thread { Message msg; @Override public void run() { try { while (!running) { Thread.sleep(100); //메세지 큐에 작업을 보내는 sendMessage(..)를 호출하기 전에 obtainMessage()로 Message객체를 가져온다. msg = handler.obtainMessage(); //메세지 큐에 작업을 보낸다. 보낸 작업은 큐에서 순서대로 해당 handler의 handleMessage(..)를 호출하게 된다. handler.sendMessage(msg); } } catch (InterruptedException e) { e.printStackTrace(); tv.setText("작업중에 에러가 발생했습니다!"); } } } } | cs |
위와 같이 Handler를 이용하게 되면 사용자 정의 쓰레드에서 sendMessage(..)를 호출만 하면 해당 Handler의 handleMessage(..)가 호출되어 그 안에서 UI객체에 대한 접근을 할 수 있다.
- Handler를 사용하면서 시간차를 두고 작업 실행하기
Handler클래스는 또한 해당 메세지 큐에 들어간 작업을 몇초 후에 실행할 건지, 또는 언제 실행할 건지를 결정할 수 있는 메서드를 제공하고 있다.
- boolean sendMessageAtTime(Message msg, long timeMillis) : 언제 큐에 들어간 작업을 실행할 건지?
- boolean sendMessageDelayed(Message msg, long delayMillis) : 몇 초 후에 큐의 작업을 실행할 건지?
테스트 해본 결과 최초로 호출했을 때만 적용이 되고, 이 후에는 일반적인 sendMessage(..)를 호출했을 때와 같이 딜레이 없이 실행된다.
- 사용자 정의 쓰레드로
'Android > 정리' 카테고리의 다른 글
타 액티비티 전환 또는 종료 애니메이션 효과 주기 (0) | 2016.10.07 |
---|---|
Bitmap 객체 (0) | 2016.09.21 |
그리기 객체 (0) | 2016.09.19 |
안드로이드에서 서블릿을 호출하여 MySQL 질의 결과 가져오기 (0) | 2016.09.08 |
리스트 뷰 (ListView) (0) | 2016.09.07 |