관리 메뉴

오늘도 더 나은 코드를 작성하였습니까?

실수하기 쉬운 안드로이드 생명주기 7개(학습 및 번역) 본문

카테고리 없음

실수하기 쉬운 안드로이드 생명주기 7개(학습 및 번역)

hik14 2022. 11. 10. 15:22

7 Android Lifecycle Interview Questions That Some Got Wrong

 

올바른 수명 주기 이해는 Android 개발에서 가장 중요합니다.

To master Android 개발은 수명 주기를 완전히 마스터하는 것이 가장 중요합니다.

 

불행히도, 일부 새로운 Android 개발자들이 실수로 생명주기를 완전히 파악하는 것을 놓쳤을 수도 있다는 사실을 알게 되었습니다.

추후에 디버그하기 어려운 이상한 버그 및 문제로 이어질 수 있습니다.

 

인터뷰를 통과하기 위해서가 아니라 Android 개발에 대해 더 확실히 이해하고 쉽게 놓칠 수 있는 함정을 방지하기 위해 이것을 공유합니다.

 

1. Launch Fragment by Default

아래 코드는 무엇이 문제일까요?

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        supportFragmentManager
           .beginTransaction()
           .replace(R.id.container, MainFragment())
           .commit()
   }
}

오답:

- KTX를 사용하여 fragment을 커밋하지 않았습니다.
- .replace 대신 .add를 사용해야된다.

정답:

Activity가 killed되고 restored되는 경우 이전에 커밋한 Fragment가 자동으로 복원됩니다.

따라서 Fragment의 새로운 생성을 방지하지 않으면, 새로운 Fragment가 생성되므로 2개의 Fragment가 동시에 inActive됩니다.

Fragment에 대한 작업이 두 번 실행될 수 있으므로 이상한 버그가 나타날 수 있습니다.

 

state restoration중에 Fragment 생성을 방지하려면 stateInstanceState == null의 조건 검사로 감싸야 합니다.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d("Track", "MainActivity onCreate")
        // Prevent recreation of fragment
        if (savedInstanceState == null) {
            supportFragmentManager
                .beginTransaction()
                .replace(R.id.container, MainFragment())
                .commit()
        }
   }
}

 2. Create Fragment with Constructor

프래그먼트를 만들고 커밋할 때 아래 코드를 사용합니다.

supportFragmentManager
    .beginTransaction()
    .replace(R.id.container, MainFragment())
    .commit()
class MainFragment: Fragment() {
    // MainFragment codes
}

fragment에 repository 참조가 필요한 경우 아래와 같이 코딩할 수 있습니까? 왜요?

class MainFragment(private val repository: Repository): Fragment() {
    // MainFragment codes
}

잘못된 대답

- 예, .replace(R.id.container, MainFragment(repository))를 사용하여 인스턴스화할 수 있습니다.
- 아니요, 컴파일 및 실행되지 않기 때문입니다.

 

옳바른 대답

아니요, 매개변수화된 생성자를 사용하여 Fragment를 직접 인스턴스화해서는 안 됩니다(매개변수화된 생성자가 필요한 경우 이 문서에 따라 이를 처리하기 위해 특수 FragmentFactory가 필요합니다)

 

아래처럼 코딩한다면,

class MainFragment(private val repository: Repository): Fragment() {
    // MainFragment codes
}

여전히 컴파일도 되고 실행됩니다.

그러나 State Restoration 중에 충돌합니다(때로는 테스트 중에 찾을 수 없음)

Android 상태 복원 코드는 항상 Fragment를 복원하기 위해 Fragment Empty Constructor를 찾습니다.

따라서 우리는 항상 우리가 구성하는 Fragment에 대해 파라미터가 없는 빈 생성자가 있는지 확인해야 합니다

3. Instantiate ViewModel Directly

Jetpack Architecture Component에서 ViewModel을 제공하는 아래 코드의 문제점은 무엇입니까?

class MainActivity: AppCompatActivity() {
    private val viewModel = MainViewModel()
    // Some other Activity Code
}

class  MainViewModel(): ViewModel {
    // Some other ViewModel Code
}

오답
- 문제가 없습니다. 컴파일되고 잘 작동합니다.
- 우리는 또한  몇 가지 종속성을 주입해야 합니다. MainViewModel(리포지토리)

 

