레이블이 성능인 게시물을 표시합니다. 모든 게시물 표시
레이블이 성능인 게시물을 표시합니다. 모든 게시물 표시

2018년 4월 17일 화요일

[안드로이드] Architecture Component 3 - Paging Library 공식문서 번역

Paging Library

페이징 라이브러리는 앱이 데이터 소스로부터 필요한 정보를 점진적으로 읽어오는 작업을 쉽게 만들어주는 라이브러리로, 디바이스에 과부하가 걸리거나, 큰 데이터베이스로부터의 쿼리를 기다리지 않도록 만들어준다.

개요

대부분의 앱들은 많은 양의 데이터를 이용하여 동작하지만, 사실은 화면에 보여야 할 필요가 있는 일부분의 데이터만 불러와서 보여지면 된다. 예를들어, 어떤 앱에서 잠재적으로 보여줄 수 있는 데이터를 몇천개 가지고 있더라도, 한번에 몇십개씩만 엑세스하면 된다. 이부분을 앱이 신경쓰고 있지 않다면, 앱에서 불필요한 데이터까지 모두 요청하게 되고, 이는 불필요한 네트워크 트래픽의 발생과, 디바이스의 성능저하를 초래한다. 데이터가 원격저장소에 저장되거나 동기화되면, 이 또한 앱이 느려지는 원인이 되고, 사용자의 데이터요금을 낭비하게 된다.
기존의 Android API는 콘텐츠의 페이징을 허용했지만 중요한 제약 조건과 단점이 있었다.
  • CursorAdapter는 데이터베이스의 쿼리결과를 ListView의 항목들에 쉽게 매핑시켜주었지만, 데이터베이스 쿼리를 UI 스레드에서 하는것과 Cursor를 이용하기 때문에 데이터의 페이징을 비효율적으로 하는 단점이었다. 더 많은 정보를 확인하려면 Large Database Queries on Android 블로그 글을 확인한다.
  • AsyncListUtil은 RecyclerView에서 포지션 기반의 페이징을 허용하지만, 포지션 기반이 아닌 페이징은 허용하지 않는다. 또한, 셀수 있는 데이터셋에서 null을 플레이스 홀더로 강제로 사용한다.
새로운 페이징 라이브러리는 이러한 문제들을 해결한다. 이 라이브러리는 필요한 데이터를 요청하는 프로세스를 간소화하는 몇몇 클래스들을 포함한다. 이 클래스들은 Room과 같은 기존의 아키텍쳐 컴포넌트 라이브러리들과도 원활하게 잘 동작하도록 구성되어 있다.

기능

페이징 라이브러리의 클래스들을 사용하면, 이 섹션에서 소개되는 작업들을 수행하도록 도와준다.

데이터를 어떻게 가져오는지 정의하기

DataSource클래스를 이용하여 페이징된 데이터를 얻어올 데이터 소스를 정의한다. 데이터 접근 방법에 따라, 이 클래스를 상속받아 서브클래스로 구성할 수도 있다.
  • 로드할 데이터가 다음 / 이전 키를 포함하고 있다면, PageKeyedDataSource 클래스를 사용한다. 예를들어, 네트워크로부터 소셜미디어의 게시물을 가져올 경우, 연속적인 다음페이지를 로드하기 위해서, 다음페이지의 토큰을 전달해야 할 수도 있다.
  • 항목 N의 데이터를 이용해 N+1의 데이터를 가져와야 한다면, ItemKeyedDataSource클래스를 사용한다. 예를들어, 스레드된 코멘트들을 가져와야 한다면, 다음 코멘트를 가져오기 위해, 하나의 코멘트의 아이디를 전달해야 할 수도 있다.
  • 데이터 저장소에서 선택한 위치로부터 데이터의 페이지를 가져와야한다면, PositionalDataSource클래스를 사용한다. 이 클래스는 "1200번 위치로부터 20개의 데이터 반환"과 같이, 선택된 위치로부터 시작하는 데이터의 집합을 요청할 수 있다.
만약 Room 라이브러리를 사용하여 데이터를 관리하고 있다면, 이 Room 라이브러리는 자동적으로 PositionalDataSource인스턴스를 생성하기 위해 DataSource.Factory를 발생시킬 수 있다.
@Query("select * from users WHERE age > :age order by name DESC, id ASC")
DataSource.Factory<Integer, User> usersOlderThan(int age);

메모리로부터 데이터 로드

