[Android] Activity 살펴보기 : Activity 의 이해

13 분 소요

Activity 의 이해

우리가 안드로이드 앱을 개발하기 위해서는 흔히 4대 component 라고 불리는 Activity, Service, Broadcast Receiver, Contents Provider 가 필요합니다. 그 중에서 특히 Activity 를 제외한 나머지 컴포넌트들은 요구사항에 따라 추가하면 되지만 Activity 는 반드시 필요한 component 입니다.

Activity 는 android.app 에 있으며, ContextThemeWrapper 를 상속하고 여러 기능을 담당하기 위해 인터페이스들을 구현하고 있습니다. 그중에서 눈에 띄는 녀석은 Window 와 관련된 인터페이스들 입니다.

View #1 Android Ui 에도 한번 언급했듯이, Activity 는 Window 를 제공하고 있습니다. Window 는 앱의 도화지 역할을 하며, 개발자가 정의한 View 를 보여주기 위해 반드시 필요한 클래스입니다.

그 밖에도 사용자와 앱 간의 상호작용을 위한 여러 기능들을 제공해주고 있습니다. 안드로이드 공식 문서 에서 중요한 몇가지 기능들을 소개하고 있습니다. 그중에서 대표적인 몇가지를 보자면,

  1. Activity Lifecycle
  2. Task 와 Backstacks
  3. 상태 관리 솔루션
  4. 화면 또는 권한 요청과 결과 응답
  5. 프로세스 수명주기

주요 기능들을 먼저 살펴본뒤에, 내부코드를 보면서 이해를 해보는 순서로 가져가보겠습니다.

Activity Lifecycle

Activity 가 실행되는 진입점은 두가지로 분류됩니다.

첫번째는 app/manifest.xml 파일이 packageManager 에 의해 parsing 되고 난 뒤 ‘앱 런처’ 에 의해 디바이스에 설치된 앱 들이 노출되게 됩니다. 설치된 앱을 사용자가 앱 런처에서 실행(launch)하면, manifest.xml 에서 intent-filter 로 action.MAIN 과 category.LAUNCHER 로 정의된 Launcher Activity 가 ActivityThreadlaunch_activity 이벤트로 dispatch 되면서 앱이 실행됩니다. 한 가지 더 생각해보자면, Launcher Activity 를 외부(런처앱)에서 실행 할 수 있어야 하기 때문에 manifest.xml 에서 Activity 태그에 exported = true 를 설정해둔다는 점입니다. 이것을 false 로 하면 앱 외부에서 실행할 수 없습니다.

두번째로, 실행된 앱 프로세스 내에서 화면 요청 과정인 ContextWrapper#startActivity() 를 실행하여 Instrumentation#execStartActivity() 에 의해 activity thread 로 dispatch 되면서 Activity 가 launch 될 수 있습니다.

두가지 방식 모두 ActivityThread 에 의해서 launch 과정이 수행되고 궁극적으로는 Instrumentation 에 의해서 인스턴스화 한 뒤, 순서대로 정의된 lifecycle callback method 를 실행하게 됩니다.

activity_lifecycle

lifecycle callback method 들을 하나씩 살펴보겠습니다.

Callbacks

onCreate()

Activity 생성 이후 가장 먼저 호출되는 콜백입니다. Activity 가 인스턴스화 된 후 부터 단 한번만 실행 되므로, 필요한 인스턴스들의 초기화와 같은 작업들을 이곳에서 수행해야 합니다.

파라미터로 전달되는 savedInstanceState bundle 객체는 상태 복원의 용도로 제공됩니다. 해당 값은 nullable 하며 복원할 상태가 없다면 null 이 전달됩니다.

중요한점은, 개발자가 정의한 뷰를 이곳에서 인스턴스화 한 뒤 Activity의 window 인스턴스에 존재하는 mContentparent 로 할당하는 뷰 연결 작업 을 수행해주어야 합니다. 이것을 수행하는 메소드는 xml 방식의 경우 Activity#setContentView() 이며, compose 방식의 경우 ComponentActivity#setContent() 입니다. 해당 내용은 View #1 Android Ui 포스팅에서 이미 다룬 내용이기도 합니다.