정답
ViewModel을 직접 인스턴스화해서는 안 됩니다.

Jetpack Architecture Component에서 제공하는 ViewModel은 Activity보다 수명이 긴 구성 변경을 통해 살아남기 위한 것입니다.

 

ViewModel을 수동으로 인스턴스화하면 작동하지만 구성 변경을 견디지 못하는 일반 ViewModel이 됩니다.

Jetpack Architecture Component가 ViewModel이 의도한 것이 아닙니다.

 

 ViewModel을 인스턴스화하려면 아래와 같이 ViewModel KTX를 사용하는 것이 좋습니다.

class MainActivity: AppCompatActivity() {
    private val viewMode:MainViewModel by viewModels()
    // Some other Activity Code
}

viewModels()는 새로 시작되거나 종료된 프로세스에서 복원된 경우에만 새 ViewModel을 인스턴스화합니다

구성 변경이 있는 경우 다시 생성하지 않고 기존 ViewModel을 검색합니다.

viewModels()는 필요에 따라 SavedStateHandle도 자동 주입합니다(예: Activity의 SavedInstanceState 및 Intent)

Dagger Hilt를 통해 관리되는 다른 종속성이 있는 경우에도 자동 주입됩니다.

@HiltViewModel
class MyViewModel @Inject constructor(
    private val repository: Repository,
    savedStateHandle: SavedStateHandle
) : ViewModel {
    // Some other ViewModel Code
}

4. ViewModel as StateRestoration Solution

 

아래 코드와 같이 ViewModel에서 제공하는 Jetpack Architecture Component의 역할은 무엇인가요?

class MainActivity: AppCompatActivity() {
    private val viewMode:MainViewModel by viewModels()
    // Some other Activity Code
}

잘못된 답변

ViewModel은 상태 복원을 위한 것입니다.

Activity가 종료되고 다시 시작되면 ViewModel이 원래 상태를 복원하는 데 도움이 됩니다.

 

정답
ViewModel은 실제로 Android 개발자를 위한 MVVM 디자인 패턴을 권장하는 Google 제공 Jetpack 아키텍처 구성 요소입니다.

구성 변경에서 살아남는 추가 기능이 있습니다. 

즉, 장치가 회전할 때 Activity와 Fragment가 파괴되더라도 각각의 ViewModel은 유지됩니다.

 

이것은 프로세스 죽음에서 살아남는 것처럼 보이지만 프로세스 죽음 문제의 절반만 해결한 것입니다

 

5. LiveData as State Restoration Solution

 LiveData를 제공하는 Jetpack Architecture Component의 역할은 무엇입니까?

 

잘못된 답변

- 이름에서 알 수 있듯이 Activity의 lifeCycle을 통해 데이터가 유지되도록 합니다.

- 데이터는 프로세스가 종료된 후 Activity가 다시 돌아올 때 자체적으로 복원됩니다.

 

정답

LiveData는 Process-Death에서 살아남기 위한 것이 아닙니다.

Observe 인스턴스(Activity 또는 Fragment)의 수명 주기에 따라 방출된 값을 제어하는 ​​특수한 유형의 데이터입니다.

ViewModel이 구성 변경에서 살아남음에 따라 ViewModel 내부에 있는 LiveData도 마찬가지입니다.

이렇게 하면 LiveData 방출 값이 아래 이미지와 같이 제어됩니다.

그러나 LiveData 자체는 메모리가 부족할 때 시스템에 의해 Activity가 종료되는 Process-Death에서 살아남을 수 없습니다. ViewModel 자체도 파괴되기 때문입니다. 여기에 있는 단계를 사용하여 시뮬레이션할 수 있습니다

@HiltViewModel
class MyViewModel @Inject constructor(
    private val repository: Repository,
    savedStateHandle: SavedStateHandle
) : ViewModel {
    // Some other ViewModel Code
}

과거에 Activity에서 단독으로 처리했던 Intent 및 SavedInstanceState를 모두 처리하는 향상된 메커니즘입니다.

LiveData가 Process-Death에서 살아남을 수 있도록 SavedStateHandle에서 LiveData를 검색합니다.

val liveData = savedStateHandle.getLiveData<String>(KEY)

 

마찬가지로, 이것은 이제 Process-Death에서 살아남을 수 있는 stateFlow에도 적용됩니다.

