기본 스레드의 관리
- 안드로이드에서도 자바와 마찬가지로
java.lang.Thread
클래스가 모든 스레드의 근간이 된다.
1.기본개념
생명주기
Thread.State
enum에 정의되어 있으며, Thread 인스턴스의 getStatus()
메서드를 이용해 확인할 수 있다.
NEW
- Thread 클래스의 인스턴스를 생성한 직후의 상태
- Thread 인스턴스 생성이 다른 클래스의 인스턴스 생성보다 무겁지는 않다.
- 생성된 스레드는 현재 스레드와 동일한 우선순위의 스레드그룹으로 할당된다.
- ex) UI스레드에서 생성하면 UI스레드와 동일한 우선순위의 스레드그룹이 된다.
RUNNABLE
Thread.start()
메서드가 호출되어 실행환경이 설정된 직후부터 스레드가 실행되는 중의 상태
- 이후에 OS의 스케쥴러가 스레드를 선택하면
run()
메서드가 호출되어 태스크가 실행된다.
BLOCK / WAITING / TIMED_WAITING
- I/O 동작, 다른스레드와의 자원동기화, 블로킹 API호출 등 스레드의 실행이 멈출 때의 상태들
- 스레드를 명시적으로 실행을 멈추는 방법
- Thread.sleep() : 스레드를 일정시간 동안 자도록 만든후 다시 실행하도록 스케쥴되도록 함
- Thread.yield() : 현재 실행을 포기하고 스케줄러가 어떤 스레드를 실행할지 결정하도록 함
TERMINATED
run()
메서드가 실행을 완료하면 스레드가 종료되고 스레드의 자원이 해제된 후의 상태
- 스레드가 종료되고 나면 해당 스레드의 인스턴스와 실행환경은 재사용할 수 없다.
- 실행환경을 설정/해제하는 것은 매우 무거운 동작
- 스레드가 동작을 명시적으로 종료하도록 요청할 수도 있다. -
interrupt()
메서드 호출
인터럽트
- 스레드를 명시적으로 종료하고 싶을 때, 스레드에
interrupt()
메서드를 이용해 인터럽트를 요청
- 어디까지나 요청일 뿐, 인터럽트 호출에 대한 실행여부는 스레드 자체가 결정한다.
- 일반적으로 스레드의 인터럽트는 공동으로(Collaboratively) 구현되어야 한다.
class TestThread extends Thread {
@Override
public void run() {
while(!isInterrunpted()) {
}
}
}
- 스레드 내에서 블로킹 API를 사용중이라 스레드가 차단되어있는 중에 인터럽트 신호를 받으면 블로킹 API는
InterruptException
을 던진다.
- InterruptException이 던져지면 인터럽트 플래그가 리셋되므로 주의한다.
class TestThread extends Thread {
@Override
public void run() {
while(!isInterrunpted()) {
doBlockingAction();
}
}
public void doBlockingAction() {
try {
} catch (InterruptException e) {
Thread.currentThread().interrupt();
}
}
}
잡히지 않는 예외
- 모든 스레드는 RuntimeException이 발생했을 때, 이 예외를 처리해주지 않으면 스레드가 비정상적으로 종료된다.
- 스레드 내에서 발생하는
RuntimeException
을 잡으려면, 스레드가 비정상 종료되기 전에 호출되는 UncaughtExceptionHandler
를 부착한다.
- 모든 스레드에 전역 핸들러를 부착할 경우 : Thread 클래스의
setDefaultUncaughtExceptionHandler()
정적 메서드 호출
- 특정 스레드에 지역 핸들러를 부착할 경우 : Thread 인스턴스의
setUncaughtExceptionHandler()
메서드 호출
- 위의 두가지 경우가 모두 부착되어 있을 경우에는 특정 스레드마다 붙은 핸들러가 우선시되어 모든스레드에 부착된 핸들러는 호출되지 않는다.
- 안드로이드 런타임은 앱이 시작될 때 전역 핸들러를 부착한다. 이 전역 핸들러의 기본동작은 잡히지 않은 예외가 발생했을 때, 프로세스를 죽이는 것으로 모든 스레드가 동등하게 처리된다.
- Ex - 앱이 크래시되기 직전에 로그남기기
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate(0;
Thread.setDefaultUncaughtExceptionHandler(new LogExceptionHandler());
}
}
public class LogExceptionHandler implements Thread.UncaughtExceptionHandler {
private final Thread.UncaughtExceptionHandler defaultHandler;
public LogExceptionHandler() {
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
default.Handler.uncaughtException(thread, throwable);
}
}
2.스레드 관리
스레드 정의(Define)와 시작
- 스레드 정의 방법에 따라, 그 스레드의 생명주기가 다르다. 그래서 때때로 안드로이드의 컴포넌트(Activity, Service, BroadcastReceiver, ContentProvider)와 생명주기가 달라서 메모리 누수를 일으키기도 한다.
익명 내부클래스로 정의
- 익명 내부클래스는 구현이 간단하지만, 외부클래스의 인스턴스에 대한 참조를 내부적으로 유지하고 있다.
public class AnyObject {
@UiThread
public void anyMethod() {
new Thread() {
@Override
public void run() {
doLongRunningTask();
}
}.start();
}
}
공개된 독립 클래스로 정의
- 스레드를 실행하는 인스턴스에 대한 잠재적인 참조를 유지하지는 않지만, 클래스의 개수가 많아진다.
class TestThread extends Thread {
@Override
public void run() {
doLongRunningTask();
}
}
public class AnyObject {
private TestThread testThread;
@UiThread
public void anyMethod() {
testThread = new TestThread();
testThread.start();
}
}
정적 내부 클래스로 정의
- 외부 클래스의 클래스 객체에 대한 내부참조를 유지하고 있다. 그렇지만 클래스객체의 참조는 메모리 누수의 원인이 되지 않는다.
public class AnyObject {
static class TestThread extends Thread {
@Override
public void run() {
doLongRunningTask();
}
}
private TestThread testThread;
@UiThread
public void anyMethod() {
testThread = new TestThread();
testThread.start();
}
}
스레드 유지
- Activity의 설정이 변경되면 기본적으로 Activity는 재시작되며 가지고있던 멤버변수들도 초기화된다. - 당연히 스레드도 초기화되어 새로운 객체로 생성되어 다시 시작된다.
- 해당 증상을 방지하기 위해 Activity와 Fragment는 설정의 변경에도 객체를 유지할 수 있는 방법을 제공한다.
Activity에서 스레드 유지
- pulbic Object onRetainNonConfigurationInstance()
- 설정 변경이 일어나기 전에 플랫폼에 의해 호출되는 콜백메서드
- 새로 생성되는 Activity 객체에 전달할 객체를 반환하도록 구현해야 한다.
- public Object getLastNonConfigurationInstance()
- 설정 변경이 이루어진 후, 새로 생성된 Activity 객체에서 호출하는 메서드
onRetainNonConfigurationInstance()
메서드에서 반환한 객체를 반환한다.
- 설정 변경이 아닌 다른이유로 Activity가 재시작 될 경우 null을 반환한다.
- 예제
public class TestActivity extends Activity {
private static class TestThread extends Thread {
@Override
public void run() {
}
}
private static TestThread thread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout_activity_test);
Object retainedObject = getLastNonConfigurationInstance();
if (retainedObject != null) {
thread = retainedObject;
} else {
thread = new TestThread();
thread.start();
}
}
@Override
public Object onRetainNonConfigurationInstance() {
if (thread != null && thread.isAlive()) {
return thread;
}
return null;
}
}
Fragment에서 스레드 유지
- Fragment의 상태 유지요청 메서드인
setRetainInstance(true)
를 이용하여 프레그먼트를 유지한다.
- 예제
public class TestFragment extends Fragment {
private static class TestThread extends Thread {
@Override
public void run() {
}
}
private TestThread thread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (thread == null) {
thread = new Thread();
thread.start();
}
setRetainInstance(true);
}
}
public class TestActivity extends Activity {
TestFragment fragment;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout_activity_test);
FragmentManager manager = getFragmentManager();
fragment = manager.findFragmentByTag("TestFragment");
if (fragment == null) {
FragmentTransaction transaction = manager.beginTransaction();
fragment = new Fragment();
transaction.add(fragment, "TestFragment");
transaction.commit();
}
}
}
댓글 없음:
댓글 쓰기