PagedList클래스는 DataSource로부터 데이터를 불러옵니다. 개발자는 데이터가 한번에 얼마나 로드가 되어야 하고, 미리 패치되어야 하는지 설정하여, 사용자가 데이터로드가 완료될 때까지 기다려야 하는 시간을 최소화할 수 있다. 이 클래스는 RecyclerView.Adapter와 같은 다른 클래스에게 업데이트 신호를 제공하여, 페이지가 로드되었을 때 RecyclerView의 컨텐츠를 업데이트하도록 도와준다.
앱의 구조에 따라, PagedList 클래스 사용을 위한 몇몇 옵션들이 있다. 더 많은 내용을 보려면, 다음의 데이터 로딩 구조를 참고한다.

UI에 데이터 나타내기

PagedListAdapter 클래스는 PagedList로부터 데이터를 UI에 나타내기 위한 RecyclerView.Adapter의 구현체이다. 예를들어, 새로운 페이지가 로드되었을 때, PagedListAdapter는 데이터가 갱신되었음을 RecyclerView에게 알려준다. 이는 RecyclerView가 실제 항목으로 플레이스홀더를 대체하도록 하여 적절한 애니메이션을 수행할 수 있다.
또한 PagedListAdapter는 백그라운드 스레드를 사용하여 한 PagedList에서 다른 PagedList 로의 변경 사항을 계산하고 (예 : 데이터베이스 변경이 업데이트 된 데이터로 새 PagedList를 생성하는 경우) 필요에 따라 목록 내용을 업데이트하기 위해 notifyItem ... () 메소드를 호출한다. 그리고나서 RecyclerView는 필요한 변화를 수행한다. 예를들어, PagedList 버전사이에 항목들의 위치가 변경되면, RecyclerView는 해당 항목을 새로운 위치로 애니메이션으로 움직인다.

데이터 갱신 관찰하기

페이징 라이브러리는 실시간 업데이트가 가능한 PagedList 컨테이너를 생성하기 위한 클래스들을 제공하고 있다.
  • LivePageListBuilder : 이 클래스는 개발자가 제공한 DataSource.Factory를 통해 LiveData<PagedList>를 생성한다. 다음의 샘플코드에서 보는것처럼, 만약 데이터베이스를 관리하기 위해 Room 라이브러리를 사용한다면, DAO는 PositionalDataSource를 사용하여 DataSource.Factory를 생성할 수 있다.
LiveData<PagedList<Item>> pagedItems =
        LivePagedListBuilder(myDataSource, /* page size */ 50)
                .setFetchExecutor(myNetworkExecutor)
                .build();
  • RxpagedListBuilder : 이 클래스는 LivepagedListBuilder와 유사하게, RxJava2 기반의 기능을 제공한다. 이 클래스는 아키텍쳐 라이브러리의 RxJava2(android.arch.paging:rxjava2:1.0.0-alpha1)기반으로 제공된다. 다음의 코드에서 볼 수 있듯이, 이 클래스를 사용하여, PagedList를 구현할 때 Flowable과 Observable을 생성할 수 있다.
Flowable<PagedList<Item>> pagedItems =
        RxPagedListBuilder(myDataSource, /* page size */ 50)
                .setFetchScheduler(myNetworkScheduler)
                .buildFlowable(BackpressureStrategy.LATEST);

데이터 흐름 생성하기

페이징 라이브러리의 컴포넌트는 백그라운드 스레드의 생산자로부터 UI스레드의 화면 출력까지의 데이터 흐름을 구성한다. 예를들어, 새로운 항목이 데이터베이스에 추가되면, DataSource는 갱신될 것이고, LiveData<PagedList> / Flowable<PagedList> 는 백그라운드 스레드에서 새로운 PagedList를 생산한다.
paging-threading.gif
Figure1 : 페이징 라이브러 컴포넌트들은 대부분의 작업을 백그라운드 스레드에서 수행한다. 그래서 UI스레드에 큰 부담이 되지 않는다.
새로 생성된 PagedList는 UI스레드에서 PagedListAdapter로 보내진다. PagedListAdapter는 백그라운드 스레드에서 DiffUtil을 이용하여, 기존의 리스트와 새로운 리스트 사이의 차이를 계산한다. 비교가 끝났으면, PagedListAdapter는 리스트의 차이점을 이용하여 RecyclerView.Adapter.notifyItemInserted() 를 호출하여 UI에 항목들을 삽입한다.
UI스레드의 RecyclerView는 삽입된 아이템을 확인하여 적절하게 애니메이셔닝 한다.