val stateFlow = savedStateHandle.getStateFlow<String>(KEY, 0)

6. When is The View Destroyed But Not the Instance

Activity 또는 Fragment와 같은 Android 프레임워크 인스턴스에는 일반적으로 View가 포함됩니다.

인스턴스의 View가 파괴되지만 인스턴스(예: Activity 또는 Fragment) 자체가 파괴되지 않는 시나리오가 있나요?

 

오답

- 구성 변경 시(예: 회전)

- 메모리가 부족하고 앱이 백그라운드에 있을 때 프로세스가 앱을 종료했습니다.

 

정답

아래에 언급된 2가지 시나리오의 경우

 

- 구성 변경 시(예: 회전)

- 메모리가 부족하고 앱이 백그라운드에 있을 때 프로세스가 앱을 종료했습니다.

 

인스턴스(액티비티 또는 프래그먼트) 및 해당 뷰가 파괴됩니다

 

인스턴스(액티비티 또는 프래그먼트) 및 해당 뷰가 파괴됩니다.

 

실제로 Activity는 항상 뷰와 함께 소멸됩니다. 따라서 Activity에는 onDestroyView()라는 이름의 수명 주기 메서드가 없습니다.

Fragment에만 onDestroyView()가 있습니다. 대부분의 경우 Fragment와 해당 뷰가 함께 파괴되었습니다.

 

그러나 Fragment이 다른 Fragment으로 교체되는 경우(Fragment 트랜잭션 명령어 replace)(참고: Fragment 트랜잭션 명령어 add가 아니라.) 스택 아래 Fragment은 여전히 ​​존재하지만 뷰는 파괴됩니다!

 

 아래와 같이 트리거된 LifeCycle API에 의해 추가로 설명됩니다.

Fragment가 다른 Fragment로 Replace되면 onDestroyView()가 호출되지만 onDestroy() 또는 onDetect()는 호출되지 않습니다.

 

 

복잡성으로 인해 프래그먼트로 작업할 때 실수로 빠지는 많은 함정이 있습니다. 

다음은 이를 방지하기 위해 확인하는 것이 좋은 상위 7개의 조각 관련 문제입니다. 

이것은 발견하기 어려운 많은 이상한 버그에 빠지는 시간을 절약할 수 있습니다.

7. Lifecycle Aware Coroutine

우리는 앱에서 코루틴을 사용합니다. 어떻게 코루틴이 수명 주기를 인식하는지 확인할 수 있습니까? (참고: Android Framework에서 제공한 것을 사용하도록 제안)

 

오답

- 일반 View의 경우,  Activity 또는 Fragment에서 lifecycleScope를 사용하고 ViewModel에서 viewModelScope를 사용합니다.

- compose View의 경우, 구성 가능한 기능이 활성화되지 않은 경우 수집하지 않는 stateFlow에 대해 collectAsState()를 사용합니다.

 

정답

일반 View의 경우 lifecycleScope를 사용할 수 있지만 Activity(또는 Fragment)의 전체 수명 주기 이벤트 동안 활성화됩니다.

때로는 onStart() 또는 onResume() 후에만 일부 프로세스가 활성화되기를 원하기 때문에 이는 이상적이지 않습니다.

그렇게 하려면 repeatOnLifecycle과 같은 API를 사용하여 lifecycleScope 내에서 추가 범위를 제공해야 합니다.

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.stateFlowValue.collect {
            // Do something
        }
    }
}

compose View의 경우

collectAsState()가 작성 기능이 활성화되어 있을 때 데이터가 안전하게 사용되는지 확인하지 않더라도 StateFlow의 방출 프로세스가 계속되는 것을 중지하지 않으므로 리소스가 낭비될 수 있습니다.

Activity(또는 Fragment)가 올바른 수명 주기(예: onStart() 이후)에 있을 때만 방출이 발생하도록 하려면 collectAsStateWithLifecycle()을 사용해야 합니다(hot stateFlow에 대해서는 WhileSubscribed(...)와 함께).

collectAsStateWithLifecycle()을 살펴보면 repeatOnLifecycle(...)도 사용하고 있습니다.

 더 이해하기 위해 아래는 이 기사의 stateFlow를 사용하는 카운터 값 생성기의 간단한 디자인입니다.