[Android] Activity와 Fragment 비교
안녕하세요 골드입니다.
요즘 날씨가 많이 덥습니다.
안드로이드에서 View를 보여주기 위한 Container역할을 하는 두 개의 Class가 있습니다. Activity와 Fragment입니다. 안드로이드에서 가장 중요한 개념 중 하나인 만큼 자세히 알아보겠습니다. 이 글은 Android 공식 문서를 바탕으로 작성하였습니다.
1. Activity 소개
Activity는 main() 메서드를 사용하여 앱을 실행하는 프로그래밍 패러다임과 다른 안드로이드 시스템의 특수성에 의하여 생긴 개념입니다. 모바일 앱은 데스크톱과 다르게 항상 같은 위치에서 시작되는 것이 아닙니다. 예를 들어, 이메일 앱을 열면 이메일 보내기 화면으로 바로 이동하거나 할 수 있습니다. 즉, 앱이 다른 앱을 호출할 때 앱의 전체를 호출하는 것이 아니라 특정 Activity를 호출하는 것입니다. 결국 Activity는 앱과 사용자의 상호작용을 위한 진입점 역할과 동시에 하나의 UI 화면을 그리는 Container 역할을 수행합니다.
1. Fragment 소개
Fragment는 독립적으로 존재할 수 없습니다. 반드시 Activity 안에(혹은 다른 Fragment) 정의되어야 합니다. 그런 의미에서 Activity가 더 큰 집합이라고 할 수 있습니다. Fragment는 Activity에 종속되어 있으면서 자체적으로 수명주기를 갖고 있습니다. Fragment를 다양한 화면 구성을 위한 앱 UI의 재사용을 위해 설계되었습니다. 다양한 화면 비율을 갖고 있는 안드로이드 생태계에서 Activity 안에 화면 UI를 Fragment로 나누면 런타임 시 Activity 화면을 동적으로 추가, 교체, 삭제할 수 있습니다. 또한 백 스택에 보관할 수 있기 때문에 변경사항을 취소하는 것 또한 가능합니다. 결국 Fragment는 다양한 UI를 모듈화하여 재사용하고 화면 구성을 더욱 쉽게 할 수 있도록 도와줍니다.
2. Activity의 수명 주기
2.1 Activity 수명 주기의 이해
사용자가 앱을 사용하고, 나가고, 다시 돌아오면 앱의 Activity 인스턴스는 수명 주기 안에서 상태가 전환됩니다. Activity 클래스는 이러한 상태 변화를 알아차릴 수 있는 여러 콜백을 제공합니다. 대표적으로 Activity에 선언되어 있는 onCreate()같은 함수가 그러한 콜백 함수입니다. 수명주기에 대한 자세한 예를 든다면, 스트리밍 동영상 플레이어를 빌드하는 경우, 사용자가 다른 앱으로 전환하였을 때, 동영상을 일시중지하고 네트워크 연결을 종료할 수 있습니다. 사용자가 다시 앱으로 돌아오면 네트워크를 연결하고 일시중지한 지점부터 동영상을 다시 재생합니다. 즉, 수명주기에 따른 여러 콜백은 Activity의 상태 변화에 따라서 적합한 작업을 수행할 수 있도록 해야 합니다. 또한, 수명주기 콜백을 올바르게 구현하면 리소스가 낭비되거나 다른 앱으로 전환할 때, 다른 앱에서 다시 돌아올 때 야기되는 다양한 문제들을 예방할 수 있습니다.
2.2 Activity의 수명 주기
- onCreate() : 필수적으로 구현해야 하며, 시스템이 Activity를 생성할 때 실행됩니다. Activity는 생성되면서 CREATED 상태가 됩니다. onCreate()에서는 주로 데이터를 바인딩하거나, Activity와 ViewModel을 연결합니다. savedInstanceState라는 매개변수가 있는데, 이 매개변수는 Activity의 이전 상태에 대한 정보가 저장되어 있는 Bundle 객체입니다. 처음 생성되었다면 이 값은 null입니다. 연결된 수명 주기 인식 구성요소가 있다면 ON_CREATE 이벤트를 수신합니다.
- onStart() : Activity가 STARTED 상태에 들어가면 시스템은 이 콜백을 호출합니다. 이제 Activity가 사용자에게 표시되는 단계이며, 앱이 Activity를 포그라운드에 보내 사용자와 상호작용할 준비를 합니다. 이 함수에서 앱이 UI를 관리하는 코드를 초기화합니다. 연결된 수명 주기 인식 구성요소는 ON_START 이벤트를 수신합니다.
- onResume() : Activity가 RESUMED 상태에 들어가면 시스템은 이 콜백을 호출합니다. Activity는 포그라운드에 표시되며 앱이 사용자와 상호작용합니다. 어떠한 이벤트로 앱의 포커스가 사라기 전까지 앱은 이 상태에 머무릅니다. 예를 들어 전화가 오거나, 멀티 윈도우 상태에서 다른 앱과 상호작용하는 경우가 있습니다. 이렇게 방해되는 이벤트가 발생하면 Activity는 PAUSED 상태에 들어가고, 시스템이 onPause() 콜백을 호출합니다. Activity가 PAUSED 상태에서 다시 RESUMED 상태로 돌아오면 시스템이 onResume()을 다시 호출합니다.
- onPause() : 사용자가 이 Activity를 잠시 떠나면 앱은 이 콜백을 호출합니다. 잠시 떠난다의 뜻이 반드시 Activity가 소멸되었다는 뜻은 아닙니다. 예를 들어 단순히 Activity가 포그라운드에 존재하지 않거나 멀티 윈도우 환경에서 포커스를 잃었을 때가 있습니다. onPause()를 사용하여 포그라운드에 있지 않을 때 배터리 성능에 영향을 미치는 불필요한 기능들은 모두 정지할 수 있습니다. onPause()는 아주 잠깐 실행되기 때문에 상태 저장 작업을 실행하기에 시간이 부족할 수 있습니다. 이러한 작업은 onStop()에서 실행합니다.
- onStop() : Activity가 더 이상 사용자에게 표시되지 않으면 시스템은 이 콜백을 호출합니다. 예를 들어 새롭게 시작된 Activity가 화면 전체를 차지하게 되었을 때 그러합니다. onStop()을 사용하여 CPU를 많이 소모하는 작업들을 종료해야 합니다. 혹은 데이터를 저장하는 적절한 시기가 될 수도 있습니다. 그러나 Activity가 완전히 종료된 것은 아니기 때문에 다시 시작되어 사용자와 상호작용할 수도 있습니다. 만약 이 상태에서 Activity가 다시 시작되면 onRestart()를 호출합니다. 혹은 Activity 실행이 완전히 종료되면 onDestroy()가 호출됩니다.
- onDestroy() : Activity가 완전히 소멸되기 직전에 호출됩니다. 이는 두 가지 상황이 있습니다. 하나는 사용자가 Activity를 완전히 닫거나 finish()가 호출된 경우, 또 하나는 기기 회전, 멀티 윈도우 모드로 인하여 시스템이 일시적으로 Activity를 소멸시키는 경우입니다. onDestroy()를 사용하여 onStop()에서 해제되지 않은 리소스를 해제해야 합니다.
2. Fragment의 수명 주기
2.1 Fragment 생명 주기의 이해
Fragment의 인스턴스도 각각 자신의 생명 주기를 갖고 있습니다. Activity와 마찬가지로 사용자와 Fragment가 상호작용하면서 다양한 생명 주기의 상태를 갖게 됩니다. Fragment의 생명 주기를 다루는 방법은 두 가지가 있습니다. 하나는 생명 주기 인식 구성요소로 생명 주기를 다룰 수 있다는 것과 Activity와 마찬가지로 콜백 함수를 사용하는 것입니다. Fragment가 생성되면 이를 FragmentManager에 반드시 알려야 합니다. FragmentManager는 Fragment들을 Activity에 onAttach()를 사용해 붙이거나 onDetach()로 떼는 일을 합니다.
2.2 Fragment의 생명 주기
- CREATED : Fragment의 생명 주기가 CREATED 상태라면 이미 onAttach()를 통해 FragmentManager에 추가된 상태입니다. 이 상태에서 데이터를 초기화, 복구하거나 저장된 상태를 불러옵니다. savedInstanceState가 null이라면 Fragment가 생성되는 것이고 그렇지 않다면 재생성되는 것입니다. 아직 Fragment 안에 선언되어 있는 View들은 생성되지 않습니다.
- STARTED : Fragment안에 있는 View들을 사용할 수 있는 상태입니다. 물론 View들은 non-null일 경우 사용 가능하며, View들의 상태 역시 STARTED 상태가 됩니다.
- RESUMED : Activity와 마찬가지로 사용자와 Fragment가 상호작용하는 단계입니다.
- STARTED : Fragment의 onPause()를 호출하는 단계이지만 Activity와 다르게 PAUSED가 아닌 STARTED 상태입니다. 사용자로부터 Fragment의 포커스를 잃은 상태입니다. 옵저버에게 ON_PAUSE 이벤트를 내보냄으로써 위에 STARTED와는 차이가 있습니다.
- CREATED : 상태명은 다르지만 Activity와 마찬가지로 더 이상 Fragment가 포그라운드에서 보이지 않게 되는 상태입니다. 옵저버에게 ON_STOP 이벤트를 내보냅니다. onDestroyView()가 호출되면 Fragment의 View들은 detach되며 옵저버에게 ON_DESTROY 이벤트를 내보냅니다. Fragment는 가비지 컬렉터에 의해 제거될 준비를 위해 관련 데이터들은 안전하게 제거되어야 합니다.
- DESTROYED : 앞에서 Fragment 안에 있는 View들이 detach되고 이제 Fragment가 FragmentManager로부터 detach되어 소멸하게 됩니다.
3. Activity의 통신
Activity의 통신은 프로세스의 통신과 비슷합니다. Activity간 데이터를 전송하기 위해서는 Intent 객체를 활용해야 합니다. Bundle 객체를 생성하여 해당 객체에 데이터를 묶어서 Intent로 전달할 수 있습니다. Intent를 토해 데이터를 전송할 때 주의해야 할 점은 데이터의 크기를 몇 KB로 제한해야 한다는 것입니다. 너무 많은 데이터를 전송하였을 경우 시스템에서 예외가 발생할 수 있습니다.
val intent = Intent(this, MyActivity::class.java).apply {
putExtra("media_id", "a1b2c3")
// ...
}
startActivity(intent)
출처 : 안드로이드 공식 문서
3. Fragment의 통신
Fragment의 통신은 대표적으로 공유 ViewModel을 사용할 수 있습니다. 하나의 ViewModel을 공유하고 그 안에 LiveData를 정의하여 observe하면 각 Fragment들은 서로의 존재를 몰라도 ViewModel을 통해 공통의 데이터를 공유할 수 있게 됩니다.
class ListViewModel : ViewModel() {
val filters = MutableLiveData<Set<Filter>>()
private val originalList: LiveData<List<Item>>() = ...
val filteredList: LiveData<List<Item>> = ...
fun addFilter(filter: Filter) { ... }
fun removeFilter(filter: Filter) { ... }
}
class ListFragment : Fragment() {
// Using the activityViewModels() Kotlin property delegate from the
// fragment-ktx artifact to retrieve the ViewModel in the activity scope
private val viewModel: ListViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
// Update the list UI
}
}
}
class FilterFragment : Fragment() {
private val viewModel: ListViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewModel.filters.observe(viewLifecycleOwner, Observer { set ->
// Update the selected filters UI
}
}
fun onFilterSelected(filter: Filter) = viewModel.addFilter(filter)
fun onFilterDeselected(filter: Filter) = viewModel.removeFilter(filter)
}
출처 : 안드로이드 공식 문서
혹은 Fragment Result API를 사용할 수도 있습니다. 이는 일회성 데이터를 전달할 때 용이합니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Use the Kotlin extension in the fragment-ktx artifact
setFragmentResultListener("requestKey") { requestKey, bundle ->
// We use a String here, but any type that can be put in a Bundle is supported
val result = bundle.getString("bundleKey")
// Do something with the result
}
}
출처 : 안드로이드 공식 문서
마지막으로 만약 내비게이션 그래프를 사용하고 있다면 Direction 클래스에 매개변수로 데이터를 전달할 수도 있으며, Bundle 객체를 전달하는 방법도 있습니다.
이상 공식 문서에 있는 내용을 정리하여 작성한 글이었습니다.
여기까지 골드였습니다.
감사합니다.
참고자료 : https://developer.android.com/guide/fragments
https://developer.android.com/guide/components/activities/intro-activities