데이터베이스 예제

다음의 코드조각들은 모든 라이브러리들이 함께 동작하도록 하는 몇몇 방법을 보여준다.

LiveData를 이용하여 페이징된 데이터 관찰하기

다음의 코드는 모든 컴포넌트들이 함께 동작하는 것을 보여준다. 사용자가 데이터베이스에 추가, 삭제, 변경할 때, RecyclerView의 내용은 자동적으로, 효율적으로 갱신된다.
@Dao
interface UserDao {
    // The Integer type parameter tells Room to use a PositionalDataSource
    // object, with position-based loading under the hood.
    @Query("SELECT * FROM user ORDER BY lastName ASC")
    public abstract DataSource.Factory<Integer, User> usersByLastName();
}

class MyViewModel extends ViewModel {
    public final LiveData<PagedList<User>> usersList;
    public MyViewModel(UserDao userDao) {
        usersList = new LivePagedListBuilder<>(
                userDao.usersByLastName(), /* page size */ 20).build();
    }
}

class MyActivity extends AppCompatActivity {
    private UserAdapter<User> mAdapter;

    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.user_list);
        mAdapter = new UserAdapter();
        viewModel.usersList.observe(this, pagedList ->
                mAdapter.submitList(pagedList));
        recyclerView.setAdapter(mAdapter);
    }
}

class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
    public UserAdapter() {
        super(DIFF_CALLBACK);
    }
    @Override
    public void onBindViewHolder(UserViewHolder holder, int position) {
        User user = getItem(position);
        if (user != null) {
            holder.bindTo(user);
        } else {
            // Null defines a placeholder item - PagedListAdapter will automatically invalidate
            // this row when the actual object is loaded from the database
            holder.clear();
        }
    }
    public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK =
            new DiffUtil.ItemCallback<User>() {
        @Override
        public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // User properties may have changed if reloaded from the DB, but ID is fixed
            return oldUser.getId() == newUser.getId();
        }
        @Override
        public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {
            // NOTE: if you use equals, your object must properly override Object#equals()
            // Incorrectly returning false here will result in too many animations.
            return oldUser.equals(newUser);
        }
    }
}

RxJava2를 이용하여 페이징된 데이터 관찰하기

LiveData대신 RxJava2를 사용하려고 한다면, Observable이나 Flowable 객체를 생성한다.
class MyViewModel extends ViewModel {

    public final Flowable<PagedList<User>> usersList;

    public MyViewModel(UserDao userDao) {
        usersList = new RxPagedListBuilder<>(userDao.usersByLastName(),
                /* page size */ 50).buildFlowable(BackpressureStrategy.LATEST);
    }
}
그리고, 다음의 코드와 같이 해당 데이터의 관찰을 시작 / 중지할 수 있다.
class MyActivity extends AppCompatActivity {
    private UserAdapter<User> mAdapter;
    private final CompositeDisposable mDisposable = new CompositeDisposable();

    @Override
    public void onCreate(Bundle savedState) {
        super.onCreate(savedState);
        MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
        RecyclerView recyclerView = findViewById(R.id.user_list);
        mAdapter = new UserAdapter();
        recyclerView.setAdapter(mAdapter);
    }

    @Override
    protected void onStart() {
        super.onStart();
        myDisposable.add(mViewModel.usersList.subscribe(flowableList ->
                mAdapter.submitList(flowableList)));
    }

    @Override
    protected void onStop() {
        super.onStop();
        mDisposable.clear();
    }
}
UserDaoUserAdapter 클래스의 코드는 LiveData의 예제와 같으므로 생략한다.

데이터 로딩 구조 선택하기

페이징 라이브러리를 이용해 데이터를 페이징하는 두가지의 기본적인 방법이 있다.

네트워크 or 데이터베이스

첫번째로, 하나의 소스(로컬저장소이든 네트워크이든)로부터 페이징을 할 수 있다. 이 경우 다음 샘플과 같이 LiveData를 이용하여 로드된 데이터를 UI에 전달한다.
데이터의 소스를 지정하려면 LivePagedListBuilder에 DataSource.Factory를 전달한다.
paging-network-or-database.png
Figure2 : 하나의 데이터소스는 DataSource.Factory를 제공하여 컨텐츠를 로드한다.
데이터베이스를 관찰할 때, 컨텐츠의 변화가 발생하면, 데이터베이스는 새로운 PagedList에 푸쉬한다. 네트워크 페이징인 경우에(백엔드가 업데이트를 보내지 않을 때), 스와이프 리프레시 같은 시그널은 현재 데이터소스를 무효화함으로써 새로운 PagedList를 받아올 수 있다. 이는 모든 데이터를 비동기적으로 새로고침한다.
PagingWithNetworkSample의 메모리 + 네트워크 저장소 구현은 스와이프 리프레시, 네트워크 에러, 재시도를 처리하는 동안, Retrofit을 이용하여 어떻게 네트워크 DataSource.Factory를 구현했는지를 보여준다.

