Android Studio

Android - 2주차 과제

내손은개발 🐾 2024. 4. 1. 09:49

Activity 생명주기

4대 컴포넌트 중에 하나인 Activity는 애플리케이션에서 보통 하나 이상의 Activity가 서로 연결된 형태로 구성어서 가장 기본이 되는 구성요소이다.

 

생명주기 ?

Life + cycle의 합성어, Lifecycle이다. 말 그대로 탄생하고 죽음에 이르기까지의 과정이라고 Activity에 적용한 것이다.

 

 

이미지

생명주기 호출
Activty 생성 시 화면에서 제거 종료 안하고 다른 Activity 실행

 

유튜브 앱 시작 유튜브 실행 중 카톡 실행 카톡 완벽히 종료 유튜브 재 실행
youtube > onCreate()
youtube > onStart()
youtube > onResume()
youtube > onPause()
youtube > onStop()
kakao > onCreate()
kakao > onStart()
kakao  > onResume()
kakao > onPause()
kakao > onStop()
kakao  > onDestroy()
youtube > onRestart()
youtube > onStart()
youtube > on Resume()
youtube > onRestart()
youtube > onStart()
youtube > onResume()

 

Activity는 실행부터 종료까지 많은 상태 변화를 거치며 상태가 변할 때마다 생명주기 함수가 자동으로 호출된다.

Activity의 상태는 ‘활성 상태’, ‘일시 정지 상태’, ‘비활성 상태’가 있다.

  • Activity running(활성 상태)
    • 사용자 화면에 보이고 있는 상태로 사용자 이벤트에 반응할 수 있는 상태이다.
    • 생성된 Activity는 onCreate() → onStart() → onResume() 함수가 호출되면서 활성상태가 된다.    
    • 일반적으로 setContentView() 함수를 이용하여 액티비티 화면을 출력하는데, setContentView() 함수가 호출되는 시점이 화면 출력 순간이 아니라, onResume() 함수까지 실행하고 그 후 setContentView() 함수에서 출력한 내용이 화면에 나오는 구조이다. 따라서 onResume()이 함수가 실행되기까지 onCreate(), onStart(), onResume() 안에서 setContentView()를 호출하면 화면에 레이아웃이 출력됩니다.
  • pause(일시 정지)
    • - Activity가 화면에 보이지만 포커스를 잃은 상태이다.
      - 예를 들어 dialog가 있다.
      - onPause() 함수가 자동으로 호출된다.
  • stop(비활성 )
    • 다른 Activity로 인해 화면이 완전히 가려진 상태이다.
    • onPause() → onStop()
    • 이후 다시 뒤로가기 등으로 돌아오면
    • onRestart() → onStart() → onResume()

하나씩 설명

더보기

- onCreate()

  • 가장 먼저 호출되는 메서드이다.
  • 생명주기에서 단 한 번만 실행된다.
  • 필수적으로 구현해야하고 Activity가 생성될 때 호출된다.

    정리 : 시스템이 activity를 생성할 때 실행되고 Activity의 전체 생명주기 동안 한 번만 발생해야하는 메서드이다.
    
- onStart()

  • Activity가 화면에 표시되기 직전에 호출된다.
  • onCreate()를 호출한 뒤 실행되고 Activity가 사용자에게 표시되기 전에 호출된다.

    정리 :  Activity가 사용자에게 보여지기 직전에 필요한 초기화 작업을 수행한다.
    
- onResume()

  • Activity가 화면에 보여지는 직후에 호출된다.
  • 예를 들어 이벤트나 애니메이션을 시작할 수 있다.

    정리 : Activity가 화면의 최상위에 위치하고 사용자와 상호 작용할 수 있는 상태가 될 때 호출된다.
    
- onPause()

  • 다른 Activity가 화면 위로 나타나거나 일부 가려질 때 호출된다.
  • 조심해야 할 점은 onPause()가 실행될 때는 아주 잠깐이므로 사용자 데이터 저장, 네트워크 호출, 데이터베이스 처리 등을 실행하면 안된다. 부하가 큰 종료 작업은 onStop() 상태일 때 실행되어야 한다.