마지막으로 onCreate() 호출 이전에 Activity 가 인스턴스화 된 경우 Lifecycle State 는 INITALIZED 이고, onCreate() 가 호출된 후에 시스템은 ON_CREATE 이벤트를 생성하게 되고 Lifecycle State 는 CREATED 가 됩니다. 만약, Lifecycle Event Observer 를 등록하고 있다면 이 엄격한 순서를 명확하게 인지해야 합니다. 그렇지 않고 사용하면, 특정 사용 사례에 미묘한 버그가 발생할 수도 있습니다.

onStart()

onCreate() 가 호출된 직후 onStart() 가 실행됩니다. onStart()가 호출되면서 Activity 가 화면에 보여지기 시작합니다. 따라서 실행중인 앱의 프로세스 상태는 Visible 이 됩니다.

onStart() 호출 후 시스템은 ON_START 이벤트를 호출하게 되고 Lifecycle State 는 STARTED 가 됩니다.

onRestoreInstanceState()

onRestoreInstanceState(Bundle) 는 저장된 상태가 있는 경우 복원할 목적으로 실행되는 콜백입니다. 매개변수로 전달받는 bundle 객체는 non-null 하며, 저장한 상태가 없어서, 상태를 복원할 필요가 없다면 onRestoreInstanceState() 는 실행되지 않습니다.

개발자에 의해 정의된 상태들을 복원할 수도 있으며, 시스템에 의해 관리되는 backstack 이나 EditText 의 text 값, scrollView 의 scroll 상태, viewtree 정보와 같은 상태값들도 자동으로 저장 및 복원됩니다. 또한 이런 경우, xml 에서는 android:id 태그 값이 할당되어 있어야 자동으로 저장 및 복원될 수 있습니다.

onResume()

onResume()이 호출되면, Activity#Window 의 mContentParent 에 연결된 화면이 보여집니다. 화면이 사용자에게 보여진다는 것은 다른말로 focused(포커스를 받고 있음) 라고 얘기하며, Focused Activity 는 backstack 의 top 에 있으며, 프로세스 상태가 Foreground 가 됩니다. 이 상태가 되어서야 사용자가 화면 내에서 앱과 상호작용 할 수 있습니다.

onResume() 호출 후 시스템은 ON_RESUME 이벤트를 호출하게 되고, Lifecycle State 는 RESUMED 가 됩니다.

onPause()

onPause()onResume() 에 대응되며, Activity 의 window 가 일부 가려졌을 때 호출됩니다. 즉, Dialog 와 같은 다른 window 가 열려서, Activity 의 window가 가려지고, 불투명으로 흐려졌을 때 호출됩니다. 또한, 동시에 Activity 는 focus 를 잃게 되며, 프로세스 상태는 Visible 이 됩니다.

아직은 Activity 의 window 가 일부 보여지고 있으므로, UI 의 update 가 가능합니다. 또한, 해당 callback 은 빠르게 지나가기 때문에 DB 트랜잭션이나 네트워크 호출과 같은 비동기로 처리해야하는 작업들을 이곳에서 처리하면 안됩니다.

onPause() 콜백부터는 onCreate() ~ onResume() 과 대응되는 즉, 반전 형태가 됩니다. onPause() 호출 전 시스템은 ON_PAUSE 이벤트를 먼저 호출하고, Lifecycle State 는 STARTED 가 됩니다. 이후 onPause() 콜백을 실행합니다. onPause() 호출 이후 Activity 가 재개되는 경우 onResume() 콜백을 호출합니다.

onStop()

onStop()onStart() 와 대응되며, Activity 의 window 가 더이상 보이고 있지 않다는 것을 의미합니다. 보통 시스템의 home 버튼이나 back 버튼으로 Activity 에서 이탈하는 경우 발생하며, configuration change 에서도 발생합니다.

아직 Activity 의 인스턴스가 메모리에는 남아있지만, windowManager 와의 연결은 제거됩니다. 보통 해당 callback 에서 실행중인 작업을 종료 및 리소스를 제거 해야 합니다. 예를들면, 더 이상 화면이 보여지지 않기 때문에 보통 ViewModel 에서 가지는 UI 관련 상태들의 수집 또는 비동기 작업을 이 콜백에서 종료 해야 합니다.

