깨알 개념/Android

[Android] Activity의 생명주기

interfacer_han 2024. 1. 23. 14:04

#1 Android Application의 시작 (AndroidManifext.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application ...>
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        ...
    </application>

</manifest>

우리가 앱 아이콘을 누르면, 안드로이드 시스템은 해당 앱의 Launcher Activity를 실행시킨다. AndroidManifext.xml의 등록된 액티비티 중에서 <category android:name="android.intent.category.LAUNCHER" /> 태그를 가지고 있는 Activity가 Launcher Activity로 설정된다. 프로젝트를 생성하면 기본적으로 존재하는 Launcher Activity가 바로 우리가 잘 아는 MainActivity다.
 
이 Launcher Activity라는 진입점을 통해 앱이 실행되면, 비로소 액티비티의 생명주기가 시작된다.
 

#2 Activity LifeCycle

#2-1 생명주기 흐름도

https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ko

'Activity launched'가 #1에서 말한 앱의 진입점이다. 진입점 이후에는 여러 상태(State)가 있으며, Activity를 이 State들을 이리저리 돌아다닌다. 그리고 상태마다 실행되는 함수들은 콜백(Callback) 함수다. Android 시스템에 의해 자동으로 호출되기 때문이다. 개발자가 직접적으로 onCreate() 등을 호출하지 않으며, 해당 함수를 오버라이드해서 설계만 해둔다. 그리고 안드로이드 시스템은 앱의 생명주기에 따라 필요한 시점에 개발자에 의해 설게된 함수를 호출한다.
 

#2-2 onCreate()

생명주기에서 반드시 단 한번만 실행되는 함수다. 그리고 Activity의 근간을 짜는 함수이기 때문에, 다른 함수들과 달리 오버라이드가 강제된다. 안드로이드 스튜디오에서 빈 프로젝트를 만들면 있는 MainActivity에 onCreate()가 이미 들어가 있는 것은 그것이 강제 사항이기 때문이다.
 

#2-3 onStart()

액티비티가 사용자에게 보이기 직전에 호출된다. 이 상태(State)에서 Activity는 스마트폰의 '화면(Foreground)'에 띄워질 것을 대비해 스마트폰 사용자와 상호작용할 준비를 한다. UI가 만들어지는 것이 바로 이 단계이다.
 

#2-4 onResume()

Activity가 실행 중인 상태다. 즉, 앱의 화면이 사용자의 눈에 보이기 시작하는 순간이다. 이 상태를 'Activity running'이라고 한다. onResume()은 onStart() 혹은 onPause()의 다음 상태다. Activity가 실행 중일 때 호출되는 함수이므로, 많은 신경을 써주어야 한다. 예를 들면, onPause() 다음으로 실행될 때를 대비해 서버와의 (재)연결하는 코드를 써 넣는 식이다.
 

#2-5 onPause()

일시중지 상태다. 사용자가 홈버튼을 눌렀거나, 혹은 갑자기 전화가 와서 전화 화면으로 전환되는 등 '화면 밖(Background)'으로 Activity가 튕겨져나가면 onPause()가 실행된다. onPause() 상태에선 안드로이드 앱이 Background에 있을 때 실행되어선 안 되는 동작을 막아야 한다. 사용자가 어떤 Activity를 화면 밖으로 치우고 다른 일을 하고 있음에도, 해당 Activity가 예를 들어, 서버와 통신을 한다면 많은 자원이 낭비될 것이다.
 
onPause()일 때, 다시 Activity를 '화면(Foreground)'로 불러들이면 onResume()이 실행된다. 사용자가 홈화면에서 다시 앱을 실행시키거나 전화가 끝나서 스마트폰 화면이 원래 하던 앱으로 자동 전환된 경우다.
 

#2-6 onStop()

onPause() 상태에서 앱이 장기간 onResume()되지 않으면 안드로이드 시스템은 해당 Activity를 onStop() 상태로 보낸다. 이 경우, Activity의 인스턴스는 여전히 메모리에 존재하지만 UI 화면 구성은 파괴된다.
 

#2-7 onRestart()

