스터디

[Manifest-Android] 12. 구성 변경

참깨빵위에참깨빵_ 2025. 6. 23. 20:11
728x90
반응형

화면 회전, Locale 변경, 다크 모드 전환 등 이벤트 발생 시 원활한 UX를 유지하려면 구성 변경 처리는 필수적이다.

안드로이드는 기본적으로 이런 변경이 발생하면 액티비티를 재시작하기 때문에 UI 상태가 일시적으로 손실될 수 있다.

이걸 효과적으로 처리하려면 아래 전략을 시도할 수 있다.

 

  • UI 상태 저장, 복원 : 액티비티 재생성 중 UI 상태를 보존, 복원하려면 onSaveInstanceState와 onRestoreInstanceState를 구현한다. 이렇게 하면 구성 변경 후 유저가 같은 상태로 돌아갈 수 있다
  • 제트팩 뷰모델 : ViewModel 클래스를 써서 구성 변경 후에도 유지되는 UI 관련 데이터를 저장한다. 뷰모델 객체는 액티비티 재생성보다 오래 지속되게 설계돼서 이런 이벤트 중에 데이터 관리하기 이상적이다
  • 구성 변경 수동 처리 : 앱에서 특정 구성 변경 중에 리소스를 업데이트할 필요 없고 액티비티가 재시작되지 않게 하려면 android:configChanges 속성을 써서 액티비티가 처리하는 구성 변경을 매니페스트에 선언하라. 그 다음 onConfigurationChanged()를 재정의해서 이런 변경사항을 수동으로 관리하라
  • 컴포즈에서 rememberSaveable 사용 : 컴포즈에선 rememberSaveable을 써서 구성 변경 사항 전반에 걸쳐 UI 상태를 저장할 수 있다. 이 메서드는 onSaveInstanceState와 유사하게 작동하지만 컴포즈에 특화돼 있으며 컴포저블 상태를 일관되게 유지하는 데 도움이 된다

 

위에서 언급한 사례 외에도 폴드를 접고 펼침 시 또한 구성 변경이 발생한다. 그래서 피그마 디자인대로 잘 만들더라도 폴드 펼침 시에는 깔끔하게 보이지만 접었을 경우에는 글자 크기가 너무 크거나 UI 구조가 어긋날 수 있다.

할 수 없다. XML 프로젝트라면 구성 변경을 캐치해서 이 때 UI 구조를 변경해줘야 하고, 컴포즈 프로젝트라면 간단한 데이터는 rememberSaveable, 복잡한 상태는 뷰모델에 저장할 수 있고 레이아웃 조정은 LocalConfiguration 객체를 사용할 수 있다.

 

XML에선 매니페스트에 내가 직접 처리할 구성 변경 사항들을 입력해줘야 한다. 아래는 예시다.

 

<activity
    android:name=".presentation.main.MainActivity"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" />

 

configChanges에 여러 값들을 썼는데 이것들은 순서대로 아래 구성 변경들을 내가 직접 처리하겠다는 뜻이다.

 

  • screenSize : 화면 크기 변경(화면 회전, 멀티 윈도우 모드에서 창 크기 변경, 폴드 접고 펼침 등)
  • smallScreenSize : 최소 화면 크기 변경(멀티윈도우 모드로 변경, 주로 태블릿 / 폴더블 기기에서 발생)
  • screenLayout : 레이아웃 변경(화면 비율 변경, 레이아웃 방향 변경 등)
  • orientation : 화면 방향 변경(가로-세로 회전, 자동 회전이 활성화된 상태에서 기기 회전)

 

이외에도 keyboardHidden(키보드 숨김, 표시), locale(언어 변경) 등 여러 값이 있다. 자세한 것은 아래 디벨로퍼를 참고한다.

 

https://developer.android.com/guide/topics/manifest/activity-element#config

 

<activity>  |  App architecture  |  Android Developers

애플리케이션의 시각적 사용자 인터페이스 일부를 구현하는 활동(Activity 서브클래스)을 선언합니다. 모든 활동은 매니페스트 파일의 {@code } 요소로 나타내야 합니다. 여기에 선언되지 않은 활동

developer.android.com

 

그리고 액티비티 / 프래그먼트에서 onConfigurationChanged()를 재정의하고 여기에 어떤 구성 변경이 발생하면 어떤 처리를 수행할지 적는다.

아래는 AI가 뱉어낸 코드니 대충 이런 느낌이라고 생각하고 넘어가면 될 듯하다.

 

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

    Log.d("MainActivity", "Configuration changed")

    // 화면 회전 처리
    when (newConfig.orientation) {
        Configuration.ORIENTATION_LANDSCAPE -> {
            Log.d("MainActivity", "Switched to landscape mode")
            // 가로 모드 전용 로직
        }
        Configuration.ORIENTATION_PORTRAIT -> {
            Log.d("MainActivity", "Switched to portrait mode")
            // 세로 모드 전용 로직
        }
    }

    // UI 모드 변경 처리 (다크모드/라이트모드)
    when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
        Configuration.UI_MODE_NIGHT_YES -> {
            Log.d("MainActivity", "Switched to dark mode")
            // 다크모드 전용 로직
        }
        Configuration.UI_MODE_NIGHT_NO -> {
            Log.d("MainActivity", "Switched to light mode")
            // 라이트모드 전용 로직
        }
    }

    // 키보드 상태 변경 처리
    when (newConfig.keyboardHidden) {
        Configuration.KEYBOARDHIDDEN_NO -> {
            Log.d("MainActivity", "Keyboard shown")
            // 키보드 표시됨
        }
        Configuration.KEYBOARDHIDDEN_YES -> {
            Log.d("MainActivity", "Keyboard hidden")
            // 키보드 숨겨짐
        }
    }

    // 폰트 크기 변경 처리
    Log.d("MainActivity", "Font scale: ${newConfig.fontScale}")

    // 언어 변경 처리
    Log.d("MainActivity", "Locale: ${newConfig.locales}")
}

 

컴포즈라면 아래처럼 할 수 있다. 역시 AI 답변을 가져온 코드기 때문에 걸러 볼 필요가 있다.

 

@Composable
fun ConfigurationAwareScreen() {
    val configuration = LocalConfiguration.current
    
    // 구성 변경 시 리컴포지션됩니다
    LaunchedEffect(configuration) {
        Log.d("Compose", "Configuration changed in Compose")
    }
    
    when (configuration.orientation) {
        Configuration.ORIENTATION_LANDSCAPE -> {
            LandscapeLayout()
        }
        else -> {
            PortraitLayout()
        }
    }
}

@Composable
fun LandscapeLayout() {
    Row(
        modifier = Modifier.fillMaxSize(),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        // 가로 모드 레이아웃
        Text("Landscape Mode")
    }
}

@Composable
fun PortraitLayout() {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center
    ) {
        // 세로 모드 레이아웃
        Text("Portrait Mode")
    }
}

 

컴포즈는 구성 변경이 발생하면 자동으로 리컴포지션되기 때문에 State 또는 StateFlow를 적절하게 쓰면 자동으로 UI가 업데이트된다. 그러나 이 효과를 기대할 수 없는 경우도 있으니 어떻게 대응하는지 정도는 찾아보고 알아둘 필요가 있다고 생각된다.

반응형