프래그먼트란?

안드로이드 앱의 사용자 인터페이스(UI) 일부를 나타내는 모듈화된 컴포넌트이다. 라고 하면 확 와닿지 않는다.

내 개인적인 이해를 가지고 예시를 들어 설명을 하자면, 퍼즐과 비슷한 것 같다.

 

액티비티가 한 폭의 그림이 담긴 큰 틀이라고 한다면, 프래그먼트는 그 그림을 이루는 퍼즐 조각같은거다.

 

우선 프래그먼트라는 개념이 나오게 된 배경을 알아보자.

 

예전에는 핸드폰이 나왔을 때 대부분 비슷비슷한 화면이었다. 근데 지금을 보자면 핸드폰 자체도 화면 크기가 굉장히 다양하다. 갤럭시 노트와 아이폰 미니만 비교해봐도 꽤나 큰 차이다. 그런데 폴드니,, 플립이니,, 뭐니 제각각 개성을 갖춘 녀석들이 등장하기도 하고 태블릿, 스마트TV, 스마트워치 등이 등장하면서 다양한 화면 크기에 대응해야하는 시기가 와버린 것이다. 

 

다양한 화면 크기에 대응하기 위해서는 화면을 동적으로 조절하고 유연하게 대응해야 할 필요가 있다. 이와 더불어 개발 측면에서도 기능을 모듈화시키고 각각 따로 떼어내는 추세이기에 그에 따라 화면도 여러개로 나누어 조합하여서 다양한 레이아웃을 생성할 수 있도록 하는 모듈화와 재사용성을 높일 필요가 있었다. 이러한 요구사항에 부합하도록 도입된 것이 프래그먼트이다.

 

요새 앱 환경도 웹의 SPA(Single Page Application) 같이 메인 액티비티 1~2개에 여러 개의 프래그먼트를 사용해서 화면을 구현하는 편이라고 한다.

 

프래그먼트 특징

1. Jetpack Library Navigation / Viewpager 와 같은 아주 유용한 라이브러리들이 프래그먼트로 사용하도록 설계되어서 함께 사용하면 아주 편리하게 화면을 구성할 수 있다.

 

2. 액티비티 내에서 여러 프래그먼트가 동작할 때, 각 프래그먼트 간에는 직접적인 통신이 가능하다. 이를 통해 모듈 간의 상호작용이 가능하다.

 

3. 화면을 여러개의 프래그먼트로 나눠서 다양한 레이아웃을 생성 가능하고 여기 저기 쓸 수 있으니 재사용성에도 기여한다. (화면이 작은 디바이스면 프래그먼트 하나 딱 보여주고 화면이 크면 프래그먼트 여러 개 띄어서 보여준다.) + 동적인 UI 업데이트

 

4. 액티비티는 안드로이드 시스템에서 직접 관리하지만 프래그먼트는 프래그먼트 매니저가 간접적으로 관리한다.

이를 통해 메모리 리소스가 상대적으로 덜 소모된다고 한다.

 

5. 액티비티안에 종속되지만 자체적인 생명주기를 가지고 있다.

 

프래그먼트의 생명주기를 왜 이해해야하는가?

이 정도 대충 읽으면 프래그먼트가 대세구만! 하는 느낌이 온다. 그럼 이제 잘 사용하고 싶어진다. 프래그먼트를 잘 사용하려면 프래그먼트의 생명주기를 잘 이해해야한다.

 

또한, 메모리 누수를 방지하기 위해서 프래그먼트의 각 단계별 상황에서 어떤 적절한 액션을 취해서 대처해야할지 알아야 하기에 잘 이해해놓아야 한다.

 

메모리 누수(Memory Leak) 란 한정된 자원을 가진 프로그램이 할당한 메모리를 제대로 해제하지 않아 발생하는 현상이다.

즉, 일꾼들에게 작업공간을 주고서 작업이 다 끝났으면 다시 다른 일꾼에게 그 공간을 잘 할당해주어야 하는데 노는 공간이 많이 발생하면서 계속 누적되고 시스템 자원이 낭비되고 결국 성능에 악영향을 끼치는 현상이다.

 

우리가 객체를 참조하고 더 이상 쓰지 않거나 필요하지 않은 시점에선 적절하게 메모리를 해제시켜야 성능과 안정성을 높일 수 있다. 그런 시점을 알려면? 생명주기 이해 해야한다!

 

위 그림은 안드로이드 공식 문서에서 제공한 그림이다. 보면 프래그먼트의 자체의 생명주기(왼쪽)와 프래그먼트 뷰의 생명주기를 구분해서 표현한다. 이 둘 주기의 차이점을 잘 파악해두면 메모리 누수나 런타임 오류를 방지할 수 있다.

 

