Notice
Recent Posts
Recent Comments
Link
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
관리 메뉴

우당탕탕 Android

[Android] Activity, Activity Lifecycle에 대해 알아보자! 본문

Android

[Android] Activity, Activity Lifecycle에 대해 알아보자!

세심 2023. 7. 4. 14:17

이 글에서는 Activity와 Activity의 Lifecycle에 대해 알아본다.

 

들어가기 전

공부하는 것을 정리하고, 나중에 기억나지 않을 때 참고하기 위한 용도의 글이므로 100% 정확하지 않을 수 있습니다! 제가 이해한 것은 모두 적은 글이니 틀린 부분이 있다면 이야기해 주세요 (ง •̀ω•́)ง✧

# 공부하게 된 계기

이전 프로젝트에서 협업하던 팀원이 코드를 생명주기에 맞게 깔끔하게 작성하고자 했다. 두 사람의 지식 차이로 인해 일정 기간 동안 공부를 진행한 후, 서로에게 지식을 전달하기 위해 시간을 가지기로 했다. 그래서 Activity와 Fragment에 대해 다시 공부하게 되었고, 학습한 내용을 기록하고 공유하기 위해 티스토리에 글을 작성하게 되었다.

 

 

 


 

 

 Activity란?

Activity는 4대 컴포넌트 중 하나로 사용자와 상호작용을 하며 Ui를 제공하는 역할을 한다. 안드로이드 폰에서 특정 앱을 실행하면 보이는 화면은 대부분 Activity로 만들어졌다. 여러 Acitivity가 함께 작동하여 하나의 앱에서 짜임새 있는 사용자 환경을 구성하지만 각 Activity는 서로 독립되어 있다. 다라서 Intent를 통해 하나의 앱에서 다른 앱의 Activity를 시작할 수 있다.

개발자 관점으로 본다면 Activity를 두 가지로 볼 수 있다.

  1. 기능을 담당하는 kotlin(JAVA) 파일
  2. UI를 담당하는 xml 파일

하나의 kotlin(JAVA) 파일에 하나의 xml 파일이 연결되어 하나의 화면을 이룬다. 예를 들어, xml 파일 속에 Button이 있다면, 그 Button의 위치나 색 등 표면적인 것들을 xml 파일 안에서 수정한다. 하지만 그 Button을 눌렀을 때 어떤 동작을 원한다면, 그 파일에 해당하는 kotlin(JAVA) 파일에 기능을 추가한다.

시스템과 앱의 주요 상호 작용

사용자가 현재 관심을 가지고 있는 사항을 추적하여 Activity를 호스팅하는 프로세스를 시스템에서 계속 실행하도록 한다. 이전에 사용한 프로세스를 유지하는 데 더 높은 우선순위를 부여한다. 앱이 프로세스를 종료하도록 도와서 이전에 상태가 복원되는 동시에 사용자가 Activity로 돌아갈 수 있게 한다. 앱이 서로 플로우를 구현하고 시스템이 이러한 플로우를 조정하기 위한 수단을 제공한다.

Activity  특징

  • 2개 이상의 Activity를 동시에 보여줄 수 없다.
  • 1개 이상의 View 혹은 ViewGroup을 포함한다.
  • 앱에는 반드시 하나 이상의 Activity가 있어야 한다.
  • Activity 내에 Fragment를 추가하여 화면을 분할시킬 수 있다.

Activity 설계

하나의 앱에서 모든 화면 당 하나의 Activity로 관리하게 되면, apk 크기도 커지고 화면 이동 간 시간적인 소요도 더 오래 걸린다. 그래서 너무 불필요하게 많이 만들지 않는 것이 권장되며, UI 액션이 많으면 Activity 사용을 고려하고 극 아니라면 Fragment로 대체하는 것이 좋다.

xml 연결 및 manifest에 Activity 추가

Activity를 생성하는 방법은 총 2가지가 있다.

  1. 안드로이드 스튜디오에서 새로운 Activity를 추가하는 방법
    • 클래스 파일과 xml 파일 연결, Manifest 추가 모두 알아서 생성해준다.
  2. 직접 클래스 파일과 xml 파일을 만들어서 추가하는 방법
    • 직접 생성한다면 이 파일들을 연결해 주고 Manifest에 Activity를 추가해줘야 한다.

