본문 바로가기

TIL

Activity & Fragment

- Activity LifeCycle

액티비티: 화면의 기본 구성 단위

onCreate(): 액티비티가 생성될 때 맨 처음에 호출되며, 화면에 보이는 뷰들의 일반적인 상태를 설정

이 콜백메소드는 꼭 오버라이딩 해서 구현해야함,너무 많은 작업을 수행하면 앱의 실행 속도가 느려질 수 있다

액티비티 전체 수명주기 동안 딱 한번만 동작되며 값 초기화나 시작 로직을 실행

 

onStart(): 사용자에게 보여지기 바로 전에 호출, UI를 유지하는 코드가 초기화 되는 곳(데이터 갱신, 애니메이션 시작)

 

onResume(): 사용자와 상호작용하기 바로 전에 호출, 필요한 리소스를 다시 로딩하고 필요한 상태를 복원하는 등의 작업을 수행

onStart와 마찬가지로 UI를 업데이트하거나 데이터를 로드하는 작업을 수행할 수 있다

 

onPause(): 다른 액티비티가 보여질 때 호출되며(방해 이벤트 및 인터럽트가 발생한 경우 - 전화가 오는 케이스, 멀티 윈도우 상 다른 앱에 포커스를 두는 경우) 리소스, 센서에 대한 핸들(GPS) 또는 액티비티가 일시 중지되고 사용자가 필요하지 않는 동안 배터리 수명에 영향을 미치는 모든 리소스를 해제 가능

 

onStop(): 액티비티가 더이상 사용자에게 보여지지 않을 때 호출되며 UI 새로 고침, 중지 작업, 애니메이션 실행 및 기타 시각적인 작업을 중지하기에 좋은 위치이며 리소스를 해제하거나 조정, 데이터 저장 등등 부하가 큰 작업을 하기에 적당

 

onRestart(): 액티비티가 중지된 이후 다시 화면에 표시될 때 호출, 중지된 상태에서의 변경사항을 처리 할 수 있다

 

onDestroy(): 액티비티가 소멸될 때 호출되며 Finish() 메서드가 호출되거나 시스템이 메모리 확보를 위해 액티비티를 제거할 때 호출된다

 

 

- Activity Stack

1. Standard: 기본적으로 설정되어 있는 모드로 액티비티가 호출될 때 마다 태스크에 저장하기 때문에 여러가개 중첩되서 저장 될 수 있다.

 

2. SingleTop: 맨 위 액티비티는 중복해서 쌓이지 않음(2위에 2불가 1,3 가능), 맨 위 액티비티를 다시 call하면 기존 액티비티를 재활용하여 호출 -> onNewIntent() 호출, 최상위 스택이 아니면  standard 모드 처럼 새로운 엑티비티가 생성되고 스택에 쌓인다. 

 

3. SingleTask:

다중 인스턴스 생성 안됨, 쌓여있던 스택은 남아있고 새로운 스택 공간이 하나 더 생성, 새로 생긴 스택 공간은 다른 액티비티 스택에 쌓인다 (three 액티비티 스택 영역에 one,two 액티비티 스택 쌓임) 유저가 보기엔 스탠다드와 똑같음

 

4. SingleInstance:

쌓여있던 스택은 남아있고, 새로운 스택 공간이 하나 더 생김

one , two 스택이 쌓인 공간에서 three 액티비티를 call한다면 태스크 영역에 three영역이 존재. 한 태스크의 하나의 singleInstance 액티비티만 존재 가능

 

- Fragment LifeCycle

필수 구현 메서드: onCreate(), onCreateView(), onPause()

 

onCreate(): 프래그먼트를 생성할 때 호출, 프래그먼트가 일시정지 혹은 중단 후 재개되었을 때 유지하고 있어야 하는 것을 여기서 초기화 

 

onCreateView(): 프래그먼트가 자신의 인터페이스를 처음 그리기 위해 호출, View를 반환해야 함, 이 메서드는 프래그먼트의 레이아웃 루트이기 때문에 UI를 제공하지 않는 경우에는 null을 반환하면 된다

 

onPause(): 사용자가 프래그먼트를 떠나면 첫번 째로 이 메서드를 호출, 사용자가 돌아오지 않을 수도 있으므로 여기에 현재 사용자 세션을 넘어 지속되어야 하는 변경사항을 저장한다. 

 

onStart(): 액티비티가 시작됨 상태에 들어가면 이 메서드를 호출, 사용자에게 프래그먼트가 보이게 되고, 이 메서드에서 UI를 관리하는 코드를 초기화, 이 메서드는 매우 빠르게 완료되고 완료되면 Resumed(재개)상태로 들어가 onResume() 메서드를 호출

onResume(): 이 상태에 들어갔을 때 사용자와 상호작용을 함. 어떤 이벤트가 발생하여 포커스가 떠날 때 까지 이 상태에 머무름. 프로그램이 일시정지되어 onPause()를 호출하고 다시 재개되면 onResume() 메서드를 다시 호출함. 재개 상태로 전환될때마다 필요한 초기화 작업들을 수행해야 함.

