[컴][안드로이드] 화면전환시 설정 값을 유지하는 방법

configuration change 값 유지 / how to keep data when the configuration is changed.


configuration 의 종류

  • display dock 에 연결
  • 폰트 크기 변경
  • 기본 언어 변경
  • 화면전환
  • 기타 등등...
이 경우 모든 현재 동작하는 Activity 는 그 Activity 를 다시 사용 하려고 할 때 destroy 되고 recreate 된다.



android:configChagnes

AndroidManifest.xml 에 사용하는
android:configChanges
방법은 권장하지 않는다. 이 방법은 개발자가 직접 configuration 변경에 대한 관리를 해야 한다. 그리고 이 방법은 개발자가 생각하는 만큼 완벽하게 orientation change(화면 방향 전환) 에 대한 처리를 해주지 않는다.[ref. 1]



onRetainNonConfigurationInstance, getLastNonConfigurationInstance


HoneyComb 이전에 사용됐던

  • onRetainNonConfigurationInstance()
  • getLastNonConfigurationInstance()
는 deprecated 됐으며,

Fragment 의 

  • setRetainInstance(boolean)
로 대체되었다.




setRetainInstance(true)

기본적으로 Activity 가 destroy 되고 recreate 될 때 Fragment 도 같이 destroy 되고 recreate 된다. 하지만 이때
setRetainInstance(true)
를 사용하면 fragment 가 destroy, recreate 되지 않게 할 수 있다.

Fragment 의 onCreate(), onCreateView() 는 다시 호출되지 않는다. 하지만 onCreateView(), onAttach() 나 onActivityCreated() 같이 Activity 와 관련있는 부분들은 여전히 호출된다.

이것은 running Threads, AsyncTasks, Sockets 등을 갖고 있는 Fragment 가 있다면 매우 유용하다.[ref. 1]


onAttach()

onAttach() 에서 Activity 값을 얻을 수 있다.

setRetainInstance(true) 를 설정하고 onCreate() 에 code 를 놓아둘 때는 한 번만 실행될 녀석을 놓아두게 된다. 이 때 이 code 가 activity reference 를 필요로 하는 경우라고 가정하자.

이런 경우에 orientation change(화면전환) 이 일어나면, reference 를 다시 설정 해 줘야 하는데, onCreate() 는 setRetainInstance(true) 일때는 다시 실행되지 않기 때문에, activity의 reference 를 onCreate 이전에 설정을 해줘야 한다. 그 때 onAttach() 를 사용할 수 있다. 이런 방법을 이용한 예가 ref. 1 이다.


onCreate()

onCreate() 부분에는 단 한 번 만들어지면 좋을 녀석을 넣어놓자.

Configuration 이 바뀌는 경우가 여러가지가 있겠지만, 가장 많은 경우인 orientation 이 바뀌는 경우(화면전환)를 고려해 보면서, 화면이 바뀌어도 값이 변하지 않아서 그대로 사용해도 될 값들과 관련된 사항들을 이 곳에 넣자.

예를 들면 context 같은 것은 이곳에 넣으면 안된다. context 값이 새로 setting 될 것인데, 이때 기존의 context 와는 다른 object 가 된다. (쉽게 얘기하면 새로운 주소값을 가지게 된다.) 그러므로 context 를 그대로 사용하면 잘못된 주소를 access 하게 되는 것이기 때문에 안된다.

참고로, setRetainInstance(true) 도 onCreate() 에 들어간다.


onCreateView()

화면이 바뀌면 당연히 화면에 그려진 GUI 와 관련된 부분들은 다시 그려져야 한다. 간단하게 생각하면 가로, 세로값등 많은 값들이 달라졌기 때문에 기존의 값을 그대로 유지 할 수 없다.