- onStop()

  • Activity가 다른 Activity에 의해 완전히 가려질 때 호출되는 메서드다.
  • 예를 들어, 홈 키를 누르거나 다른 Activity로 이동할 경우가 있다.

   정리 : Activity가 더 이상 사용자에게 표시되지 않을 때 호출된다. 완전히 가려서 보이지 않을 때 실행된다.
    
- onDestroy()

  • Activity가 소멸되기 전에 호출된다. 사용자가 Activity를 닫거나 finish()가 호출되어 액티비트 종료될 때이다.
  • 여기서는 Activity가 사용하는 리소스를 해제하거나 등의 마무리 작업을 수행할 수 있다.

- onRestart()

  • onStop()이 호출 된 이후에 다시 기존 Activity로 돌아오는 경우 호출된다.
  • onRestart()가 호출된 이후 이어서 onStart()가 호출된다.

 

 

 


 

Fragment 생명주기

 

Activity와 비슷한 콜백 메서드들도 있다.

Fragment에서 최소한으로 구현해야 하는 3개의 생명주기 메서드는

  • onCreate()

fragment를 생성할 때 호출한다. fragment가 일시정지 혹은 중단 후 재개되었을 때 유지하고 있어야 하는 것을 여기서 초기화 해야한다.

  • onCreateView()

fragment가 자신의 인터페이스를 처음 그리기 위해 호출한다. View를 반환해야 한다.

이 메서드는 fragment의 layout 루트이기 때문에 UI를 제공하지 않는 경우에는 null을 반환하면 된다.

  • onPause()

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

 

하나씩 설명

더보기

- onAttach()

  • fragment가 액티비티에 연결할 때 호출
  • 연결 되기 전에 일부 초기화 작업을 수행할 수 있다.

- onCreate()

  • 프래그먼트가 액티비티의 호출을 받아 생성한다.
  • Bunddle로 액티비티로부터 데이터가 넘어온다.
  • fragment의 초기화 작업을 수행할 수 있다.

+ onActivityCreated는 중단 됨

- onCreateView()

  • 레이아웃 inflate 담당한다.
  • savedInstanceState로 이전 상태에 대한 데이터 제공

- onViewCreated()

  • onCreateView()를 통해 반환된 View 객체는 onViewCreated()의 파라미터로 전달 된다.
  • 이 때 Lifecycle이 INITIALIZED 상태로 업데이트가 되었다.
  • 때문에 View의 초기값 설정, LiveData 옵저빙, RecyclerView, ViewPager2에 사용될 Adapter 세팅은 이 메소드에서 해주는 것이 적절하다

- onViewStateRestored()

  • 저장해둔 모든 state 값이 Fragment의 View의 계층 구조에 복원되었을 때 호출된다.
  • ex) 체크박스 위젯이 현재 체크되어있는가
  • View lifecycle owner : INITIALIZED → CREATED 변경

- onStart()

  • 사용자에게 보여질 수 있을 때 호출된다.
  • Activity의 onStart() 시점과 유사하다.
  • Fragment의 childFragmentManager을 통해 FragmentTransaction을 안전하게 수행할 수 있음
  • View lifecycle owner : CREATED → STARTED 변경

- onResume()

  • 사용자와 프래그먼트가 상호작용 할 수 있는 상태일 때 호출
  • Fragment가 보이는 상태에서 모든 Animator와 Transition 효과가 종료되고, 프래그먼트와 사용자가 상호작용 할 수 있을 때 onResume Callback이 호출된다.

- onPause()

  • Fragment가 visible 일 때 onPause()가 호출된다.
  • 이 때 Faragment와 View의 Lifecycle이 PAUSED가 아닌 STARTED가 됨

- onStop()

  • Fragment가 더 이상 화면에 보여지지 않게 되면 onStop() 콜백 호출되게 된다.
  • 부모 액티비티, 프래그먼트가 중단될 때, 상태가 저장될 때 호출
  • View와 Lifecycle : STARTED → CREATED
  • API 28버전을 기점으로 onSaveInstanceState() 함수와 onStop() 함수 호출 순서가 달라짐, 따라서 onStop()이 FragmentTransaction을 안전하게 수행하는 마지막 지점이 된다.

- onDestoryView()

  • 모든 exit animation, transaction이 완료되고 Fragment가 화면으로부터 벗어났을 경우 호출된다.
  • view와 lifecycle : CREATED → DESTROYED
  • 가비지 컬렉터에 의해 수거될 수 있도록 Fragment View에 대한 모든 참조가 제거되어야 한다.
  • getViewLifecycleOwnerLiveData()