onStop(): 다른 액티비티가 완전히 화면을 가리게 되면 호출.  여기에서 화면에 보이지 않을 때 실행할 필요가 없는 기능을 모두 정지할 수 있음. 앱이 사용자에게 보이지 않는 동안 앱은 필요하지 않은 리소스를 해제하거나 조정해야 함. onPause()대신 onStop()을 사용하면 사용자가 멀티 윈도우 모드에서 Activity를 보고 있더라도 UI 관련 작업이 계속 진행 된다. 또한 onStop()을 사용하여 CPU를 많이 소모하는 종료 작업을 실행해야 함(ex.데이터 베이스에 저장할 시기)

onDestroy(): Activity 또는 Fragment가 소멸되기 전에 호출. 사용자가 Activity를 완전히 닫거나 구성변경으로 인해(기기회전 또는 멀티 윈도우 모드) 시스템이 일시적으로 소멸시키는 경우에도 호출. 여기에서는 이번 콜백 메서드(onStop()등)에서 해제되지 않은 리소스들을 해제해야 한다.

 

- Fragment 데이터 전달 방식

1. bundle과 FragmentManager로 전달
2. Fragment Result API를 사용하여 Data 전달
3. Fragment간 공통의 ViewModel(ex. HostActivity의 ViewModel)로 전달
4. Jetpack의 Navigation에서 제공하는 safe-args로 전달

 

1. bundle과 FragmentManager로 전달

//전송하려는 Fragment class
//PassBundleFragment는 본인이 전달하고자 하는 Fragment class
 val bundle = Bundle()
 bundle.putString("key", "value")
 
val passBundleBFragment = PassBundleBFragment()
passBundleBFragment.arguments = bundle parentFragmentManager.beginTransaction()
	.replace(R.id.fragment_container_bundle, PassBundleFragment())
	.commit()
    
// 받을 Fragment class    
override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
	var result = arguments?.getString("key")
	
    	return inflater.inflate(R.layout.fragment_pass_b, container, false)
    }

 

 

 

2. Fragment Result API를 사용하여 Data 전달

// 전송하려는 Fragment class
//PassBundleFragment는 본인이 전달하고자 하는 Fragment class
//fragment_container_bundle은 본인의 Fragment가 담겨있는 Container
button.setOnClickListener {
    val result = "result"
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
    parentFragmentManager.beginTransaction()
       .replace(R.id.fragment_container_bundle, PassBundleFragment())
       .commit()
}

// 받을 Fragment class

setFragmentResultListener("requestKey") { requestKey, bundle ->
        val result = bundle.getString("bundleKey")
        // Do something with the result
    }
    
//"requestKey"는 사용하는 Fragment에서 어떤 listener에게 데이터를 전달할 지 결정하기 위한 식별자로 사용된다.
// 다른 fragment에서 같은 "requestKey"로 setFragmentResultListener를 적용할 경우 마지막에 적용한 listener가 호출된다.

 

3. Fragment간 공통의 ViewModel(ex. HostActivity의 ViewModel)로 전달

하나의 Activity에서 container로 여러 Fragment를 이동하는 경우 사용할 수 있다.

class ViewModelPassActivity : AppCompatActivity() {
    val viewModel : PassingViewModel by viewModels()
}

class PassAFragment : Fragment() {
    val viewModel : PassingViewModel by activityViewModels()
}

class PassBFragment : Fragment() {
    val viewModel: PassingViewModel by activityViewModels()
}

//Activity에 Fragment들이 같은 데이터를 바라볼 수 있게 ViewModel 객체를 만들고 
// 그 객체에 데이터를 전달하여 간단하게 Fragment 통신을 달성할 수 있다.

 

4. Jetpack의 Navigation에서 제공하는 safe-args로 전달

Jetpack의 Navigation을 사용하는 프로젝트에서 적용하기 좋은 방법

//Navigation.xml에서 이동할 화면에 대한 정의(action)와 전달할 데이터(argument)를 정의한다.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@id/passArgsAFragment">

    <fragment
        android:id="@+id/passArgsAFragment"
        android:name="com.gunt.fragmentdatapassexample.pass.passargs.PassArgsAFragment"
        android:label="PassArgsAFragment" >
        <action
            android:id="@+id/passAToB"
            app:destination="@id/passArgsBFragment"
            app:launchSingleTop="true" />
        <argument
            android:name="argsString"
            app:argType="string"
            android:defaultValue=""/>
    </fragment>
    <fragment
        android:id="@+id/passArgsBFragment"
        android:name="com.gunt.fragmentdatapassexample.pass.passargs.PassArgsBFragment"
        android:label="PassArgsBFragment" >
        <action
            android:id="@+id/passBToA"
            app:destination="@id/passArgsAFragment"
            app:launchSingleTop="true"
            app:popUpTo="@id/nav_graph" />
        <argument
            android:name="argsString"
            app:argType="string"
            android:defaultValue=""/>
    </fragment>
</navigation>
//전송하려는 Fragment class
//액션에 본인이 보내려는 데이터를 담아 액션을 수행한다.
binding.btnSend.setOnClickListener {
            val action = PassArgsAFragmentDirections.passAToB(binding.etText.text.toString())
            findNavController().navigate(action)
        }
        
// 받을 Fragment class
 val args: PassArgsAFragmentArgs by navArgs()
 binding.textView.text = args.argsString