Part2. 지난 시간까지는 기본적인 안드로이드 스레드, 프로세스에 대해서 설명하였다면, 이번씨간 부터는 어떤 방식으로 실제 구현하는가에 대한 초점을 맞추며 배워보도록 하자.
오늘 내용. 스레드 사용의 몇 가지 기본사항을 다루고, 안드로이드 구성요소와 협력하는 스레드에 대해 논의 하고, 스레드 관리로 마무리한다.(태스크의 취소, Activity, Fragment객체에서 스레드를 유지하는 방법)
7.1 기본 사항
안드로이드 thraed 클래스는 일반 자바 프로그래밍의 Thread클래스와 같고, Thread 클래스는Runnable로 표현되는 태스크를 위한 실행 환경을 만든다.
7.1.1 생명주기 스레드가 존재하는 동안에 나타날 수 있는 관찰 가능한 상태를 설명한다. (Thread.State) 다음과 같이 생성 -> 실행 / (차단/대기) -> 종료 순으로 설명된다. - 생성
Thread 객체의 인스턴스화, 기본적으로 스레드 생성시 생성한 대상의 동일한 우선순위로 할당된다 (UI스레드에서 생성됐다면 UI스레드와 같은 우선순위 그룹) - 실행
Thread.start() 호출될 때 실행될 준비가 된 것. (Runnable 상태- 스케줄러가 실행을 위해 스레드를 선택하면 run()이 호출된다.) - 차단/대기
Thread.sleep() 이나, Thread.yield() (어떤 스레드를 실행할지 결정하게 함) 가 불렀을 경우 차단 혹은 대기한다. - 종료
run이 종료되면 스레드가 종료되고 스레드 자원이 해지된다.
7.1.2 인터럽트
응용프로그램은 그 태스크가 완료되기 전에 스레드 실행을 종료하길 원할 때(즉, UI스레드에서 다운로드 중 중단 버튼을 누를 때) 스레드에 인터럽트를 요청하여 중단할 수 있다.Thread t = new SimpleThread();
t.start(); // 스레드 시작
t.interrupt(); // 인터럽트 요청
인터럽트는 스레드 내부 플래그만 설정하고 스레드에 영향을 주지 않으므로, 스레드는 다른 스레드가 자신을 인터럽트하고 종료할 수 있도록 취소 지점을 구현해야 한다. public class SimpleThread extends Thread {
@Override
public void run() {
while(isInterrupted() == false) {
//스레드가 살아 있다.
}
// 테스크가 완료되고 스레드가 종료된다.
}
}
스레드가 이미 차단된 스레드라면 인터럽트 시 InterruptException을 던진다. 때문에 스레드가 종료되기 전에 스레드는 catch절에서 현재 공유 객체의 상태를 정리하는 것이 바람직하다. void myMethod() {
try {
// 차단 호출
} catch (InterruptedException e) {
// 1. 정리 작업
// 2. 다시 한 번 인터럽트
Thread.currentThread().interrupt();
}
}
※ Thread.stop() 은 비 일관적이기 때문에 사용하지 마라.
7.1.3 잡히지 않는 예외
기본적으로 스레드는 Runnable.run() 메서드의 끝의 경로에 도달할 때 정상적으로 종료한다. 그러나 코드에서 예상치 못한 에러가 발생하면, 확인되지 않은 예외가 발생한다. 이를 디버깅 하기 위해서 스레드가 종료되기 전에 UncaughtExceptionHandler를 부착할 수 있다.
스레드 전역 핸들러 static void setDefaultUncaughtExceptionHandler( Thread.UncaughtExceptionHandler handler);
스레드 지역 핸들러 void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler);
지역, 전역 모두 가지면 지역 핸들러가 더 우선순위되어 호출된다.Thread t = new Thread(new Runnable() {
@Override
public void run() {
throw new RuntimeException("Unexpected error Occured");
}
});
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
// 로깅
// 네트워크 전송
Log.d(TAG, throwable.toString());
}
});
t.start();
7.2 스레드 관리
각 응용 프로그램은 스레드 사용과 관리 방법을 책임진다. 정의, 시작, 유지, 취소라는 3단계의 스레드 특성을 구현할 수 있다.
7.2.1 정의와 시작
스레드는 여러 구성요소의 생명주기보다 오래 살 수 있고, 재사용되지 않는 경우에도 구성요소의 객체를 메모리에 유지할 수 있다. 때문에 메모리 누수의 위험과 크기 모두 영향을 미친다.
스레드를 정의하는 방법과 각각의 의미를 살펴보자.
public class AnyObject {
@UiThread
public void anyMethod() {
new Thread() {
public void run() {
doLongRunningTask();
}
}.start();
}
}
요 방법은 간단하게 사용 가능하지만 외부 클래스에 대한 참조를 보유한다.
스레드를 실행하는 클래스로 정의하지 않고 독립형 클래스로 정의할 수 있다. class MyThread extends Thread {
public void run() {
doLongRunningTask();
}
}
public class AnyObject {
private MyThread mMyThread;
@UiThread
public void anyMethod() {
mMyThread = new MyThread();
mMyThread.start();
}
}
독립형 클래스는 외부 클래스에 대한 참조를 보유하진 않지만, 정의할 때 필요한 클래스의 개수가 많아질 수 있다.
인스턴스 대신 클래스 객체 안에서 정의할 수 있다.public class AnyObject {
static class MyThread extends Thread() {
public void run() {
doLongRunningTask();
}
};
private MyThread mMyThread;
@UiThread
public void anyMethod() {
mMyThread = new MyThread();
mMyThread.start();
}
}
정적 내부 클래스는 인스턴스 클래스가 아니라 외부 클래스 객체에만 참조를 유지한다. 메모리 누수에 안전한 방법이다.
스레드 정의를 선택하는 방법 내부 클래스는 외부 참조를 포함하므로 많은 양의 메모리가 누수될 수 있다. 공개 클래스 및 정적 내부 클래스는 이를 해결해 줄 수 있다. 익명 내부 클래스는 스레드를 참조할 객체가 없으므로 응용프로그램으로 인해 영향 받을 수 없다.
위에서 설명한 생성 방법은 스레드 생성을 통제할 수 없는 문제가 있다. 앞으로 스레드 풀이나 HandlerThread로 실행 스레드의 개수를 제한할 수 있는 방법을 배울 것이다.
7.2.2 유지
스레드는 안드로이드 구성요소의 생명주기를 따르지 않는다. 일단 run 메서드가 끝나거나 프로세스가 종료될 때까지 실행된다.
만약 설정 변경 시 액티비티가 재 생성되엇다면 이전 스레드의 결과를 재 사용할 수 없다. 이는 불필요한 작업을 계속 할 수도 있다. 스레드를 유지하는 방법을 다음에서 배워보자.
- 엑티비티에서 스레드 유지
Activity 클래스는 스레드 유지를 처리하기 위해 두 가지 메서드를 포함한다.
-public Object onRetainNonConfigurationInstance() 설정 변경은 현재 Activity객체가 파괴되고 다른 인스턴스로 대체되도록 만든다. 이 메서드는 설정변경이 일어나기 전에 플렛폼에서 호출되며 구현사항은 설정 변경돈안 유지되고 새로운 Activity객체에 전달하고자 하는 객체(스레드)를 반환하면 된다.
-public Object getLastNonConfigurationInstance() 이 메서드는 설정 변경이 이루어진 후 onRetainNonConfigurationInstance() 에서 반환된 유지된 객체를 가져오기 위해 새로운 Activity객체에서 호출될 수 있다.
public class ThreadRetainActivity extends Activity {
public static class MyThread extends Thread {
private ThreadRetainActivity mActivity;
public MyThread(ThreadRetainActivity activity) {
mActivity = activity;
}
@Override
public void run() {
final String text = getTextFromNetwork();
mActivity.setText(text);
}
private String getTextFromNetwork() {
//네트워크 시뮬레이션
SystemClock.sleep(5000);
return "text from network";
}
}
private static MyThread t;
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_retain_thread);
textView = (TextView) findViewById(R.id.text_retain);
Object retainedObject = getLastNonConfigurationINstance();
if(retainedObject != null) {
t = (MyThread) retainedObejct;
t.attach(this);
}
}
@Override
public Object onRetainNonConfigurationInstance() {
if ( t != null && t.isAlive()) {
return t;
}
return null;
}
public void onClickStartThread(View v) {
t = new MyThread(this);
t.start();
}
private void setText(final String text) {
runOnUiThread(new Runnable() {
@Override
public void run() {
textView.setText(text);
}
});
}
}
- 프레그먼트에서 스레드 유지
프로그먼트에서 스레드 유지는 인스턴스 유지가 더 쉽다. 방법은 Fragment.onCreate() 에서 setRetainInstance(true)를 호출한다.public class ThreadRetainWithFragmentActivity extends Activity {
private ThreadFragment mThreadFragment;
private TextView mTextView;
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_retain_thread);
mTextView = (TextView) findViewById(R.id.text_retain);
FragmentManager manager = getFragmentManager();
mThreadFragment = (ThreadFragment) manager.findFragmentByTag("threadfragment");
if (mThreadFragment == null) {
FragmentTransaction transaction = manager.beginTransaction();
mThreadFragment = new ThreadFragment();
transaction.add(mThreadFragment, "threadfragment");
transaction.commit();
}
}
public void onStartThread(View v) {
mThreadFragment.execute();
}
public void setText(final String text) {
runOnUiThread(new Runnalbe() {
@Override
public void run() {
mTextView.setText(text);
}
});
}
}
//ThreadFragment
public class ThreadFragment extends Fragment {
private ThreadRetainWithFragmentActivity mActivity;
private MyThread t;
private class MyThread extends Thread {
@Override
public void run() {
final String text = getTextFromNetwork();
mActivity.setText(text);
}
private String getTextFromNetwork() {
//네트워크 시뮬레이션
SystemClock.sleep(5000);
return "Text from network";
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true); // 자동으로 스레드 유지
}
@Override
public void onAttatch(Activity activity) {
super.onAttatch(activity);
mActivity = (ThreadRetainWithFragmentActivity) activity;
}
@Override
public void onDetach() {
super.onDetach();
mActivity = null;
}
public void execute() {
t = new MyThread();
t.start();
}
}
7.3 마치며
이번시간에는 스레드의 정의 인터럽트 방법, 구성요소 생명 주기에서 스레드 유지 방법에 대해서 알아보았다. 가장 중요한 것은 응용 프로그램 메모리 누수 위험과 크기를 줄이고 스레드의 시작과 종료를 제어하기 위해서 스레드 관리가 필요한 것이다. |