➡ 코드를 구성하다 보면 파일이 서로 꼬이기도 하고, 어쩔 땐 툴에서 Activity를 생성했음에도 Manifest 파일에 추가가 안 돼서 헤맬 수 있다. 과정을 알아두면 문제가 생겼을 때 대처가 가능하니 알아보고자 한다.

 

클래스 파일과 xml 파일을 생성했다면, 클래스 파일과 xml 파일을 연결해줘야 한다. MainActivity.kt와 activity_main.xml 파일을 연결하려면 MainActivity.kt의 onCreate 메소드 안에 setContentView(R.layout.activity_main) 코드를 넣어주면 된다.

//클래스 파일에 activity_main.xml 파일에 연결
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
}

클래스 파일과 xml 파일을 연결했다면, Activity를 사용할 수 있도록 manifest.xml 파일에 Activity 정보를 추가해줘야 한다. 

아래 코드는 MainActivity 하나만 등록되어 있지만, 앱을 만들면서 Activity를 추가하면 manifest.xml에도 Activity를 추가해야 한다. InteliJ나 안드로이드 스튜디오에서 Activity를 추가하면 Manifest 파일에 자동으로 등록된다. 하지만 클래스에 직접 Activity를 상속받고 xml을 연결했다면 직접 Manifest 파일에 동록 해줘야 한다.

<manifest ... >
    <application ... >
        <activity android:name="com.example.myapp.MainActivity" ... >
        </activity>
    </application>
</manifest>
<!-- 새로운 Activity 추가한 경우 -->
<manifest ... >
    <application ... >
        <activity android:name="com.example.myapp.MainActivity" ... >
        </activity>
				<activity android:name="com.example.myapp.SubActivity"/>
    </application>
</manifest>

Intent filter(인텐트 필터)를 설정해 준다. 이것은 어떤 Intent를 받을지 '필터' 역할을 한다. Intent Filter에는 action, category, data 속성으로 구성되어 있는데 이 속성들로 어떤 데이터를 전송하고, 어떤 데이터를 보낼지 등을 선택할 수 있다. 자동으로 생성하면 MainActivity에 intent filter가 적용되어 있고, <action> 속성에 ACTION_MAIN이 적용되어 있다. ACTION_MAIN은 앱을 시작할 때 시작할 Activity를 지정하는 액션이다.

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>

 

 

Activity Lifecycle (생명주기)


1. 처음 실행될 때 onCreate() 메소드가 호출된다.

2. 이후에는 onStart, onResume() 메소드를 거치며 실행 상태가 된다.

3. 사용자가 Activity를 다른 Activity로 전환시킬 때 onPause()가 호출된다.

4. onStop()이 호출되어 중지 상태로 바뀌게 된다.

5. 이후 Activity가 소멸되는 경우에는 onDestory()가 호출되어 Activity가 메모리에서 사라진다.

1. onCreate()

View 생성, 초기화 담당

Activity의 생명주기 동안 onCreate() 메소드는 여러 기능을 수행하는 중요한 콜백 메소드이다. 이 메소드는 Activity가 생성될 때 호출되어 해당 Activity의 모든 요소를 초기화한다. 또한, UI 레이아웃을 코드로 동작하게 만들기 위해 setContentView() 메소드를 이 메소드 내에서 사용하여 정의한다. 이 메소드는 시스템이 Activity를 생성할 때 단 한 번 호출되는 콜백 메소드로서, Activity의 시작 로직을 포함한다. 주로 ViewBinding이나 DataBinding을 사용하여 UI를 구성하는 레이아웃에 연결하고, ViewModel과의 연결 또는 클래스 인스턴스를 생성하는 등의 작업을 수행한다. ViewBinding이나 DataBinding을 사용하지 않는다면 setContentView()를 호출하여 UI를 설정해야 하며, 이 메소드는 onCreate() 콜백에 종속적이다. 또한, 이 메소드는 savedInstanceState라는 Bundle 타입의 매개변수를 받아 이전 Activity의 상태를 저장하고 생성 시에 해당 상태를 설정하기 위한 변수이다. 처음 Activity를 실행했다면 이 매개변수는 NULL 값을 갖는다.

lateinit var textView: TextView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_activity)

    textView = findViewById(R.id.text_view)
}

Activity는 생성됨 상태에 머무르지 않는다. onCreate() 메소드가 실행을 완료하면 시작됨 상태가 되고, 시스템이 연달아 onStart()와 onResume() 메소드를 호출한다.

 

