본문 바로가기

안드로이드/Q&A

[안드로이드 Q&A] 프래그먼트에서 add()와 replace()의 차이점이 무엇일까?

프래그먼트를 주로 사용하면서

add()replace()가 과연 어떤 점이 차이점이 있는지 항상 궁금했다.

그래서 각 역할에 대해서 알아보고, 차이점을 정리해보기로 했다.

 

replace()는 아래와 같이 사용된다.

fragmentTransaction.replace(int containerViewId, Fragment fragment, String tag)

 

인자로 컨테이너 아이디대체할 프래그먼트, 그리고 옵션으로 태그값을 넣게 된다.

여기서 컨테이너에 추가되었던 기존 프래그먼트를 바꾼다.

동일한 containerViewId로 추가 했던 모든 프래그먼트에 대해서 remove(Fragment)를 호출하고

새로운 프래그먼트를 추가하는 것이다.

 

add()는 아래와 같이 사용된다.

fragmentTransaction.add(int containerViewId, Fragment fragment, String tag) 

 

add() 또한 replace()와 동일하게

인자로 컨테이너 아이디와 대체할 프래그먼트, 그리고 옵션으로 태그값을 넣게 된다.

동일한 containerViewId로 추가 했던 프래그먼트를 remove하는 것이 아니라 그 위에 프래그먼트를 추가하게 된다.

 

즉, replace()add()는 둘다 일부 루트 요소에 단편을 추가하는 데 사용되지만,

replace()이전 프래그먼트를 제거한 후에 새로운 프래그먼트를 추가하다는 차이점이 있다.

 

더 명확하게 이해하기 위해서 라이프 사이클 로그 흔적들을 보자

 

[addToBackStack()이 사용되지 않았을 경우 - add() 사용하기]

Activity : onCreate() - onStart() - onResume()
//Activity is visible

add Fragment A : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()
//Fragment A is visible

add Fragment B : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()
//Fragment B is visible

add Fragment C : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()
//Fragment C is visible

remove Fragment C : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()
//Fragment B is visible

(Back button clicked)
Activity : onPause() - onStop() - onDestroy()
Fragment A : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()
Fragment B : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach() 
//App is closed, nothing is visible

위의 로그처럼 add()를 사용했을 경우 기존에 있던 프래그먼트를 지우는 것이 아니라 그 위에 다시 추가하고 있다.

그리고 remove()를 통해서 프래그먼트를 없앨 수도 있다.

 

여기서 백 버튼을 누를 경우에는 엑티비티가 종료되기 때문에

엑티비티가 종료되면서 추가되었던 프래그먼트도 모두 없애주게 된다.

 

[addToBackStack()이 사용되지 않았을 경우 - replace() 사용하기]

Activity : onCreate() - onStart() - onResume() 
// Activity is visible

add Fragment A : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment A is visible

add Fragment B : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment B is visible

(replace Fragment C)    
Fragment B : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               
Fragment A : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()
Fragment C : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment C is visible

(Back button clicked)
Activity : onPause() - onStop() - onDestroy()
Fragment C : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()
//App is closed, nothing is visible

위의 로그처럼 add()를 통해 먼저 A, B 프래그먼트를 추가했다.

그리고 여기서 replace()를 통해서 C 프래그먼트를 대체시키려고한다.

이때 add()를 통해서 추가되었던 프래그먼트 B와 A가 remove() 호출과 함께 지워지게 된다.

그리곤 프래그먼트 C가 대체된다.

 

여기서도 마찬가지로, 백 버튼을 누를 경우에는 엑티비티가 종료되기 때문에

엑티비티가 종료되면서 추가되었던 프래그먼트가 모두 종료된다.

 

추가적으로 addToBackStack()을 사용했을 때의 로그 흔적도 살펴보도록 하자.

 

[addToBackStack()이 사용될 경우 - add()를 사용하기]

Activity : onCreate() - onStart() - onResume() 
//Activity is visible

add Fragment A : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment A is visible

add Fragment B : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()
//Fragment B is visible

add Fragment C : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume()
//Fragment C is visible

remove Fragment C : onPause() - onStop() - onDestroyView() 
//Fragment B is visible

(Back button clicked)
Fragment C : onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment C is visible

(Back button clicked)
Fragment C : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()
//Fragment B is visible

(Back button clicked)
Fragment B : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach() 
//Fragment A is visible

(Back button clicked)
Fragment A : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()
//Activity is visible

(Back button clicked)
Activity : onPause() - onStop() - onDestroy() 
//App is closed, nothing is visible

여기서는 addToBackStack()을 같이 호출했을 경우 add() 했던 프래그먼트가 어떻게 작동하는지 알아볼것이다.

먼저 A, B, C의 프래그먼트를 차례대로 add()를 통해서 프래그먼트를 추가했다.