- onDestroy()

  • Fragment가 제거되거나, FragmentManager가 destroy 됐을 경우, onDestroy() 콜백 함수가 호출
  • Fragment Lifecycle의 끝을 알림

- onDetach()

  • 프래그먼트가 액티비티로부터 해제되어질 때 호출된다.
FirstFragment 생성 FirstFragment -> SecondFragment SecondFragment -> FirstFragment
> onAttach
> onCreate
> onCreateView
> onViewCreated
> onViewStateRestored
> onStart
> onResume
First> onPause
First> onStop
Second> onAttach
Second> onCreate
Second> onCreateView
Second> onViewCreated
Second> onViewStateRestored
Second> onStart
Second> onResume
First> onDestroyView
백버튼으로 돌아갈 경우
Second> onPause
Second> onStop
First> onCreateView
First> onViewCreated
First> onViewStateRestored
First> onStart
First> onResume
Second> onDestroyView
Second> onDestory
Second> onDetach

 


 

Activity Stack

  • Activity 스택(Activity Stack)이라는 개념을 사용하여 화면 간 전환을 관리한다.
  • Activity 스택은 앱이 실행 중일 때 각 화면(Activity)을 스택(Stack)에 쌓아두고, 이전 화면으로 돌아가거나 새로운 화면을 열 때마다 스택에 새로운 화면을 추가한다.
  • 이를 통해 앱이 사용자의 탐색 흐름을 추적하고, 뒤로가기 버튼을 눌렀을 때 이전 화면으로 자연스럽게 돌아갈 수 있도록 한다.

 

단어 정리

Stack ?

스택은 후입선출(LIFO, Last-In-First-Out)의 구조를 가지며, 새로운 항목이 스택의 맨 위에 추가되고, 가장 최근에 추가된 항목이 가장 먼저 제거됩니다. 따라서 Activity 스택에서는 사용자가 현재 화면에서 뒤로가기 버튼을 누를 때마다 스택의 맨 위에서 가장 최근에 추가된 화면이 제거되고 이전 화면이 보여지는 것이다.

 

Task ?

Activity 스택은 Task라는 단위로 관리된다.

각 Task는 하나 이상의 Activity로 구성되어 있으며, 백스택(back stack)이라고도 불린다.

앱 아이콘을 눌러 앱을 다시 시작하면, 앱은 해당 Task의 최상위 Activity를 다시 보여준다.

 

back stack ?

각 Task는 백스택이라는 개념을 가지고 있다.

 Activity나 Fragment가 스택(Stack) 자료 구조 형태로 관리되는 메모리 영역을 의미합니다.

사용자가 앱 내에서 다양한 화면을 이동할 때 사용되는 기록을 관리하며, 역순으로 사용자의 이전 동작을 추적한다.

 

 

stack을 관리하는 방법으로 크게 2가지가 있다.

1. LaunchMode
2. Intent Flag

 

 

1. AndroidManifest의 LaunchMode의 옵션으로 관리

launchMode ?

Activity의 특성을 정의하는데 사용되는 속성이다.

launchMode를 설정함으로써 Activity의 동작 방식을 지정할 수 있다.

 

1-1) android:launchMode="standard"

  • 기본적인 런칭 모드로, 새로운 Activity 인스턴스가 항상 현재 Task의 백스택에 추가된다.
  • 즉, Activity를 여러 번 시작할 때마다 각각 새로운 인스턴스가 생성되고 백스택에 추가되는 형식이다.

 

1-2) android:launchMode="singleTop"

  • Activity를 시작할 때, 이미 백스택의 맨 위에 동일한 Activity 인스턴스가 있는 경우에는 새로운 인스턴스를 생성하지 않고 기존의 인스턴스를 재사용한다.

 

1-3) android:launchMode="singleTask"

  • 새로운 Task에 시작되며, 해당 Task에는 단 하나의 인스턴스만 존재한다.
  • 이미 Task에 존재하는 경우에는 해당 Task를 먼저 앞으로 이동시키고, 그 위에 새로운 인스턴스를 추가한다.