2. onStart()

화면 표시되기 전 단계

Activity가 화면에 보이기 직전에 호출되는 메소드는 onStart()이다. onCreate() 이후 시작됨 상태가 되고, 시스템은 연달아 onStart()와 onResume() 콜백을 호출한다. onStart()는 두 가지 경우로 실행되는데, 처음 생성 이후 호출될 때와 onRestart() 콜백을 수신한 후 재시작될 때 호출된다. onStart()에서는 Activity가 사용자에게 표시되고, Activity를 foreground로 보내 상호작용할 수 있도록 준비된다. 예를 들어, 이 메소드에서는 앱이 UI를 관리하는 코드를 초기화할 수 있다. 활동이 시작됨 상태로 전환하면 이 활동과 관련된 모든 수명 주기 인식 구성요소는 ON_START 이벤트를 수신한다. onStart() 메소드는 매우 빠르게 완료되며, 생성됨 상태와 마찬가지로 시작됨 상태에 머무르지 않는다. 이 콜백이 완료되면 활동이 재개됨 상태로 전환되고, 시스템은 onResume() 메소드를 호출한다.

 

3. onResume()

화면에 표시되는 단계로, 화면 갱신 등 화면 처리 담당

사용자로부터 어떠한 동작도 받지 않았을 때 호출되며, Activity가 화면에 보이게 되는 메소드는 onResume()이다. 활동이 재개됨 상태에 들어가면 foreground에 표시되고, 시스템은 onResume() 콜백을 호출한다. 재개됨 상태에 들어갈 때 앱이 사용자와 상호작용한다. 실제로 setContentView() 결과가 보이는 부분이다. 어떤 이벤트가 발생하여 앱에서 포커스가 떠날 때까지 앱이 이 상태에 머무른다. 예를 들어 전화가 오거나, 사용자가 다른 활동으로 이동하거나, 기기 화면이 꺼지는 이벤트가 해당한다. 활동이 재개됨 상태로 전환되면 이 활동과 연결된 모든 수명 주기 인식 구성요소는 ON_RESUME 이벤트를 수신한다. 이 상태에서 수명 주기 구성요소는 foreground에서 사용자에게 보이는 동안 실행해야 하는 모든 기능을 활성화할 수 있다. 예를 들면 카메라 미리 보기를 시작하는 것이다. 방해되는 이벤트가 발생하면 활동은 일시중지됨 상태로 전환되고, 시스템은 onPause() 콜백을 호출한다. 활동이 일시중지됨 상태에서 재개됨 상태로 돌아오면 시스템은 onResume() 메소드를 다시 한번 호출한다. 따라서 onResume()을 구현하여 onPause() 중에 해제한 구성요소를 초기화하고, 활동이 재개됨 상태로 전환될 때 필요한 다른 초기화 작업도 수행해야 한다. onResume()은 onPause()에서 해제한 리소스를 다시 초기화하는 코드를 포함해야 한다. 즉, onResume()과 onPause()는 대칭적으로 리소스와 데이터의 초기화와 해제를 수행해야 한다.

 

구성요소가 ON_RWSUME 이벤트를 수신할 때 카메라에 엑세스 하는 수명 주기 인식 구성 요소의 예시는 다음과 같다

➡ 공식 문서에 카메라 관련해서 onResume()을 설명하는 거 같은데 나중에 실습 후 정리하도록 하자.

 

4. onPause()

Activity가 foreground에서 background로 전환되는 시점인 경우, 화면의 일부가 가려져 있는 상태일 때 onPause() 콜백이 호출된다. 이벤트가 발생하여 새로운 Activity가 foreground로 나오기 전까지는 onPause() 상태에 머물다가, 모든 화면이 가려지는 순간 onStop() 콜백이 호출된다. 또한, Dialog Activity나 투명 Activity가 화면 위에 뜨는 경우에도 onPause() 상태에 머물게 된다. onPause() 메소드를 사용하여 Activity가 일시중지된 상태일 때 지속적으로 실행되거나 적절하게 조정해야 할 작업을 일시중지하게 된다. onPause()는 매우 짧은 시간 동안 실행되므로 현재 화면을 구성하는 데이터를 저장하거나 네트워크 호출 또는 DB 트랜잭션과 같은 시간이 소요되는 작업을 실행해서는 안된다. 이러한 부하가 큰 작업들은 주로 onStop()에서 처리해야 한다.