home 버튼으로 앱에서 이탈 시, App 프로세스 상태는 background 가 됩니다.(주의할 점은 Activity 전환의 경우 프로세스 상태는 background 로 바뀌지 않는 다는 것 입니다.) 이러한 경우 후술할 예정이지만 시스템이 메모리 부족으로 인해 언제든지 프로세스를 종료할 수 있으며, 앱 프로세스가 메모리에서 제거될 수 있습니다. 따라서, 사용자 경험을 보완하기 위해 다른 포스팅에서 서술한 바와 같이 상태 관리 #2 SavedState 메커니즘을 사용해야 합니다. 또한 backstack 역시 마찬가지로 해당 매커니즘으로 관리되어 메모리 부족으로 인해 시스템이 프로세스를 종료시켰더라도 최근 화면에서 다시 앱을 실행하면 이전 진행 흐름(Activity backstack)을 유지할 수 있습니다. 아래 Lifecycle Scenario 에서 더 자세히 다루겠습니다.

onStop() 호출 전 시스템은 ON_START 이벤트를 먼저 호출하고, Lifecycle State 는 CREATED 가 되며, 이후 onStop() 이 실행됩니다. 또한, onStop() 이후 다시 Activity 가 재개되는 경우 onRestart() - onStart() - onResume() 콜백을 이어서 호출합니다.

onSaveInstanceState()

상태 복원은 시스템에 의해 프로세스가 메모리에서 제거되는 경우 사용자 경험을 보완하기 위해 이전까지의 실행 흐름을 저장 및 복원하여 재개 시점부터 이어나갈 수 있도록 도와주기 위해 제공되는 매커니즘 입니다. 그중 onSaveInstanceState() 는 상태를 저장하기 위한 콜백으로 반드시 호출되는 콜백은 아닙니다.

onSaveInstanceState() 는 사용자가 명시적으로 Activity 를 이탈하는 경우에 대해서는 호출되지 않습니다.(자세히는 onFinish() 를 호출한 경우) 앱이 정상적으로 종료된 경우 상태를 저장 및 복원할 필요가 없기 때문입니다. 즉, onSaveInstanceState() 는 다른 화면으로의 전환이나 시스템의 home 버튼에 의해 잠시 Activity 를 이탈한 경우, 마지막으로 configuration change 가 발생한 경우 onStop() 이후에 호출됩니다.

매개변수로 전달받는 bundle 객체에 상태를 저장해주면, 이에 대응되는 콜백인 onRestoreInstanceState(Bundle)onCreate(Bundle) 콜백에 매개변수로 전달받는 bundle 객체로 복원할 수 있습니다.

onDestroy()

onDestroy()onCreate() 와 대응되며, Activity 가 더 이상 유효하지 않아 메모리에서 제거되기 직전에 호출되는 콜백입니다. onStop() 까지는 화면 전환이나 시스템 UI 의 home 버튼을 눌러 잠깐 Activity 를 이탈했을 때 호출되지만, 사용자가 앱의 backstack 의 마지막 Actvitiy 를 back 버튼으로 이탈하여 onFinish() 가 호출되거나 configuration change 로 인해 메모리에서 제거될 때 onDestroy() 가 호출될 수 있습니다. 따라서 메모리에서 제거되기 직전이므로 onStop() 에서 정리하지 않은 외부 참조 및 리소스를 반드시 여기서 제거해야 합니다. 그렇지 않으면 메모리 누수의 원인이 될 수 있습니다.

앱의 backstack 에서 마지막 Activity 가 제거되는 경우 앱 프로세스 상태는 EMPTY 가 됩니다. onDestroy() 호출 전 시스템은 ON_DESTROY 이벤트를 먼저 호출하고, Lifecycle State 는 DESTROYED 가 되며, 이후 onDestroy() 콜백이 실행됩니다.

lifecycle_state

이미 위에서 서술했듯이 lifecycle 관련 callback 실행은 전후에 시스템에서 특정 event 들을 실행합니다. 따라서 LifecycleOwner 구현체로부터 lifecycle 을 얻을 수 있다면, observer 패턴으로 callback 을 등록하여 lifecycle-aware 하게 특정 동작을 실행시킬 수 있습니다. 가장 흔한 예시로 androidx.lifecycle 의 repeatOnLifecycle() 확장 메소드를 Activity 또는 Fragment 에서 화면이 보이지 않는 경우 ViewModel 에서 수집하는 UI 관련 상태의 수집을 중지시키기 위한 용도로 사용할 수 있습니다.