앞서 설명했던 예제와 같이 remove()를 통해서 C라는 프래그먼트를 지우려고한다.

 

그런데 좀 다른 점이 있다.

onDestroyView()까지만 호출되고 onDestroy()onDetach가()가 호출이 안되었다.

 

왜 그런걸까?

 

addToBackStack()을 호출하면, commit()을 호출하기 전에 적용된 모든 변경 내용이

백 스택에 하나의 트랜잭션으로 추가된다고 한다.

그리고 back 버튼을 누르면 모두 한꺼번에 되돌려진다.

 

즉, 프래그먼트를 제거하는 트랜젝션을 수행할때 addToBackStack()호출하지 않는 경우에는

해당 프래그먼트 트랜잭션이 적용되면 소멸이 되고, 사용자가 이를 되짚어 탐색이 불가능하게 된다.

 

반면에 프래그먼트를 제거하면서 addToBackStack()호출하면,

해당 프래그먼트는 중단되고, 사용자가 뒤로 탐색하면 재개된다.

 

그렇기에 onDestroyView()까지 호출되고,

백버튼을 누를 경우 C 프래그먼트가 다시 onCreateView()를 호출하는 것을 확인 할 수 있는 것이다. 

 

이제 연속적으로 백버튼을 누를 경우

C프래그먼트가 종료되고,

B프래그먼트가 종료되고,

A프래그먼트가 종료되고,

액티비티가 종료된다.

 

[addToBackStack()이 사용될 경우 - replace()를 사용하기]

Activity : onCreate() - onStart() - onResume() 
//Activity is visible

add Fragment A : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment A is visible

add Fragment B : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment B is visible

(replace Fragment C)    
Fragment B : onPause() - onStop() - onDestroyView()  
Fragment A : onPause() - onStop() - onDestroyView() 
Fragment C : onAttach() - onCreate() - onCreateView() - onActivityCreated() - onStart() - onResume() 
// Fragment C is visible

remove Fragment C : onPause() - onStop() - onDestroyView() 
//Activity is visible

(Back button clicked)
Fragment C : onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment C is visible

(Back button clicked)
Fragment C : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach()               
Fragment A : onCreateView() - onActivityCreated() - onStart() - onResume()   
Fragment B : onCreateView() - onActivityCreated() - onStart() - onResume() 
//Fragment B is visible

(Back button clicked)
Fragment B : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach() 
//Fragment A is visible

(Back button clicked)
Fragment A : onPause() - onStop() - onDestroyView() - onDestroy() - onDetach() 
//Activity is visible

(Back button clicked)
Activity : onPause() - onStop() - onDestroy()
//App is closed, nothing is visible

위의 코드는 addToBackStack()과 함께 replace()를 사용했을 때 어떻게 작동하는지 알아보기 위한 로그 흔적이다

 

먼저 A와 B 프래그먼트를 add()를 통해 추가한다.

그리고 C 프래그먼트를 replace()를 통해 대체한다.

 

이때 B 프래그먼트가 중지되고, 다음으로 A가 중지되면서 그 상태가 트랜잭션의 백스택에 저장된다.

그리곤 C 프래그먼트가 보여지게 된다.

 

여기서 C를 remove()를 하게 되면, 엑티비티가 보여지게 된다.

 

백버튼을 눌러보자!

C 프래그먼트에 대한 트랜잭션이 재개 되면서 다시 C 프래그먼트가 보여지게 된다.

 

이 상태로 백버튼을 또 누르게 되면

C에 대한 트랜잭션은 백스택에서 사라지게된다.

그리곤 차례대로 중지되어 있던 A와 B가 onCreateView()를 호출하면서 다시 보여지게된다.

 

이제 연속적으로 백버튼을 누르게되면

B 프래그먼트는 백스택에서 사라지게 되고,

A 프래그먼트도 백스택에서 사라지게 되고,

마지막으로 엑티비티가 종료되게 된다.

 

항상 add()replace()를 사용하면서 어떤 경우?

add()를 사용하고, replace()를 사용하는 것인가? 궁금해왔었다.

 

그리고 addToBackStack()을 사용하면 백 버튼을 누를때 엑티비티처럼 뒤로가기 기능을

이용할 수 있다는 것은 알고 있었지만, 어떤 흐름으로 보여지게 되고, 호출되는지 몰랐었다.

이번 리서치를 통해서 정리해보았는데, 궁금했던 점이 해소된거 같다.

 

[참고링크]

1. https://www.it-swarm.dev/ko/android/add-replace-%EB%B0%8F-addtobackstack-%EC%9D%98-%E2%80%8B%E2%80%8B%EC%B0%A8%EC%9D%B4%EC%A0%90/1042355407/

 

 

2. https://youngest-programming.tistory.com/21