활동이 이 상태에 들어가는 이유에는 여러 가지가 있다.

  1. onResume() 섹션에서 설명하였듯이, 일부 이벤트가 앱 실행을 방해합니다. 이것이 가장 일반적인 사례입니다.
  2. Android 7.0(API 수준 24) 이상에서는 여러 앱이 멀티 윈도우 모드에서 실행됩니다. 언제든지 그중 하나의 앱(창)만 포커스를 가질 수 있기 때문에 시스템이 그 외에 모든 다른 앱을 일시중지시킵니다.
  3. 새로운 반투명 활동(예: 대화상자)이 열립니다. 활동이 여전히 부분적으로 보이지만 포커스 상태가 아닌 경우에는 일시중지됨 상태로 유지됩니다.

활동이 일시중지됨 상태로 전환되면 이 활동과 연결된 모든 수명 주기 인식 구성요소는 ON_PAUSE 이벤트를 수신합니다. 이때, 수명 주기 구성 요소는 foreground에 있지 않을 때 실행할 필요가 없는 기능들을 모두 정지할 수 있습니다. 예를 들어, 카메라 미리 보기를 정지하는 작업 등이 이에 해당합니다. 또한, onPause() 메소드를 사용하여 시스템 리소스, 센서 핸들 (예: GPS) 또는 활동이 일시중지되어 사용자가 필요로 하지 않는 리소스를 해제할 수도 있습니다. 이를 통해 배터리 수명에 영향을 미칠 수 있는 리소스들을 관리할 수 있습니다.

 

ON_PAUSE의 예시로, 위의 ON_RESUME이벤트에 대응하며 ON_RESUME 이벤트가 수신된 후 초기화된 카메라를 해제한다.

➡ 공식 문서에 카메라 관련해서 onPause()을 설명하는 거 같은데 나중에 실습 후 정리하도록 하자.

 

5. onStop()

통신이나 센서 처리 정지 담당

Activity가 이벤트로 인해 새로운 화면이 나타나서 전체 화면을 가리게 되거나, 사용자에게 더 이상 표시되지 않으면, 중단됨 상태에 들어가고 시스템은 onStop()을 호출한다. 이는 예를 들어 새로 시작된 활동이 화면 전체를 차지할 경우에 적용된다. 시스템은 활동의 실행이 완료되어 종료될 시점에 onStop()을 호출할 수도 있다. onStop()에서는 Activity에서 이제 사용되지 않을 리소스들을 해제하거나 현재 상태 정보를 저장하기 위한 DB 작업들을 진행해야 한다. onStop()의 중요한 점은 Activity 객체는 아직 메모리 안에 머무리게 된다. Activity가 다시 재개되면 이 정보들을 다시 호출할 수 있다. 재개 시 onCreate에서 생성한 구성 요소는 다시 초기화할 필요 없고 레이아웃에 있는 각 View 객체도 현재 상태를 기록하기에 저장 및 복원할 필요 없다. 활동이 중단됨 상태로 전환하면 이 활동의 수명 주기와 연결된 모든 수명 주기 인식 구성요소는 ON_STOP 이벤트를 수신한다. 여기에서 수명 주기 구성요소는 구성 요소가 화면에 보이지 않을 때 실행할 필요가 없는 기능을 모두 정지할 수 있다. onStop() 메소드에서는 앱이 사용자에게 보이지 않는 동안 앱은 필요하지 않은 리소스를 해제하거나 조정해야 한다. 예를 들어 앱은 애니메이션을 일시중지하거나, 세밀한 위치 업데이트에서 대략적인 위치 업데이트를 전환할 수 있다. onPause() 대신 onStop()을 사용하면 사용자가 멀티 윈도우 모드에서 활동을 보고 있더라도 UI 관련 작업이 계속 진행된다. 또한 onStop()을 사용하여 CPU를 비교적 많이 소모하는 종료 작업을 실행해야 한다. 예를 들어 정보를 데이터베이스에 저장할 적절한 시기를 찾지 못했다면 onStop() 상태일 때 저장할 수 있다.

 

다음 예시는 초안 내용을 영구 저장소에 저장하는 onStop()을 구현한 것이다.

➡ 공식 문서에 코드 사용해서 설명하는 거 같은데 나중에 실습 후 정리하도록 하자.

 