lifecycleScope.launch {
  repeatOnLifecycle(Lifecycle.State.STARTED) {
    uiStateFlow.collect { uiState ->
      updateUi(uiState)
    }
  }
}

Lifecycle Scenario

해당 포스팅에서는 더 자세한 Activity 의 Lifecycle 을 알아보기 위해 몇가지 시나리오를 예시로 다뤄보려 합니다.

Single Activity

Activity 가 단 하나만 존재하는 경우에 대해 몇가지 시나리오를 더 자세히 살펴보겠습니다.

  • 시스템 back 버튼을 누르거나 onFinish() 를 명시적으로 호출한 경우

Activity 가 onPause() ~ onDestroy() 까지 연속적으로 호출됩니다. 즉, Activity 가 메모리에서 제거됩니다. 하지만 onSaveInstanceState() 는 호출되지 않습니다. Backstack 에 하나의 Activity 만 존재했다면, 앱 프로세스 상태는 EMPTY 가 됩니다.

  • Configuration Change 가 발생한 경우

Activity 가 onPause() - onStop() - onSaveInstanceState(bundle) - onDestroy() 까지 연속적으로 호출 된 후, 즉시 onCreate(bundle) - onStart() - onRestoreInstanceState(bundle) - onResume() 까지 호출됩니다. 위에서 설명한 바와 같이 이 때의 onCreate(Bundle) 의 bundle 파라미터는 null 이 아닙니다.

  • Dialog 와 같이 다른 window 에 의해 일부가 가려진 경우

Activity 가 onPause() 까지 호출되면서 포커스를 잃고 반투명 으로 보여지게 됩니다. 다른 window 가 제거되는 경우 다시 onResume() 이 호출되며 재개됩니다.

  • 다른 앱으로 전환되는 경우

Notification 을 클릭하거나 최근 화면에서 다른앱을 클릭하여 전환되는 경우 Activity 가 onPause() - onStop() - onSaveInstanceState(bundle) 까지 연속적으로 호출되고, 앱 프로세스가 background 로 전환됩니다. Background 상태인 앱 프로세스는 언제든지 메모리가 부족할 때 시스템에 의해 제거되어질 수 있기 때문에 상태 저장 및 복원을 위해 onSaveInstanceState() 가 호출되어집니다.

이후 다시 원래 앱으로 돌아간다면 onRestart() - onStart() - onRestoreInstanceState(bundle) - onResume() 순서로 재개됩니다. onStop() 까지 호출되었다면 onStart() 가 아닌 onReStart() 부터 시작한다는 점이 다릅니다. 또한 만약, 앱 프로세스가 시스템에 의해 메모리에서 제거되었다면 onCreate(bundle) - onStart() - onRestoreInstanceState(bundle) - onResume() 순서로 재개됩니다. 여기서 만약 프로세스가 제거된다면 onDestroy() 콜백은 실행되지 않는다는 것 입니다. 만약, 특정 컴포넌트 또는 앱이 종료되었는지를 판단하기 위해 onDestroy() 콜백을 이용한다면 정상적으로 확인할 수 없을 것 입니다.

Mutli Activity

Activity 가 하나일 때와 두개일 때는 큰 차이는 없습니다. 위의 4가지 시나리오에서 두개의 Activity 간의 전환이 된 경우가 추가됬을 뿐입니다.

  • Activity a 에서 b 로의 전환 후 시스템 back 버튼을 누르거나 onFinish() 를 명시적으로 호출한 경우