onStop() 상태일때 사용자가 Activity를 '화면 안(Foreground)'으로 끌어다놓으면 실행된다. UI는 이미 소실된 상태이므로, onRestart()와 onStart()를 거쳐 UI를 다시 만들고, onResume()에서 다시 사용자에게 해당 UI를 보여준다.
 

#2-8 onDestroy()

'앱 종료하기' 버튼을 만드는 등 프로그래머가 의도적으로 Activity를 파괴하는 명시적 코드를 짰거나, 화면 회전(해당 링크의 #1-2 참조)이나 언어 변경과 같은 환경의 변화(Configuration Change)가 생기면 호출되는 상태다. 후자의 경우에는 안드로이드 시스템은 곧바로 onCreate() 메소드를 호출한다. 
 

#3 액티비티 생명주기 살펴보기용 앱

#3-1 MainActivity.kt

// package com.example.activitylifecyclelogger

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        Log.i("interfacer_han", "MainActivity.onCreate()")
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val switchActivityButton = findViewById<Button>(R.id.switchActivityButton)
        switchActivityButton.setOnClickListener {
            val intent = Intent(this, SecondActivity::class.java)
            /* FLAG_ACTIVITY_REORDER_TO_FRONT 플래그는 호출하려는 액티비티가 이미 스택에 존재하는 경우,
             * 해당 액티비티를 스택의 맨 위로 이동시키고 기존 인스턴스를 재사용하도록 함.
             * 이 플래그가 없으면, Activity를 전환할 때마다 onCreate()됨.
             */
            intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
            startActivity(intent)
        }
    }

    override fun onStart() {
        Log.i("interfacer_han", "MainActivity.onStart()")
        super.onStart()
    }

    override fun onResume() {
        Log.i("interfacer_han", "MainActivity.onResume()")
        super.onResume()
    }

    override fun onPause() {
        Log.i("interfacer_han", "MainActivity.onPause()")
        super.onPause()
    }

    override fun onStop() {
        Log.i("interfacer_han", "MainActivity.onStop()")
        super.onStop()
    }

    override fun onRestart() {
        Log.i("interfacer_han", "MainActivity.onRestart()")
        super.onRestart()
    }

    override fun onDestroy() {
        Log.i("interfacer_han", "MainActivity.onDestroy()")
        super.onDestroy()
    }
}

기본적으로 존재하는 onCreate() 외의 다른 State에 해당하는 메소드들을 전부 오버라이드하고 Log를 통해 살펴본다.
 

#3-2 SecondActivity.kt

#3-1과 거의 같음 (생략)

MainActivity에서 전환될 액티비티다. 해당 액티비티에서도 마찬가지로 MainActivity로 갈 수 있다.
 

#3-3 로그 확인 - 액티비티 전환

1. 시작 화면 (MainActivity)
     MainActivity.onCreate()
     MainActivity.onStart()
     MainActivity.onResume()

2. SecondActivity로 전환
     MainActivity.onPause()
     SecondActivity.onCreate()
     SecondActivity.onStart()
     SecondActivity.onResume()
     MainActivity.onStop()
 
3. 다시 MainActivity로 전환
     SecondActivity.onPause()
     MainActivity.onRestart()
     MainActivity.onStart()
     MainActivity.onResume()
     SecondActivity.onStop()
 

#3-4 로그 확인 - 액티비티 회전

1. 시작 화면 (MainActivity)
    MainActivity.onCreate()
    MainActivity.onStart()
    MainActivity.onResume()
 
2. 시작 화면에서 90도 회전
     MainActivity.onPause()
     MainActivity.onStop()
     MainActivity.onDestroy()
     MainActivity.onCreate()
     MainActivity.onStart()
     MainActivity.onResume()
 
3. (2)에서 180도 회전
로그 발생되지 않음. 가로와 세로의 너비가 변하지 않는 회전이기 때문에 환경 변화는 없다고 볼 수 있으며, 따라서 onDestroy()도 실행되지 않는다.
 

#4 요약

액티비티 생명주기는 안드로이드 시스템의 헌법이다.
 

#5 완성된 앱

https://github.com/Kanmanemone/android-practice/tree/master/lifecycle/ActivityLifecycleLogger