구성 변경 처리

일부 기기 설정은 런타임에 변경될 수 있습니다(예: 화면 방향, 키보드 가용성 그리고 사용자가 멀티 윈도우 모드를 사용 설정할 때). 이러한 변경이 발생하면 Android는 실행 중인 Activity (onDestroy()가 호출된 후 onCreate()가 호출됨)를 다시 시작합니다. 다시 시작 동작은 애플리케이션이 새 기기 설정과 일치하는 대체 리소스로 자동으로 다시 로드되도록 하여 새 구성에 맞게 조정되도록 설계되었습니다.

다시 시작을 적절히 처리할 때는 활동이 이전 상태로 복원되는 것이 중요합니다. onSaveInstanceState(), ViewModel 객체 및 영구 저장소 조합을 사용하여 모든 구성 변경에 걸쳐 활동의 UI 상태를 저장하고 복원할 수 있습니다. 활동 상태를 저장하는 방법에 관한 자세한 내용은 UI 상태 저장을 참고하세요.

애플리케이션이 애플리케이션 상태를 그대로 유지한 채 스스로 다시 시작할 수 있는지 시험해 보려면, 애플리케이션에서 여러 가지 작업을 실행하는 동안 구성 변경을 실행해 보아야 합니다(예를 들어 화면 방향 변경 등). 언제든 애플리케이션이 사용자 데이터나 상태를 손실하지 않고 다시 시작할 수 있어야 합니다. 그래야 구성 변경과 같은 이벤트를 처리할 수 있기 때문입니다. 그렇지 않으면 걸려 오는 전화를 받은 사용자가 한참 뒤에 애플리케이션으로 돌아왔을 때 애플리케이션 프로세스가 이미 소멸되어 있을 수도 있습니다. 활동 상태를 복원하는 방법을 알아보려면 활동 수명 주기를 참고하세요.

하지만 애플리케이션을 다시 시작하고 상당량의 데이터를 복원하면 비용도 많이 들고 미흡한 사용자 환경이 만들어지는 상황에 직면할 수도 있습니다. 그러한 상황이라면 두 가지 다른 옵션이 있습니다.

  1. 구성 변경 중 객체 보존

    구성이 변경되는 중에 활동이 다시 시작될 수 있게 허용하되, 활동의 새 인스턴스에 상태 저장 객체를 넣습니다.

  2. 구성 변경 직접 처리

    구성 변경을 처리할 때 숨겨진 복잡성이 있기 때문에 직접 구성 변경을 처리하는 것은 권장하지 않습니다. 그러나 선호하는 옵션(onSaveInstanceState(), ViewModel 객체, 영구 저장소)을 사용하여 UI 상태를 보존할 수 없다면 특정 구성 변경 중에는 시스템에서 활동을 다시 시작하지 못하도록 차단할 수 있습니다. 구성이 변경될 때 앱에서 콜백을 받으므로 필요에 따라 활동을 수동으로 업데이트할 수 있습니다.

구성 변경 중 객체 보존

활동을 다시 시작하기 위해 많은 수의 데이터 세트를 복구해야 하는 경우 네트워크 연결을 다시 설정하거나 다른 집약적 작업을 실행한 다음, 완전히 다시 시작하세요. 구성 변경 때문에 사용자 환경이 느려질 수 있습니다. 또한 시스템이 onSaveInstanceState() 콜백을 사용하여 개발자 대신 저장하는 Bundle을 사용하여 활동 상태를 완전히 복원하지 못할 수 있습니다. 이는 대형 객체(예: 비트맵)를 담도록 디자인된 것이 아니며 이 안의 데이터는 반드시 직렬화했다가 메인 스레드에서 다시 역직렬화해야 합니다. 이렇게 하면 메모리를 아주 많이 소모할 수 있으며 구성 변경이 느려질 수 있습니다. 이런 경우에는 ViewModel 객체를 사용하여 활동의 일부를 다시 초기화하는 부담을 완화할 수 있습니다. ViewModel 객체는 모든 구성 변경에서 보존되므로 UI 데이터를 다시 쿼리할 필요 없이 유지하기에 좋은 위치입니다. 앱에서 ViewModel 클래스를 사용하는 방법에 관한 자세한 내용은 ViewModel 개요를 참고하세요.

구성 변경 직접 처리

애플리케이션이 특정 구성 변경 중에 리소스를 업데이트하지 않아도 되고 그와 동시에 성능 한계가 있어 활동을 다시 시작해서는 안 되는 경우, 활동이 구성 변경을 알아서 처리한다고 선언하면 됩니다. 이렇게 하면 시스템이 활동을 다시 시작하지 않게 할 수 있습니다.

주의: 구성 변경을 직접 처리하면 대체 리소스를 사용하는 것이 훨씬 더 까다로워질 수 있습니다. 시스템이 개발자 대신 자동으로 이를 적용해주지 않기 때문입니다. 이 기법은 구성 변경으로 인한 재시작을 반드시 피해야만 하는 경우 최후의 수단으로서만 고려해야 하며 대부분 애플리케이션에는 권장하지 않습니다.