Activity a 의 onCreate() - onStart() - onResume() 이 호출된 상태에서 b 로의 전환이 일어나면 a 는 onPause() 까지 호출되며, b 의 onCreate() - onStart() - onResume() 이 호출된 후, A의 onStop() - onSaveInstanceState() 가 호출됩니다. 두 액티비티의 Lifecycle 순서는 병렬로 실행되며, 액티비티내의 콜백 호출 순서는 보장됩니다. 또한, a 액티비티 는 backstack 에 쌓이게 되고 메모리에서 제거된 것이 아닙니다. b 액티비티에서 back 버튼 혹은 onFinish() 가 호출된다면 b 액티비티는 명시적으로 종료되기 때문에 onPause() 가 호출되고, a 의 onRestart() - onStart() - onResume() 이 호출된 후 B의 onStop() - onDestroy() 가 이어서 호출됩니다. a 의 경우 메모리에서 제거되지 않았기 때문에 상태는 저장했지만 복원할 필요가 없으므로 onRestoreInstanceState() 가 호출되지 않습니다.

  • 만약 Activity a -> b 로 전환 후, 다른앱 으로 전환했다면?

위의 콜백 호출에서 볼 수 있듯이 b 로 전환할 때 시스템에 의해 종료될 경우를 대비하기 때문에 a 액티비티가 포커스를 잃으면서 onSaveInstanceState() 까지 호출됩니다. 또한, 다른앱으로 전환 하면서 b 액티비티는 onPause() - onStop() - onSaveInstanceState() 까지 호출됩니다.

  • a -> b 로 전환한 상태에서 메모리 부족 등으로 인해 시스템에 의해 프로세스가 종료되고 다시 앱 프로세스를 실행했다면?

상태를 저장했기 때문에 backstack 이나 view 계층구조 모두 복원되어 이전의 흐름에서 그대로 재개할 수 있습니다. 단지 두 Activity 모두 메모리에서 제거되었기 때문에 다시 앱 프로세스를 실행했을 때 Activity b 가 onCreate() - onStart() - onRestoreInstanceState() - onResume() 이 호출되며, b 에서 a 로 되돌아간다면 b 는 onPause() - onStop() - onDestroy() 가 호출되며 Activity a 는 onCreate() - onStart() - onRestoreInstanceState() - onResume() 이 호출됩니다.

  • a -> b 로 전환한 상태에서 configuration change 가 발생했다면?

Activity b 가 onPause() - onStop() - onSaveInstanceState() - onDestroy() 까지 호출된 후 즉시 onCreate() - onStart() - onRestoreInstanceState() - onResume() 이 호출됩니다. 즉 위에서 다룬 콜백이 그대로 실행됩니다. 이후 b 에서 a 로 되돌아간다면, Activity b 는 onPause() - onStop() - onDestroy() 가 호출되며 Activity a 는 onCreate() - onStart() - onRestoreInstanceState() - onResume() 이 호출되게 됩니다.

뭔가 특이점이 느껴지셨나요? 맞습니다. 특정 상황에서의 lifecycle 변화가 focus 를 받고 있는 Activity 에서 일어난다는 점입니다. 이 규칙으로 이해하신다면, 별도로 해당 상황들에 대한 lifecycle 변화를 외울 필요없이 떠올리실 수 있을겁니다.

Task 와 backstack

Task 란 안드로이드에서 A task is a collection of activities that users interact with when trying to do something in your app. 라고 정의하고 있습니다. 즉, activity들을 유지하는 collection 입니다. 하지만 여러 activity 를 전환하면서 단순히 collection 으로 그 순서를 유지하기는 어렵습니다. 따라서 backstack 이라고 불리는 stack 구조의 LIFO 자료구조를 사용하여 activity 들의 순서를 유지합니다. 요약하자면, Task 에는 backstack 이라 불리는 스택 자료구조로 Activity 들을 가지고 있습니다.

Task 와 Process 의 개념은 다릅니다. Process 개념은 안드로이드 프레임워크가 Linux 기반이기 때문에 Linux 의 프로세스 개념과 같습니다. 하지만, Task 는 Activity 에 대한 메모리를 저장하고 있는 단위로 안드로이드에서 만들어진 개념입니다.

조금더 예시를 들어볼까요? 개발자가 만든 앱 A 의 Activity A1 에서 다른 앱 B 의 Activity B1 를 요청해서 어떤 결과값을 가져온다고 생각해 봅시다.(B 앱에서 딥링크를 처리하고 호출가능하도록 exported = true 로 뒀다고 가정합니다.) 그러면 프로세스 관점에서는 앱A 는 background 이고 앱B 는 Foreground 일 것 입니다. 하지만, Task 에서는 A1 과 B1 이 차례로 쌓여있을 것입니다.(여기서 B1 의 launchMode 를 standard 로 가정합니다.) 어떻게 다른 앱이 현재 Task 에 쌓일 수 있을까요? 실제로 Activity 는 SystemServer 의 ActivityManagerService 에 의해 관리됩니다. 이를 이용한 Task 개념에서는 프로세스가 다르더라도 Activity 관점에서 바라보기 때문에 가능한 것 입니다.