# onPause()와 onStop()의 차이는?

  • onPause()는 forground에서 background로 전환되는 시점으로 화면의 일부가 가려진 상태에 호출된다.
  • onStop()은 완전히 화면이 가려지면 호출된다.

 

6. onDestory()

View 폐기 및 리소스 해제

Activity가 완전히 소멸되기 전에 onDestroy() 콜백이 호출된다. onDestory()가 발생하는 경우는 2가지이다.

  1. (사용자가 Activity를 완전히 닫거나 활동에서 `finish()`가 호출되어) Activity가 종료되는 경우
  2. 구성 변경(ex. 기기 회전 또는 멀티 윈도우 모드)으로 인해 시스템이 일시적으로 활동을 소멸시키는 경우
  3. 그 외에도 시스템에 의한 Activity가 제거가 될 수도 있고, 이 경우에서도 `onDestory()`까지 호출이 된다.

자세히 설명하자면, 여러 태스크를 사용하는 앱에서 메모리를 많이 사용할 때 발생할 수 있다. 이 경우 OutOfMemoryError가 발생하지만, 여러 태스크를 사용한다면 OutOfMemoryError가 발생하기 전에 메모리를 줄일 수 있는 방법으로 background 태스크의 Activity를 종료시키게 된다. Activity가 소멸됨 상태로 전환하면 이 Activity의 수명 주기와 연결된 모든 수명 주기 인식 구성요소는 ON_DESTROY 이벤트를 수신한다. 여기에서 수명 주기 구성요소는 활동이 소멸되기 전에 필요한 정리 작업을 수행할 수 있다. ViewModel 객체를 사용하여 관련 View 데이터를 포함하여 활동의 상태를 유지해야 한다. ViewModel은 구성 변경으로 인해 Activity가 다시 생성될 때 그대로 유지되므로 추가 작업이 필요하지 않다. 하지만 Activity가 다시 생성되지 않을 경우 ViewModel은 onCleared() 메소드를 호출하여 활동이 소멸되기 전에 모든 데이터를 정리해야 한다. 이러한 두 가지 시나리오는 isFinishing() 메소드를 사용하여 구분할 수 있다. Activity가 종료되는 경우, onDestroy()는 활동이 수신하는 마지막 수명 주기 콜백이 된다. 구성 변경으로 인해 onDestroy()가 호출되는 경우, 시스템은 즉시 새로운 Activity 인스턴스를 생성하고, 새로운 구성에서 해당 인스턴스의 onCreate()를 호출한다. onDestroy() 콜백은 이전 콜백에서 해제되지 않은 모든 리소스 (예: onStop()에서 정리되지 않은 것)를 해제해야 한다.

 

생명주기 메소드 호출 순서

  1. 시작할 때 : onCreate() → onStart() → onResume()
  2. 화면 회전할 때 : onPause() → onStop() → onDestory() → onCreate() → onStart() → onResume()
  3. 홈 버튼 클릭 시 : onPause() → onStop()
  4. 홈 이동 후 다시 돌아올 때 : onStart() → onResume()
  5. 백 버튼 클릭하여 Activity 종료 시 : onPause() → onStop() → onDestory()

Activity 전환 시 생명주기 메소드 호출

Activity A에서 Activity B를 시작할 때 메소드들이 어떻게 호출되는지 알아보자!!

전환되면서 A에서 onStop()을 호출하고 다시 B에서 onCreate()로 시작되는 줄 알았는데 그게 아니라고 하더라!

  1. Activity A에서 onPause() 메소드 호출 (Activity A, background로 이동)
  2. Activity B는 onCreate(), onStart(), onResume() 메소드를 실행하여 foreground 상태가 된다.
  3. Activity B가 onResume()까지 호출된 이후에는 foreground 상태가 되고, 전체 화면을 가득 채우기 때문에 Activity A에서는 onStop() 메소드가 호출된다.

이런 걸 알아두면, 두 Activity에서 공통적으로 사용되는 클래스나 DB를 사용할 때 문제를 예방할 수 있다고 생각한다.

Activity A가 onPause()가 아닌 onStop()에서 클래스, DB를 저장할 시 Activity B에서는 저장되지 않은 상태에서 데이터를 가져오게 되고 그 이후에 저장되기 때문에 다른 값들을 사용하게 된다. 따라서 Activity 전환 시에 생명주기에 잘 알고 사용하도록 해야 한다.