활동이 구성 변경을 직접 처리한다고 선언하려면 매니페스트 파일의 적절한 <activity> 요소를 수정하고, 처리하려는 구성을 나타내는 값이 있는 android:configChanges 속성을 포함하도록 합니다. 사용 가능한 값은 android:configChanges 속성에 관한 문서에 기록되어 있습니다. 가장 많이 사용되는 값은 "orientation", "screenSize", "screenLayout", "keyboardHidden"입니다.

  • "orientation" 값은 화면 방향이 변경될 때 다시 시작되지 않도록 합니다.
  • "screenSize" 값도 방향이 변경될 때 다시 시작되지 않도록 하지만 Android 3.2(API 수준 13) 이상에서만 적용됩니다.
  • "screenLayout" 값은 폴더블 휴대전화 및 컨버터블 Chromebook과 같은 기기에서 트리거할 수 있는 변경사항을 감지하는 데 필요합니다.
  • "keyboardHidden" 값은 키보드 가용성이 변경되었을 때 앱이 다시 시작하지 못하도록 합니다.

앱에서 방향 변경을 수동으로 처리하고 싶다면 android:configChanges 속성에서 "orientation", "screenSize", "screenLayout" 값을 선언해야 합니다. 이 속성에는 여러 개의 구성 값을 선언할 수 있습니다. 각각을 파이프 문자 |로 구분하면 됩니다.

예를 들어 다음 매니페스트 코드는 화면 방향 변경과 키보드 가용성 변경을 둘 다 처리하는 활동을 선언합니다.

<activity android:name=".MyActivity"
          android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
          android:label="@string/app_name">

이제 이러한 구성 중 하나가 변경되면 MyActivity가 다시 시작되지 않습니다. 대신 MyActivity onConfigurationChanged() 호출을 수신합니다.

onConfigurationChanged() 메서드는 새 기기 설정을 지정하는 Configuration 객체로 전달됩니다. Configuration 객체의 필드를 검토하여 새 설정을 확인한 후 인터페이스에서 사용된 리소스를 업데이트하여 적절히 변경할 수 있습니다. 이 메서드가 호출된 시점에는 새 설정을 기준으로 리소스를 반환하도록 활동의 Resources 객체가 업데이트되므로 시스템이 활동을 다시 시작하지 않아도 UI의 요소를 손쉽게 재설정할 수 있습니다.

예를 들어, 다음의 onConfigurationChanged() 구현은 현재 기기의 방향을 확인합니다.

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)

    // Checks the orientation of the screen
    if (newConfig.orientation === Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show()
    } else if (newConfig.orientation === Configuration.ORIENTATION_PORTRAIT) {
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show()
    }
}

자바

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

Configuration 객체는 변경된 것뿐만 아니라 현재 구성 전체를 나타냅니다. 대부분의 경우 개발자는 구성이 정확히 어떻게 변경되었는지에는 관심이 없고, 처리 중인 구성에 대체 리소스를 제공하는 모든 리소스를 단순히 재할당하기만 하면 됩니다. 예를 들어, 이제 Resources 객체가 업데이트되었으므로 모든 ImageView 인스턴스를 setImageResource()로 재설정할 수 있으며 새 구성에 적합한 리소스(앱 리소스 개요에 설명되어 있음)가 사용됩니다.

Configuration 필드에서 가져온 값이 Configuration 클래스에서 가져온 특정 상수와 일치하는 정수라는 점을 주의 깊게 보세요. 각 필드에 사용해야 하는 상수에 관한 문서는 Configuration 참조에 있는 적절한 필드를 참고하세요.

주의: 활동이 직접 구성 변경을 처리한다고 선언하는 경우, 대체를 제공하는 요소의 재설정에 대해 본인이 직접 책임을 지게 됩니다. 활동이 직접 방향 변경을 처리하고 가로 및 세로 방향 사이에서 바뀌어야 하는 이미지가 있는 경우, 각 리소스를 각 요소에 재할당해야 하며 이를 onConfigurationChanged() 중에 실행해야 합니다.

이러한 구성 변경을 기반으로 애플리케이션을 업데이트하지 않아도 되는 경우, 대신 onConfigurationChanged()를 구현하지 않아도 됩니다. 이런 경우, 구성 변경 전에 쓰였던 리소스가 모두 그대로 사용되고 활동의 다시 시작만 피한 것이 됩니다.

그러나 이 기법을 사용해도 일반 활동 수명 주기 동안 상태를 유지하지 않아도 되는 것은 아닙니다. 애플리케이션은 항상 종료되었다가 이전 상태를 그대로 유지한 채 다시 시작될 수 있어야 합니다. 방지할 수 없는 구성 변경은 애플리케이션을 다시 시작할 수 있습니다. 사용자가 애플리케이션을 종료하고 앱이 백그라운드에 배치되면 시스템에서 앱을 소멸할 수 있습니다.