우선 액티비티 생명주기와의 콜백함수 차이점을 보면 onCreateView() onCreatedView(), onViewStateRestored() 와 onSaveInstanceState(), onDestroyView()가 있다.

 

차이점만 인지하고 이제 생명주기를 알아보자.

 

onAttach() & onCreate()

(+내 메모리에 잘 넣기 위해서 조금 각색해보았다. 정확한 표현이 아닐 수 있기에 (?) 표시해두었다.)

Fragment의 압축파일(?) 같은 녀석이 프래그먼트 매니저를 통해서 호스트 액티비티에 attach되고, onAttach() 작업이 완료되면 onCreate()에서 프래그먼트 자체가 압축해제(?) 되어 생성된다.

 

생명주기 그림을 보면 알겠지만 아직 프래그먼트 뷰 생성 전이므로 뷰와 관련된 작업을 이 메소드에서 진행하는건 적절하지 않다.

 

한줄 요약

onAttach() -> 프래그먼트가 호스트 액티비티에 attach 된다.

onCreate() -> attach된 프래그먼트 자체가 생성된다.

 

onCreateView & onViewCreated()

onCreateView에서 프래그먼트 뷰가 초기화되고 완료됨과 동시에 onViewCreated()를 호출하여 완전히 생성된 뷰 객체를 반환한다. onCreateView에서 레이아웃을 inflate 하기에 뷰 객체들을 참조할 수 있지만, 안정적으로 뷰 객체의 생성이 보장이 된 onViewCreated에서 참조하는 것이 안전하다. (+ 여기에서 findViewById, 뷰 바인딩, LiveData 옵저빙, 각종 어댑더 세팅하면 된다.)

 

이제 슬슬 생명주기를 왜 알아야하는지 감이 오기 시작하죠?? (어디서 어떤 처리를 해야하는지 알겠죠?)

 

onCreateView() - > 프래그먼트 뷰 초기화 + 뷰 객체 반환

onViewCreated() -> 뷰의 생성 완료를 보장

onViewStateRestored() 

프래그먼트의 상태를 복원할 때 호출된다. 프래그먼트의 뷰 계층 구조와 연관된 상태를 복원하는데 사용된다.

많은 블로그에서 이렇게 설명한다. 사실 이렇게 들으면 초보입장에서는 알아듣기 힘든 것 같다.

 

우선, 이 콜백메소드는 일반적으로 프래그먼트가 소멸되고 다시 생성될 때 호출된다.

쉽게 이야기하자면, 우리가 앱을 보다가 잠시 떠났다가 들어왔을 때 다 초기화되어있을 수도 있지만 요즘 앱들은 내가 나갔다가 와도 내가 보던 그 화면 위치, 내가 작성하던 텍스트 등 포커싱 아웃 전 내 UI 현재 상태를 유지하고 있다.

 

그 상태들을 복원시킬 때 쓰이는게 이 메소드다. 

 

onViewStateRestored()  - > 소멸된 프래그먼트 상태 복원

 

onStart()

프래그먼트가 사용자에게 보여질 수 있을 때 호출되며 이 때부터 사용자에게 프래그먼트 뷰가 보이게 된다. 이 시점부터 프래그먼트는 자식 프래그먼트매니저를 통해 프래그먼트의 추가, 교체, 제거 등의 작업으로 관리되어진다.

 

onResume()

액티비티와 마찬가지로 onResume() 부터 사용자와 상호작용할 수 있는 상태이다. 이 때 상호작용에 필요한 작업을 수행하면 된다. 

 

onPause()

프래그먼트가 일시 중지될 때 호출된다.

onPause()에서는 사용자와의 상호작용이 중단되는 시점에서 필요한 작업을 수행한다.

 

onStop()

프래그먼트가 사용자에게 더 이상 표시되지 않을 때 호출된다.

onStop()에서는 UI 업데이트 및 리소스 정리와 같은 작업을 수행한다.

 

onSaveInstanceState()

프래그먼트가 소멸되기 전에 호출되며, 프래그먼트의 현재 상태를 저장하는 데 사용됩니다. 주로 화면 회전이나 다른 구성 변경 시에 발생하는 프로세스의 재시작으로부터 데이터를 보존하고 복원하는 데 활용됩니다. 

 

onDestroyView()

프래그먼트의 뷰 계층 구조가 소멸될 때 호출됩니다.

onDestroyView()에서는 UI와 관련된 자원을 해제하는 작업을 수행합니다.

 

onDestroy()

프래그먼트가 소멸될 때 호출됩니다.

onDestroy()에서는 필요한 자원을 해제하고 정리하는 작업을 수행합니다.

 

onDetach()