네트워크 and 데이터베이스

두번째로, 로컬저장소로부터 데이터를 페이지할 수 있고, 로컬저장소는 네트워크로부터 추가 데이터를 페이징한다. 이는 종종 네트워크 연결을 최소화하고, 백엔드의 캐시로 데이터베이스를 사용하여 더 나은 연결지양 경험을 제공한다.
이 경우, LiveData를 이용하여 데이터베이스로부터 컨텐츠를 페이징하고, 최신 데이터의 시그널을 관찰하기 위해, LivePagedListBuilder에 BoundaryCallback을 전달한다.
paging-network-plus-database.png
Figure3 : 데이터베이스는 네트워크 데이터의 캐시로 이용된다. UI는 데이터베이스로부터 데이터를 로드하고, 데이터가 없을 때, 네트워크로 신호를 보내서 네트워크로부터 데이터베이스로 데이터를 로드한다.
이후, 이 콜백을 네트워크 요청에 연결하면 데이터가 데이터베이스에 바로 저장된다. UI는 데이터베이스의 갱신을 구독하고 있으므로, 새로운 데이터의 변경은 자동적으로 UI로 전달된다.
PagingWithNetworkSample의 데이터베이스 + 네트워크 저장소 구현은 스와이프 리프레시, 네트워크 에러, 재시도를 처리하는 동안, Retrofit을 이용하여 어떻게 네트워크의 BoundaryCallback을 구현하는지 보여준다.

원문 : https://developer.android.com/topic/libraries/architecture/paging.html


2018년 1월 15일 월요일

[안드로이드] 스레드3 - 기본 스레드의 생명주기 관리

기본 스레드의 관리

  • 안드로이드에서도 자바와 마찬가지로 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() {
        //isInterrupt() 메서드를 수시로 검사하여 true를 반환하면 태스크를 완료한 것으로 처리
        while(!isInterrunpted()) {
            //스레드가 살아있음
        }
        //태스크 완료되어 스레드 종료됨
    }
}
  • 스레드 내에서 블로킹 API를 사용중이라 스레드가 차단되어있는 중에 인터럽트 신호를 받으면 블로킹 API는 InterruptException 을 던진다.
    • InterruptException이 던져지면 인터럽트 플래그가 리셋되므로 주의한다.
class TestThread extends Thread {
    @Override
    public void run() {
        //isInterrupt() 메서드를 수시로 검사하여 true를 반환하면 태스크를 완료한 것으로 처리
        while(!isInterrunpted()) {
            doBlockingAction();
        }
    }
    
    public void doBlockingAction() {
        try {
            //Blocking API 호출
        } catch (InterruptException e) {
            //1.자원 정리
            //2.isInterrupt() 플래그가 초기화되었으므로, run() 메서드가 isInterrupt()를 제대로 인식하도록 자신을 다시 인터럽트
            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 {
    //UI 스레드에서 호출되는 메서드
    @UiThread
    public void anyMethod() {
        new Thread() {
            @Override
            public void run() {
                // 긴 태스크를 실행한다고 가정
                // 해당 스레드가 살아있는 동안 이 스레드는 AnyObject 인스턴스의 참조를 유지하고 있다.
                doLongRunningTask();
            }
        }.start();
    }
}

공개된 독립 클래스로 정의

  • 스레드를 실행하는 인스턴스에 대한 잠재적인 참조를 유지하지는 않지만, 클래스의 개수가 많아진다.
class TestThread extends Thread {
    @Override
    public void run() {
        // 긴 태스크를 실행한다고 가정
        doLongRunningTask();
    }
}

public class AnyObject {
    //UI 스레드에서 호출되는 메서드
    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;

    //UI 스레드에서 호출되는 메서드
    @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() {
            //TODO::긴 작업
        }
    }

    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() {
            //TODO::긴 작업
        }
    }

    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();
           
        }
    }
}