: 스마트폰을 회전시키면 장치 구성(device configuration)이라는 게 변경된다. 장치 구성은 장치의 현 상태를 나타내는 집합이다. 예로 화면 방향, 화면 밀도, 화면 크기, 키보드 타입, 언어 등이 있다. 장치 구성이 변경되면 애플리케이션은 변경된 장치 구성에 가장 맞는 리소스로 변경된다. 예로 화면 밀도를 보자. 만약 특정 버튼에 이미지를 적용한다고 하면 화면 밀도마다 drawable-mdpi(중밀도), drawable-hdpi(고밀도) 등에 맞는 이미지를 만든다. 애플리케이션은 자신의 장치 구성에 맞는 화면 밀도의 이미지를 찾아 보여줄 것이다. 이렇게 안드로이드 화면이 가로/세로 방향으로 변경된다면, 즉 장치 구성이 변경된다면 안드로이드는 그 것에 맞는 리소스를 찾고 해당 리소스를 적용하게 된다. 


※ 가로 방향 레이아웃 생성

: 실제 가로 방향 레이아웃을 생성해보자. 안드로이드 프로젝트에서 res 디렉터리에 오른쪽 마우스 버튼을 클릭하고 New -> Android resource directory를 들어간다. Resource Type 드롭다운에서 layout을 선택하고 Available qualifiers 항목에서 Orientation을 선택한 후 >>를 누른다. 그러면 Screen orientation 드롭다운이 나오는데 거기서 Landscape를 선택한다. 

그리고 OK 버튼을 누르면 res/layout-land 디렉터리가 생성된다. -land는 -mdpi,-hdpi 같은 구성 수식자이다. 구성 수식자는 안드로이드가 현재의 장치 구성에 가장 잘 맞는 리소스들을 식별하는 방법이다. 장치가 가로 방향으로 바뀌었을 때 안드로이드는 res/layout-land 디렉터리의 리소스를 사용하며 해당 디렉터리가 없다면 res/layout 디폴트 리소스를 사용한다. 만약 메인 액티비티의 layout을 activity_main.xml이라고 하자. 메인 액티비티가 가로 방향으로 바뀌었을 시 안드로이드는 res/layout-land 디렉터리를 찾고, 있다면 거기에 있는 activity_main.xml을 적용한다. res/layout-land 디렉터리가 없다면 res/layout 디렉터리의 activity_main.xml을 적용한다. 이 과정에서 레이아웃만 새로 바뀌는 게 아니다. 액티비티도 새로운 인스턴스로 생성되어 시작한다. 서로 다른 레이아웃을 보여주기 위해서는 onCreate() 메서드 안에서 setContentView 메서드가 다시 호출되야 하기 때문이다. 따라서 안드로이드 화면 방향이 변경되면 기존의 액티비티 인스톤스는 소멸되고 새로운 액티비티 인스턴스가 생성되 onCreate()의 setContentView에서 가장 적합한 리소스에 따라 액티비티가 그려지게 된다. 이렇게 런타임 시 장치 구성(화면 방향, 언어, 키보드) 변경이 생기면 현재의 액티비티는 소멸되고 새로운 액티비티를 생성한다. 


※ 가로/세로 화면 변경 시 데이터 저장

: 만약 1초에 한 번씩 0부터 1씩 더해지는 count 변수가 있고 count 변수가 메인 액티비티에 출력된다고 가정하자. count가 10일 때 화면이 가로 방향으로 변경되면 count 값은 0에서 다시 출력된다. 화면이 가로 방향으로 변화는 장치 구성의 변경이기 때문에 기존의 메인 액티비티 인스턴스는 사라지게 된다. 따라서 count 변수도 사라지고 새로운 액티비티가 생성되 count는 0부터 다시 시작하게 된다. 이러한 결함을 바로 잡으려면 화면 구성이 변경되기 전의 count 값을 별도의 공간에 저장하다가 새로운 액티비티 인스턴스가 생성되면 count 값을 복구해야 한다. 이를 해주는 게 다음 함수이다.

@Override protected void onSaveInstanceState(Bundle saveInstanceState)

Bundle 객체는 문자열 키와 특정 타입의 값 한 쌍으로 데이터를 저장하는 객체이다. 저장할 데이터를 saveInstanceState.putInt("count", count); 형식으로 저장할 수 있다. Bundle 객체에 저장하거나 읽을 수 있는 타입은 기본형 데이터 타입이나 Serializable 또는 Parcelable 인스턴스를 구현하는 객체여야 한다. 위 메서드는 onPause(), onStop(), onDestroy()가 호출되기 전에 호출된다. 따라서 화면 방향이 가로로 변경되면 위 메서드가 콜백되고 count 값을 저장하면 된다. 다음으로 count 값 복구는 아래와 같이 하면 된다.

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { count = savedInstanceState.getInt("count", 0); } }

복구할 때는 onCreate 메서드에서 번들 객체에 저장된 값을 확인하면 된다. 화면 방향이 변경되면 새로운 액티비티 인스턴스가 생성되므로 onCreate 메서드가 호출된다. onCreate 메서드의 매개변수인 번들 객체 내부에는 우리가 onSaveInstatceState에서 값을 저장하는 데 사용한 번들 객체가 전달된다. 따라서 onCreate 매개변수 번들 객체에서 저장했던 count 값을 얻을 수 있다. 


※ onSaveInstanceState(Bundle)의 다른 용도

: 안드로이드는 메모리 회수를 해야 한다. 일정 시간동안 장치를 사용하지 않으면 액티비티가 소멸될 수 있다. 물론, 실행 중인 액티비티는 절대로 회수하지 않는다. 일시 정지(Pause), 중단(Stopped) 상태에 있는 액티비티만 회수한다. 따라서 액티비티가 일시 정지/중단 되면 액티비티는 onSaveInstanceState를 호출한다. 그리고 Bundle 객체는 안드로이드 운영체제에 의해 액티비티 레코드에 기록된다. 액티비티가 일시 정지/중단이 되면 보존(stashed) 상태가 된다. 보존 상태에서는 액티비티 객체는 존재하지 않고 액티비티 레코드만 존재하게 된다. 안드로이드 운영체제는 보존 상태의 액티비티를 액티비티 레코드를 사용하여 되살린다. 이 때 Bundle 객체를 참조하여 데이터를 복구할 수 있다. 참고로 액티비티 레코드는 사용자가 Back 버튼을 누를 때 액티비티가 완전히 소멸되는 데 그 때 액티비티 레코드도 같이 소멸된다.


* onSaveInstanceState는 사용자가 Back 키로 액티비티를 명시적으로 폐기한 경우 호출이 안 된다. 영속적으로 저장하고 싶으면 onPause에서 저장해라.

+ Recent posts