프래그먼트가 액티비티에서 분리될 때 호출됩니다.

onDetach()에서는 프래그먼트가 속한 액티비티에 대한 참조를 해제하는 작업을 수행합니다.

Activity

안드로이드에서 Activity는 화면에 표시되는 UI 구성을 위해 가장 기본이 되는 요소이다. UI 구성을 위한 틀의 개념!

안드로이드 앱은 지정된 Activity를 통해 사용자에게 UI를 표시하게 된다.

 

맨 처음 그냥 아무 수정없이 프로젝트를 생성하면 보게 되는 것이 MainActivity인데 이 Activity가 바로 앱이 실행되고 최초로 보여지는 Activity이다. (꼭 이름이 MainActivity이어야 하는건 아니다.)

 

 

위와 같이 manifest파일을 보면 현재 앱이 가지고 있는 액티비티를 모두 볼 수 있고 그중 카테고리에 LAUNCHER라는 단어를 통해 실행 되었을 때 가장 먼저 실행되는 액티비티 또한 알 수 있다.

 

Activity의 생명주기 (Activity Lifecycle)

액티비티는 사람과 같이 태어나고 죽기까지의 생명주기가 있다. 사실 사람처럼은 아니고 시작과 종료 사이에 특정 행위를 수행하기 위한 상태가 있다고 생각하면 된다.

 

예를 들자면, 게임을 하다가 애인에게 영상통화가 왔고 나는 잠시 그 영상통화를 받고 다시 게임에 왔는데 게임이 진행되던 내용이 모두 사라지고 처음부터 다시 시작한다면 아주 골 때릴 것이다.

 

혹은 어떤 앱을 설치해서 설문조사를 하는데 설문조사 내용에 다 답변하여 제출 하기 전까지는 핸드폰이 다른 어떤 행동을 하는걸 허락하지 않는다면 그것도 정말 곤욕스러울 것이다. 

 

위와 같이 잠시 앱 사용을 중단하고 다른 앱을 실행시키는 일은 정말 많다. 그래서 액티비티에 각 상황마다의 상태를 정의하고 그 상태에서의 특정행위를 실행할 수 있도록 만들어주는 것이 생명주기의 필요성 중 하나이다.

 

이제 액티비티 생명주기의 콜백 메소드(상태에 따라 불려지는 함수)를 보면서 이해해보자.

위 다이어그램은  Activity Lifecycle 을 시각적으로 나타낸 것이다.

핵심 콜백 메소드로 onCreat(), onStart(), onResume(), onPause(), onStop(), onDestroy() 가  있다.

(콜백 함수의 정의로 두 가지가 있는데 하나는 다른 함수의 인자로써 이용되는 함수이면 콜백함수라 불리우고 나머지 하나는 어떤 이벤트에 의해 호출되어지는 함수도 콜백함수라 불리어진다.여기서는 후자의 속하는 것 같다.)

 

각각의 콜백 메소드가 언제 호출되고 호출이 되었을 때 어떤 동작들이 주로 구현되는지 예를 통해 살펴보자.

그 전에 foreground(포그라운드) 와 background(백그라운드) 를 알아야 한다. 포그라운드는 사용자가 현재 액티비티에 포커스하고 있을 때를 말한다. 즉, onResume()에서 onPause()호출사이를 말한다. 백그라운드는 포그라운드의 반대로 포커싱되고 있지 않은 상태를 말한다. 다만, 말그대로 백그라운드에서 제한된 기능의 실행은 되고있는 상태이다.

더보기

onCreate()

액티비티가 생성될 때 호출되며 액티비티 전체 수명 주기 동안 딱 한 번만 초기화 및 시작 로직을 실행한다.

주로 UI에 필요한 레이아웃 그리고 데이터들을 정의하고 설정하는 등의 동작을 한다.

setContentView를 통해 XML 레이아웃 파일을 넘겨줌으로써 초기화면을 띄운다.

또한, onCreated() 는 파라미터로 savedInstanceState를 받는데 그 안에 있는 액티비티의 이전 상태가 저장된 bundle객체를 통해 이전 상태를 복원하여 화면에 표시할 수 있게끔 하는 기능도 수행한다.(첫화면은 null을 담고 있다.)

onCreate() 호출 이후 시스템은 액티비티가 STARTED 상태에 진입함에 따라 onStart()와 onResume()을 연달아 호출한다.

더보기

onStart()

onStart() 메소드가 호출되면 액티비티가 실제로 사용자에게 보여지고 포그라운드 작업으로써 사용자와의 상호작용을 할 수 있도록 준비하는 동작이 실행된다. onStart() 메소드는 onCreate()와 onResume() 단계의 사이에 호출되며 굉장히 빠른 속도로 실행되고 액티비티가 RESUMED상태에 들어감과 동시에 시스템이 onResume()메소드를 호출하게 된다.