1-4) android:launchMode="singleInstance"

  • 다른 launchMode와는 달리 새로운 Task에 시작된다.
  • 따라서 해당 Activity를 시작하는 Task에는 해당 Activity 인스턴스만 포함된다.
  • 또한 이 모드를 사용하는 Activity는 다른 Task와 분리되어 있으므로, 다른 Task에 속한 다른 Activity와 독립적으로 작동한다.
  • 쉽게 말해, singleTask와 동일하지만 활동은 항상 자체 작업의 단 하나의 유일한 멤버로 위에 다른 Activity를 쌓을 수 없다.

 

2. Intent Flag로 관리

startActivity()에 전달하는 인텐트 플래그에 아래와 같은 플래그를 주어 스택을 관리한다.

 

2-1) FLAG_ACTIVITY_NEW_TASK

  • singleTask와 유사하며 활동을 새 작업에서 시작한다.
  • 이미 실행 중인 작업이 있다면 그 작업을 마지막으로 포그라운드로 이동하고 새 Intent를 수신한다
  •  
val intent = Intent(this, SecondActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(intent)

2-2) FLAG_ACTIVITY_CLEAR_TOP

  • 호출하는 Activity 위에 있는 모든 Activity를 제거한다. 호출하는 Activity가 맨 위에 있지 않으면 새로운 인스턴스를 생성한다.
  • 새롭게 부른 Activity가 가장 Top에 있는 Activity면 새 인스턴스가 생성되는 대신 기존 인스턴스가 호출을 수신한다.
  • 예를 들어, 현재 애플리케이션의 모든 Activity를 제거하고 Main으로 다시 시작할 때 사용된다.
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP

2-3) FLAG_ACTIVITY_SINGLE_TOP

  • 호출하는 Activity가 이미 스택의 맨 위에 있는 경우 새로운 인스턴스를 시작하지 않고 기존 인스턴스를 재사용한다.
intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP

참고

 


 

 

Fragment 데이터 전달 방식

1) bundle과 FragmentManager로 전달 

전송하려는 Fragment

//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

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를 이용하여 전달

FragmentManager은 fragment의 결과의 중앙 저장소 역할을 하기 때문에 fragment가 서로 직접 참조할 필요없이 fragment간 통신을 할 수 있다.

 

프래그먼트와 통신  |  Android 개발자  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 프래그먼트와 통신 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 프래그먼트를 재사용하려면 자체

developer.android.com

공식문서를 통해 실습을 해보았다.

더보기

데이터를 보내는 SenderFragment와 받는 ReceiverFragment를 만들었다.

위에 사진처럼 한 Activity위에 2개의 Fragment를 띄운다음

SenderFragment에는 2개의 버튼을 넣고 선택한 버튼에 따라 ReceiverFragment의 TextView값을 바꾸는 예제이다.

//ReceiverFragment
		...
class ReceiverFragment : Fragment() {
    private lateinit var binding:FragmentReceiverBinding
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentReceiverBinding.inflate(inflater,container,false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        setFragmentResultListener("request") { key, bundle ->
            bundle.getString("senderKey")?.let{
                binding.receiveTextView.text = it
            }
        }
    }
}
//SenderFragment
		...
class SenderFragment : Fragment() {
    private lateinit var binding:FragmentSenderBinding
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentSenderBinding.inflate(inflater)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        with(binding){
            aButton.setOnClickListener {
//                val bundle = Bundle()
//                bundle.putString()
                val bundle = bundleOf("senderKey" to binding.aButton.text)
                setFragmentResult("request",bundle)
            }
            bButton.setOnClickListener {
                val bundle = bundleOf("senderKey" to binding.bButton.text)
                setFragmentResult("request",bundle)

            }
        }
    }
}

 

setFragmentResult, setFragmentResultListener을 사용해서 key값으로 bundle의 값을 전달

3) Fragment간 shared ViewModel로 전달

ViewModel을 사용하여 데이터를 공유하는 방식

  • ViewModel은 여러 fragmen간에 또는 fragment와 호스트 활동 간에 데이터를 공유해야 할 때 적합하다.
  • Fragment가 Activity 하위에 위치하기 때문에 사용할 수 있다.