화면이 바뀌게 되면, 이 화면을 다시 그려야 하기 때문에 inflate 를 해줘야 한다. 그러므로 화면이 바뀌어서 다시 돌아오게 되면 onCreateView() 부터 시작하게 된다. 그러니 이부분에는 화면을 inflate 하는 부분, inflate 를 set 하는 부분등 inflation 과 관계있는 code를 넣어줘야 한다.


LoaderManager.initLoader()

setRetainInstance(true) 라고 해도 만약 onStart() 같은 곳에서 LoaderManager.initLoader() 를 호출하게 되면 onLoadFinished() 는 호출된다.

그러므로 orientation rotate 등이 발생할 때를 위해 아래같은 처리가 필요하다.
        LoaderManager loaderManager = getActivity().getSupportLoaderManager();
        final Loader<Cursor> loader = loaderManager.getLoader(LOADER_ID);
        if(loader == null || loader.isReset()){
            loaderManager.initLoader(LOADER_ID, null, this);
        }
좀 더 자세한 이야기는 아래 글에서 확인 해 보자.
Scenario where initLoader() does not call onLoadFinished()



Code example

public class MainFragment extends Fragment
        implements LoaderManager.LoaderCallbacks<Cursor> {


    private static final String TAG = "MainFragment";

    private static final int LOADER_ID = 1;


    private PullToRefreshListView mPullRefreshListView;
    private MMiniNoteListAdapter mNoteAdapter;


    private HorizontalListView mPodList;
    private PodListAdapter mPodListAdapter;

    private LoadFeedAsyncTask mLoadFeed = null;


    /**
     * Hold a reference to the parent Activity so we can report the
     * task's current progress and results. The Android framework
     * will pass us a reference to the newly created Activity after
     * each configuration change.
     */
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        //mCallbacks = (TaskCallbacks) activity;
    }


    /**
     * This method will only be called once when the retained
     * Fragment is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Retain this fragment across configuration changes.
        setRetainInstance(true);


        // Put objects which you do not want to be created again.
        // ie. run once.
        mPodListAdapter = new PodListAdapter();
        mNoteAdapter = new MMiniNoteListAdapter();

        mLoadFeed = new LoadFeedAsyncTask(mPodListAdapter);
        mLoadFeed.execute(getActivity());   // Though getActivity is changed after orientation change,
        // this statement is ok because it is run only once.

        Log.d(TAG, "LoadFeedAsyncTask is invoked with Activity " + getActivity());

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {

        View fragment = inflater.inflate(R.layout.main_fragment, container, false);

        mPodListAdapter.setInflater(inflater);
        mNoteAdapter.setInflater(inflater);

        NoteLoadSingleton nls = NoteLoadSingleton.getInstance();
        mPullRefreshListView = getNoteListView(fragment, nls);
        mPodList = getPodListView(fragment);

        nls.init(getActivity(), mPullRefreshListView);

        return fragment;

    }


    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

    }

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onStart() {
        super.onStart();
        initLoader();
    }


    @Override
    public void onResume() {
        // restore the first items

        super.onResume();

        initBroadcastReceiver();

    }

    @Override
    public void onPause() {

        if (mBroadcastReceiver != null) {
            getActivity().unregisterReceiver(mBroadcastReceiver);
            mBroadcastReceiver = null;
        }

        try {
            saveFirstPageOfNotes();
        } catch (RemoteException e) {
            e.printStackTrace();
        } catch (OperationApplicationException e) {
            e.printStackTrace();
        }


        super.onPause();
    }


...


    private void initLoader() {

        LoaderManager loaderManager = getActivity().getSupportLoaderManager();
        final Loader<Cursor> loader = loaderManager.getLoader(LOADER_ID);
        if (loader == null || loader.isReset()) {
            loaderManager.initLoader(LOADER_ID, null, this);
        }


        // @note : showLastNotes() is run in the onLoadFinished()
    }

...


}


References

  1. MONDAY, APRIL 29, 2013,  Handling Configuration Changes with Fragments from http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
  2. Handling Runtime Changes | Android Developers from http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange

댓글 없음:

댓글 쓰기