더보기

onResume()

액티비티가 RESUMED 상태에 진입하며 포그라운드에 액티비티가 표시되고 사용자와 앱이 상호작용 가능한 상태가 된다.

지금부터 어떤 방해 이벤트나 인터럽트(전화가 오거나 화면이 꺼지거나 대화상자 표시 등)가 발생하여 포커싱을 놓치지 않는 이상 앱은 RESUMED 상태에 머무른다.

더보기

onPause()

RESUMED상태에서 방해 이벤트나 인터럽트 발생으로 액티비티가 PAUSED 상태에 들어가게 되면 시스템은 onPause() 메소드를 호출한다. 이 때는 해당 액티비티가 포커싱되지 않아 포그라운드에 있지 않게 되므로 다시 RESUMED 상태에 들어가기 전까지 계속 실행되어서는 안되지만 언젠가 다시 시작할 수 있는 작업을 잠시 일시중지하는 작업을 수행한다.

 

액티비티가 다시 시작되면  메모리 상 남아있던 액티비티 인스턴스를 다시 불러와 onResume()메소드를 호출한다. 액티비티가 완전히 화면에서 보이지 않게 되면 onStop()메소드를 호출하고 액티비티는 STOPPED 상태에 진입한다.

 

키포인트 두 가지 중 첫번째는 onPause()메소드를 사용하여 배터리 수명에 영향을 미칠 수 있는 모든 시스템 리소스, 하드웨어 센서 등을 할당 해제하면 자원을 효율적으로 사용할 수 있다는 것.

두번째는 onPause()  아주 잠깐 실행되기 때문에 무언갈 저장하는 작업을 실행하기엔 시간이 부족할 수 있다는 것. 따라서 onPause() 내에서는 사용자 데이터 저장, 네트워크 호출, DB 트랜잭션 등을 실행해서는 안 된다. 이렇게 부하가 큰 작업들은 onStop() 에서 수행해야 한다.

더보기

onStop()

Activity가 종료되거나, 다른 Activity가 화면을 가릴 졌을 경우 등의 상황에 호출된다.

액티비티가 STOPPED 상태에선 해당 Activity Object가 메모리에만 남아있게 된다. 여기서는 자동으로 onSaveInstanceState()가 실행되어, 데이터를 Key-value 형태로 값을 저장하여 onCreate()에서 다시 복원을 하여 데이터만 있다면 화면을 다시 불러올 수 있다. 시스템이 더 우선순위가 높은 프로세스를 위해 메모리를 확보해야하는 경우 이 액티비티를 메모리 상에서 죽이게 되는데 메모리 상에서 죽어도 Bundle의 VIew객체 상태를 그대로 저장해둬서 상태 복원이 가능하다.

onStop() 메소드안에서는 필요하지 않은 리소스를 해제하거나 조정해야하며 CPU를 비교적 많이 소모하는 작업을 종료해야 한다. 예를 들어 GPS의 위치 인식 정확도를 낮추기, DB에 정보를 저장하기가 있다. 

만약 사용자가 다시 액티비티로 돌아오게 되면, 'STOPPED' 상태에서 다시 시작되어 onRestart()  onStart()  onResume() 이 연달아 호출되며 'RESUMED' 상태로 변화하여 다시 포그라운드 액티비티로써의 태스크를 시작하며 사용자와 상호작용을 시작한다.

사용자에 의해, 혹은 시스템에 의해 액티비티가 완전히 실행 종료될때면 시스템은 onDestroy() 콜백 메소드를 호출하게 된다.

더보기

onDestroy()

액티비티가 완전히 소멸되기 전에 이 콜백 메소드가 호출된다. 아래와 같은 경우 액티비티가 완전히 소멸된다.

  1. finish() 가 호출되거나 사용자가 앱을 종료하여 액티비티가 종료되는 경우
  2. 화면 구성이 변경되어 (기기 회전 등) 일시적으로 액티비티를 소멸시키는 경우

액티비티가 종료되는 경우 onDestroy()  마지막 라이프사이클 콜백 메소드가 된다. 만약 위의 2번 사유로 인해 호출된거라면, 시스템이 즉시 새롭게 변경된 액티비티 인스턴스를 생성하여 onCreate() 를 호출한다.

만약 onDestroy() 가 호출되기 까지 해제되지 않은 리소스가 있다면, 모두 여기서 해제해줘야 한다. Memory Leak (메모리 누수) 의 위험이 있다.

이후, 액티비티는 메모리 상 완전히 소멸되게 된다.

+ Recent posts