Activity를 공유하는 여러 Fragment들은 Activity의 메모리를 공유할 수 있고 AAC의 ViewModel은 Activity의 lifecycle보다 오래 살아있는 것이 보장되기 때문에 안전하게 공통의 Activity의 ViewModel을 사용하여 데이터 전달이 가능하다.

출처

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

 

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

출처

Jetpack의 Navigation을 사용하는 방식이다.

navigation에서 화면전환에 필요한 Action과 data를 정의할 수 있고, defaultValue도 정의가 가능하다.

 

간결하고 안정적인 처리의 장점이 있지만 다른 Data전달 방식보다 환경세팅하는 것이 많고 알아야 할 기능들이 많다.

직접 실습을 진행해보았다.

 

초기 설정

더보기

1. 2개의 Fragment생성, activity_main.xml 설정

(bottomnavigation은 개인적)

 

2. navigation 생성

데이터 이동

더보기

1. gradle

앱 수준의 gradle

plugins{
	...
	id("androidx.navigation.safeargs.kotlin")
}

 프로젝트 수준의 gradle

plugins {
	...
    id("androidx.navigation.safeargs.kotlin") version "2.7.7" apply false
}

 

2. 초기에서 만든 navigation에서 action과 argument를 추가해준다.

<fragment
    android:id="@+id/AFragment"
    android:name="com.example.kotlin_navi_bottom.AFragment"
    android:label="fragment_a"
    tools:layout="@layout/fragment_a">
    <action //추가
        android:id="@+id/action_AFragment_to_BFragment"
        app:destination="@id/BFragment" />
</fragment>

<fragment
    android:id="@+id/BFragment"
    android:name="com.example.kotlin_navi_bottom.BFragment"
    android:label="fragment_b"
    tools:layout="@layout/fragment_b">
<argument //추가
    android:name="aData"
    android:defaultValue="AA"
    app:argType="string" />
</fragment>

 GUI로도 할 수도 있다.

 

3. MianActivity.kt

val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
binding.bottomNavigationView.setupWithNavController(navHostFragment.navController

 nav_graph와 연결해주는 코드로 공식문서에 나와있다.

 

4. 데이터 처리

나는 AFragment의 데이터를 BFragment로 보낼 것이다.

그러면 AFragment의 fragmentDirection은 AFragmentDirections가 된다.

class AFragment : Fragment() {
    private lateinit var binding: FragmentABinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentABinding.inflate(inflater, container, false)

        binding.aButton.setOnClickListener {
            val action = AFragmentDirections.actionAFragmentToBFragment(aData = "This is 'A'data")//
            findNavController().navigate(action)
        }
        return binding.root
    }
}

 navigation의 argument name을 aData로 지정했었는데 그 aData의 arguments "This is 'A'data"를 전달해 준다.

class BFragment : Fragment() {
    private lateinit var binding: FragmentBBinding
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentBBinding.inflate(inflater, container, false)
        val aData = BFragmentArgs.fromBundle(requireArguments()).aData
        binding.bTextView.text = aData
        return binding.root
    }
}

결과 : A_Fragment의 버튼을 누르면 B_Fragment의 text가 argument에서 설정한 이름인 aData를 통해서 "This is 'A'Data"라는 Data로 바뀌게 된다. 

전달 방식만 보려고 대충 만든 것이다.(내가 봐도 대충만든 것같아서...)

화면이 이동하는 이유는 action을 넣어줬기 때문이다.

 

 

 

 

 

ref

 

[Android] Fragment간 데이터 전달 방법들

Fragment간 데이터 전달에 관한 여러 방법들

velog.io

 

Jetpack Navigation - 5 (Safe Args)

개복치개발자 강의는 아래의 링크에서 확인할 수 있습니다. 개복치개발자 | Linktree uyalae@naver.com linktr.ee Navigation에서 Fragment끼리 데이터를 전달하는 방법에 대해 Bundle에 대해서 알아봤습니다. 그

philosopher-chan.tistory.com

 

 

프래그먼트와 통신  |  Android 개발자  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 프래그먼트와 통신 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 프래그먼트를 재사용하려면 자체

developer.android.com

 

 

대상 간 데이터 전달  |  Android 개발자  |  Android Developers

이 페이지는 Cloud Translation API를 통해 번역되었습니다. 대상 간 데이터 전달 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 탐색을 사용하면 대상 인수를 정

developer.android.com