런처앱 에서 앱을 실행했을 때 task 가 존재하지 않는다면, task 를 생성한 뒤 manifest 에 정의된 Launcher Activity 를 실행하게 됩니다. 하지만 개발자는 필요로 인해 task 가 있는 상태에서 다른 task 로 activity 를 실행하거나, 같은 task 안에서 같은 activity 를 재사용 하고 싶을 수 있습니다.(같은 task 안에서 default 로 같은 activity 를 두번 실행하면, backstack 에 호출 순서대로 두개가 쌓이게 됩니다.)

이를 위해 manifest 에서 activity 의 launchMode 태그로 실행 방법에 대해 명시할 수 있습니다.

  1. standard : default 모드 입니다. 호출 순서대로 backstack 에 무조건 쌓입니다. 즉, 같은 activity 가 여러번 호출되면 여러번 인스턴스화 하고 쌓입니다.
  2. singleTop : 만약, backStack 의 Top 에 호출하는 Activity 가 있다면, 재사용하고 onNewIntent() 를 호출합니다. 그렇지 않다면 standard 와 동일하게 실행됩니다.
  3. singleTask : taskAffinity(task 를 분류하는 단위) 가 같은 기존 task 에서 같은 activity 가 있는지 찾아 있으면, 해당 activity 위에 존재하는 모든 activity를 제거하면서 인스턴스를 재사용 하고 onNewIntent() 를 호출합니다. 또는, 다른 task affinity 에 Activity 인스턴스가 존재하면 해당 task 를 현재 task 로 결합하면서 위로 쌓습니다. 없다면, 새로운 task 에 root activity 로 만들어 실행합니다.
  4. singleInstance : Task 에는 하나의 activity 만 존재할 수 있습니다. activity 호출은 task 생성을 동반합니다.
  5. singleInstancePerTask : singleInstance 와는 달리 task 내에 다른 activity 는 실행될 수 있습니다.

보통의 앱에서는 standard 나 singleTop 으로도 충분할 것입니다. 필요에 따라 적절하게 사용하면 됩니다.

launchMode 태그 이외에도 Activity 실행시에 intent 로 flag 를 설정할 수도 있습니다.

  1. FLAG_ACTIVITY_NEW_TASK : launch mode 의 singleTask 와 동일하게 실행됩니다.
  2. FLAG_ACTIVITY_SINGLE_TOP : launch mode 의 singleTop 과 동일하게 실행됩니다.
  3. FLAG_ACTIVITY_CLEAR_TOP : 실행하는 Activity 가 이미 task 에 존재한다면, 재사용 하고 그위에 존재하는 모든 activity 를 소멸시킵니다. 따라서 실행하는 Activity 가 backstack 의 top 이 됩니다.

Intent 로 설정된 flag 는 launchMode 보다 높은 우선순위를 가집니다.

상태 관리

상태 관리 솔루션의 경우 이미 안드로이드 상태 관리 에서 다루었습니다. 해당 내용들은 1~3 로 3개의 챕터로 자세히 소개하고 있으니 생략하겠습니다.

화면 또는 권한 요청과 응답 처리

화면요청은 Api 30 미만에서는 contextWrapper#startActivity() 또는 Activity#startActivityForResult() 로 응답 처리가 가능합니다. 또한, 권한은 ActivityCompat#requestPermissions() 로 요청하고, Activity#onRequestPermission() 에서 응답을 처리할 수 있습니다.

API 30 이상에서는 ActivityResult*** 로 화면 또는 권한을 요청하고 callback 으로 응답을 처리합니다. 해당 자세한 내용들은 ComponentActivity 챕터에서 다루고 있습니다.

프로세스 수명주기

