이 글은 www.androiddesignpatterns.com에 소개된 글을 저자의 동의를 얻고 번역한 것임을 알려드립니다.

원문 : http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html

 

이 글은 단말기의 Configuration(언어, Orientation 등등)이 변경될 때, active object(running Threads, Sockets, AsyncTasks, etc..)들을 유지하는 최선의 방법은 뭘까? 라는 질문에 대한 답변이다.

 

물음에 답하기 위해, Activity의 life cycle과 관련된 long-running background tasks를 작성할 때 개발자들이 겪고 있는 문제점들을 설명한 뒤, 이 문제점들을 해결할 수 있는 2개의 방법을 제안하고, 마지막으로 권장하는 방법(retained Fragment)을 sample code를 통해 제시한다.

 

Configuration Changes & Background Tasks

Configuration이 변경될 때마다 Activity는 destroy-and-create 과정을 수행하는데, 문제는 Configuration 변경은 예측이 불가능하며 아무때나 발생할 수 있다는 점이다.

예를 들어, Activity가 AsyncTask를 실행시킨 뒤 사용자가 단말기를 회전시키면 Activity는 destroy된 뒤 다시 create된다. Activity가 재생성되었다는 사실을 모르는 AsyncTask는 작업을 종료했을 때 자신을 실행시킨 Activity(destroyed)에게 결과를 전달한다. 반면, 재생성된 Activity는 이전에 실행한 AsyncTask가 동일한 작업을 수행중이라는 사실을 모르기 때문에 새로운 AsyncTask를 실행시켜서 동일한 작업을 수행한다.

위와 같은 이유로 Configuration이 변경될 때 active object들을 유지하는 것은 매우 중요하다.

 

Bad Practice: Retain the Activity

아마 가장 많이 사용되는 해결책은 Android manifest의 android:configChanges 속성을 수정하여 Configuration이 변경되어도 destroy-and-create되지 않도록 하는 것일 것이다. 이 방법은 매우 간편해서 개발자들에게 매력적인데, 구글의 개발자들은 이 방법을 권장하지 않는다. 가장 큰 이유는 Configuration이 변경될 때마다 Code에서 resource들을 직접 관리해야되기 때문인데, 이는 string, drawable, dimension, etc…..등을 현재 단말기의 Configuration에 맞게 적용해야 된다는 의미다.

또 하나의 이유는 많은 개발자들이 android:configChanges=”orientation” 설정만으로 Activity가 destroy-and-recreate되는 것을 피할 수 있다고 생각하기 때문이다. 실제로 단말기의 언어 설정이 변경되거나, Display dock에 꽂히거나, Font scale factor가 변경되는 등의 이유로도 Activity는 destroy-and-recreate된다.

위와 같은 이유로, android:configChanges 속성을 수정하여 Activity의 기본 동작을 제어하는 것은 좋은 방법이 아니다.

 

Deprecated: Override onRetainNonConfigurationInstance()

Honeycomb이 나오기 전까지 Activity instance간에 Active object를 주고받는 방법은 onRetainNonConfigurationInstance() 메소드와 getLastNonConfigurationInstance() 메소드를 override하는 방법이었다.

Activity instance는 onRetainNonConfigurationInstance() 메소드에서 Activity object를 전달한 뒤 getLastNonConfigurationInstance()에서 전달받으면 됐다. 하지만 API 13부터 Fragment의 setRetainInstance(boolean) 메소드로 인해 deprecated 됐다.

 

Recommended: Manage the Object Inside a Retained Fragment.

Android 3.0에서 Fragment가 소개된 이후로, retained Fragment를 통해 Activity instance간에 active object들을 주고받는 것을 권장하고 있다. 기본적으로 Fragment는 parent Activity가 destroy-and-create 될 때 같이 destroy-and-create 되지만 setRetainInstance(true)를 호출해줌으로써 destroy-and-create cycle을 피할 수 있다.

Fragment가 running Thread, AsyncTask, Socket 등을 갖고 있을 때 이는 매우 유용하다.

아래의 코드는 retained Fragment를 이용해서 어떻게 AsyncTask를 유지하는지 기본적인 예제다. 예제 코드는 progress 갱신과 결과가 현재 화면에 나타나고 있는(마지막으로 생성된) Activity에게 전달되는 것과, Configuration의 변경으로 인해 AsyncTask가 leak되는 일이 발생하지 않을 것을 보장한다.

 

Flow of Events

MainActivity가 처음 시작될 때, TaskFragment를 생성한 뒤 추가한다. TaskFragment는 AsyncTask를 실행하고 progress의 갱신과 최종 결과를 TaskCallbacks interface를 통해 MainActivity로 전달한다. Configuration 변경이 발생할 때, MainActivity는 일반적인 lifecycle event(destroy-and-create)를 진행하고, 새로운 Activity instance가 생성되면 Fragment의 onAttach(Activity)로 전달된다. 그러므로 TaskFragment는 항상 마지막으로 생성된(currently displayed) Activity instance를 갖고 있다.

Application Framework에서 Activity가 재생성될 때 다시 할당을 해주기 때문에 TaskFragment와 AsyncTask에서는 Configuration 변경을 걱정할 필요가 없다.

 

Conclusion

Activity life cycle과 background task를 동기화하는 작업은 까다롭고 Configuration 변경은 이를 더 복잡하게 한다. 하지만 다행히도 retained Fragment는 마지막으로 생성된 Activity의 reference를 보관하기 때문에 이를 매우 쉽게 만들어준다.

+@ 일반적인 Fragment는 Configuration 변경등의 이벤트로 인해 Activity 재실행시 onAttach() -> onCreate() -> onCreateView()의 흐름으로 생성과정이 진행되지만 retained Fragment의 경우 Activity의 재생성으로부터 Fragment를 유지해주기 때문에 onAttach() -> onCreateView()의 흐름으로 진행된다. onCreateView()가 다시 호출되는 이유는 View에 대해서는 retained라는 말이 유효하지 않기 때문이다. View는 다시 설정해줘야 한다.

Sample Code from GitHub.