프래그먼트 - 2 commit/commitNow/allowingStateLoss

상상코딩·2022년 3월 16일
2

안드로이드

목록 보기
14/21
post-custom-banner

Fragment 전환 시 IllegalStateException?

java.lang.IllegalStateExeption: Can not perform this action after onSaveInstatnceState

문제의 원인은 Activity의 onSaveInstanceState()가 호출된 후에 FragmentTransaction의 commit()을 동작하는 데에 있습니다.

온 블로그에서 딱 저렇게 불친절하게 말을 해놨다.^^,, 진짜 체질에도 없는 영어문서를 얼마나 찾고 읽었는지........

onSaveInstanceState 이후에 호출되면 안된다? 그게 뭔 소린데,,

onSaveInstanceStateonPause 전에 불리거나, onStop전에 불린다. 호출의 정확한 시기는 API레벨에 따라 다르당 (그냥 앱이 활동상태에만 부르도록 한다면 안찾아봐도 됨^^)

뷰는onSaveInstanceState에서 상태를 저장한다. 그리고 1.onCreate에서 savedInstanceState를 처리하거나, 2.onRestoreInstanceState로 저장된 상태를 불러와서 처리한다.

근데 우려되는 경우는 언제냐!!!!

그림을 보면, 1.onStart / 2.onSaveInstanceState / 3.onStart 이렇게 나와있는데, 이는 1.포그라운드에 있던 앱이 2.백그라운드로 빠졌다가 3.다시 포그라운드로 오는 그림이다.

자 여기서,, 2. 백그라운드로 갈 때를 잘 생각해보자.

onSaveInstaceState() 호출로 상태를 save (= 상태A) 했는데 이를 restore할 새도 없이 여기다가 다른 트랜잭션을 commit(= 상태A + 다른 트랜잭션 = 상태B) 때려버린다?

이러고 3. 다시 포그라운드로 왔을 때를 상상해보자.
상태를 restore하려고 하는데, 내가 예상한 상태는 상태B였는데, 상태A가 restore 될 것이다.

그럼 내가 설정한 모습과 다른 UI 가 보이는 것 = 상태 손실

이런 상태 손실을 막기 위해, 안드로이드는 이미 저장된 상태가 있을 때 commit을 하게 되면 IllegalStateException를 뱉는 것이다.




그럼 어떻게 해야해요?

1. (꼭 써야할 때만..)
상태 손실이 ㄱㅊ다? = commitAllowingStateLoss를 호출하면 된다.

supportFragmentManager.commit(allowingStateLoss = true){
    //수행할 트랜잭션
}

근데 손실을 감내하면서 호출 하는것은 그다지 추천하는 방법은 아니라고 docs에도 나와있긴하다!

2. 이렇게하장! + 주의사항

onStart이후로 뷰가 완전히 restore되었을 때, 또 onSaveStateInstance 호출되기 전에 그 구간에만 트랜잭션 commit을 호출하면 된다.
즉, onStart - onPause 사이에만 호출되도록!

라고 써놨지만, 당연히 뷰에 구현해둔 모든 트랜잭션은 다 뷰가 살아있을때만 접근하도록 했을 것이다.

주의해야할 시점은 비동기콜백으로 결과를 받아 commit 하는 경우이다. 즉, 해당 뷰의 생명주기와 관련없이 함수가 호출되는 경우이다.

ex. onActivityResult, 비동기서버콜백(scope 잘 처리할 것) + 웬만한 뷰 전환은 콜백으로 받아서 처리하는 대신 역할을 잘 나누어 의존성을 떨어뜨리자.


executePendingTransactions?

commit함수는 사실 바로 실행되지 않는다. 이는 비동기함수로서, mainThread에 연산을 넣어두고 바로 실행하지 않는다.

이에 따라, 어떤 프래그먼트를 add().commit()을 해두고 isAdded를 바로 체크한다면 false를 뱉을 수도 있다. 이런 과정에서 원하는 결과값이 나오지 않을 수 있다는 것!
-> 이럴 때 pending 되어있는 연산들을 다 수행하라고 하는 명령어executePendingTransactions를 사용하는 것이다.

-> 그렇지만 이것도 최후의수단으로 남겨두고 웬만하면 사용하지말자! Use commitNow()




commitNow/ commitNowAllowingStateLoss

commit vs commitNow
commitAllowingStateLoss vs commitNowAllowingStateLoss

  • Now 0 : Sync - 동기적으로 수행
  • Now x : Async - 비동기적으로 수행(메인스레드에 작업을 넣어둠)

commitNow() 사용 시 주의점 : 백스택

commit()과 혼재해서 사용할 경우 문제가 된다.
왜냐면, commit은 작업을 비동기로 처리하고, commitNow는 작업을 동기적으로 처리하기 때문. 따라서 순서보장이 안된다. 이게 왜 문제일까?

만약 B프래그먼트로 교체하기.commit()를 하고, 이후 백스택에 넣기.commitNow()를 하게되면, 어떤 연산이 먼저 수행될지 모른다. 이 경우 B가 백스택에 있을지, 원래 프래그먼트가 백스택에 있을지에 따라 결과가 달라진다.

executePendingTransactions()보다는 commitNow를 쓰세요

위에서 설명한 바와 같이 24.0.0.0 버전에서는 executePendingTransactions() 을 실행하는 것 보다 나은 대안으로 commitNow()를 추가했다.

commitNow()를 호출하면 현재 연산만 호출한다. 그러나,executePendingTransactions()를 실행하면 밀려있는 모든 연산을 수행하기 때문에, 원하는 작업뿐 아니라 밀려있던 다른 작업들도 추가로 수행되어 원하지 않는 결과를 맞이할 수도 있기 때문이다.





onSaveInstanceState의 호출 시점 : 언제불리는거야?!

구글 doc -22.03.17 기준은 아래와 같이 말하는데,

If called, this method will occur after onStop() for applications targeting platforms starting with Build.VERSION_CODES.P. For applications targeting earlier platform versions this method will occur before onStop() and there are no guarantees about whether it will occur before or after onPause().

API레벨 28 이전은 onStop()전에 불리는데, onPause() 전에 불릴지 후에 불릴지는 아무도 모른다.
28부터는 onStop()후에 불린다.


+) 참고사항
사용자가 직접 해당 액티비티를 종료했다면 onSaveInstanceState는 불리지 않는다. 왜냐면 어차피 종료해서 복구할 일이 절대 없을 거니까.





결론

  1. allowingStateLoss는 웬만하면 사용하지 말자.
  2. executePendingTransactions도 웬만하면 사용하지말자. -> 이게 필요하면 commitNow사용.
  3. commitcommitNow는 혼재하지말자. 혼재할 경우는 commitNow 전에 pending 되어있는 연산들 날려야하겠다.
  4. savedInstanceState가 있는 경우(onSaveInstanceState 호출 이후부터 restore(onCreateonRestoreStateState) 전까지)에는 commit하지 말 것.











ref.
https://medium.com/@bherbst/the-many-flavors-of-commit-186608a015b1

위링크의 번역본인데 중간중간 오번역이 조곰 있당..!! https://medium.com/hongbeomi-dev/번역-다양한-종류의-commit-8f646697559f

profile
히히낙낙
post-custom-banner

0개의 댓글