프로세스 수명주기는 공식문서 에 자세히 설명되어 있습니다.

  1. Foreground Process: 앱에서 사용자와 상호작용 하고 있는 Activity 가 존재하거나, BroadcastReceiver.onReceive() 가 실행중이거나, Service 의 수명주기 콜백이 실행중이라면 Foreground 상태로 유지됩니다. Foreground 가 우선순위가 가장 높으며, Background 가 가장 낮습니다. 우선순위가 높을 수록 시스템의 타겟이 되지 않지만, 이러한 프로세스들은 적게 존재합니다. 또한, 메모리가 완전히 부족한 경우 최후에는 Foreground 프로세스들도 종료될 수 있습니다.
  2. Visible Process: 앱의 Backstack 에서 Top Activity 가 Dialog 와 같은 다른 window 에 의해 일부 가려져서 onPause() 콜백이 실행된 경우, ForegroundService 인 경우, 시스템이 사용중인 서비스를 호스팅한 경우 프로세스 상태가 Visible 이 됩니다. 이때는 사용자가 해당 프로세스가 사용중임을 알고 있지만, 일부 가려지거나 보이지 않는 상태입니다. 따라서 이 프로세스가 제거되면 사용자 경험에 유의미한 악영향을 끼칠 수 있습니다.
  3. Service Process: Background Service 가 실행중인 경우에 해당합니다. 짧은 시간 동안 실행하는 것이 권장되며, 오랫동안 실행해야 한다면 Foreground Service 를 이용하고, 프로세스의 종료에도 주기적으로 실행해야 하는 작업이 있다면 Scheduling API(WorkManager or AlarmManager)를 사용하는 것이 적절합니다. 만약 오랫동안 Service Process 상태를 유지하면, 시스템은 이를 Cached Process 상태로 강등시킬 수 있으며, Foreground 와 Visible 프로세스를 유지하기에 메모리가 부족한 경우 Cached Process 이후에 Service Process 를 종료시킬 수 있습니다.
  4. Cached Process: 1,2,3 에 해당하지 않는 경우 Cached Process 상태가 됩니다. 이 경우 언제든지 시스템에 의해 종료될 수 있으며 안드로이드 13버전 부터는 백그라운드 제약으로 인해 프로세스가 아에 실행되지 않거나, 제한된 조건내에서 실행될 수 있습니다. Cached Process 는 리스트로 유지되며, 구체적으로 이 상태의 프로세스들중 어떤 프로세스를 먼저 제거할지는 플랫폼에 따라 다릅니다. Activity 의 경우 Window 가 완전히 가려져서 보이지 않는(onStop) 상태일 때에 해당되며, 만약 시스템에 의해 종료된다면 onDestroy() 가 호출되지 않음을 유의해야 합니다.

보통 앱의 비즈니스에 따라 background 에서 특정 작업을 수행할 필요가 있습니다. 음악 스트리밍 앱의 경우 다른앱 으로 전환하여 해당 앱이 보이지 않는 상태에서도 음악이 재생되어야 할 수도 있으며, 헬스 앱의 경우 앱이 보이지 않는 상태에서 걸음수를 수집해야 할 수도 있습니다.

안드로이드에서의 최근 업데이트 방향성은 background 작업에 대해 보안 및 사용자 경험 측면에서 개발자가 크게 사용하지 못하도록 제약을 추가하고 있습니다. 보통의 음악 스트리밍이나 헬스 데이터 앱은 ForegroundService 를 이용하여 Notification 을 통해 프로세스가 보이지는 않지만 실행 중 임을 알려주도록 권고하고 있습니다. 그렇지 않은 경우 정해지지 않은 방식으로 background 에서 실행중인 앱은 동작하지 않거나 언제든지 시스템에 의해 종료될 수 있습니다. 또한 전원 관리에서 소개하는 앱 제한/대기/잠자기 모드 로 인해 background 작업에 대해 많은 제약이 존재합니다. 이에 대한 만병통치약은 ForegroundService 이지만 반드시 Notification 이 노출되어야 한다는 단점(?)도 존재하며 적절한 방법을 이용하여 안드로이드 환경에서의 요구를 충족시키는 앱을 개발하여야 합니다.

그외 자세한 설명은 공식문서를 보시길 권장드립니다.

태그:

카테고리:

업데이트:

댓글남기기