이 게시글은 아래의 문서를 보고 작성했습니다.
안드로이드는 모바일 운영체제로서 다양한 애플리케이션을 실행하고 관리하는 기능을 제공합니다. 이를 위해 안드로이드는 작업(Task)와 백 스택(BackStack) 개념을 사용합니다.
작업
작업은 사용자가 특정 작업을 할 때 상호작용하는 액티비티의 컬렉션입니다. 액티비티는 스택(백 스택)에 각 액티비티가 열린 순서대로 정렬됩니다. 예를 들어 이메일 앱에는 새 메시지 목록을 표시하는 액티비티가 하나 있을 수 있습니다. 사용자가 메시지를 선택하면 메시지를 볼 수 있도록 새 액티비티가 열립니다.
백 스택
새로운 액티비티는 작업의 백 스택에 추가됩니다. 사용자가 뒤로 버튼을 누르면 새로운 액티비티가 완료되고 스택에서 팝됩니다.
작업의 새로운 액티비티가 백 스택에 추가되는 방식
애플리케이션의 작업은 기기의 홈 화면에서 시작합니다. 사용자가 애플리케이션을 실행하면 애플리케이션의 작업이 Foreground로 나옵니다. 애플리케이션의 작업이 없으면 새 작업이 생성되고 이 애플리케이션의 기본 액티비티가 백 스택의 루트 액티비티로 열리게 됩니다.
아래의 그림을 보겠습니다.
액티비티1이 Foreground에 있는 상황에서 액티비티2를 시작했습니다. 그럼 새로운 액티비티2가 스택의 맨 위에 Push 되고 Focus를 갖게 됩니다. 포커스를 갖는다는 말은 애플리케이션의 화면에서 우리가 확인할 수 있다는 말입니다.
액티비티2가 Push 되었다고 액티비티1가 삭제되는 것은 아닙니다. 액티비티1은 여전히 스택에 남아있지만 `중지` 된 상태를 가지게 됩니다. 사용자가 뒤로 버튼을 누르면 현재 스택의 맨위가 POP 되고 이전 액티비티가 다시 시작됩니다.
위의 그림은 이와 같은 과정을 나타낸 것입니다.
사용자가 계속 뒤로 버튼을 눌러서 스택이 비게 되면 사용자는 홈 화면으로 돌아갑니다. 모든 액티비티가 스택에서 삭제되면 작업이 더 이상 존재하지 않게 됩니다.
작업의 새로운 액티비티가 백 스택에 추가되는 방식
작업의 모든 액티비티는 백그라운드에 있는 동안 중지되지만 작업의 백 스택은 유지됩니다.
아래의 그림을 한번 보겠습니다.
Task A에서 Task B라는 새로운 작업을 시작한다고 생각해 보겠습니다. (우리가 메시지를 보낼 때 사진을 첨부하려고 갤러리에 접근하는 것이 새로운 작업을 시작하는 것입니다.) Task B가 실행되면 기존의 Task A는 Task B가 실행되는 동안 Focus를 잃습니다. Task B의 작업이 완료되면 다시 Task A로 돌아와야 하기에 기존의 Task A는 Backgound에 위치합니다. 이런 과정을 통해 사용자는 중단된 액티비티를 계속해서 이어나갈 수 있습니다.
백 스택의 특성
백 스택은 후입선출(Last-In-First-Out) 객체 구조로 작동합니다. 백 스택에 Push 되는 것들은 액티비티의 인스턴스들 입니다.
액티비티의 인스턴스가 저장되기 때문에 아래 그림과 같은 상황이 발생할 수도 있습니다.
위의 그림을 보게되면 `Home Activity` 가 개별적인 인스턴스로 스택에 적재되어 있는 것을 확인할 수 있습니다. 스택은 정렬할 수 없기 때문에 이전의 액티비티의 인스턴스를 순서가 아닐 때 Pop 하는 것은 불가능합니다. 그렇기에 `Home Activity`의 새로운 인스턴스가 생성되어 스택에 Push 되게 됩니다.
작업 관리
스택에 이런 작동 방식은 앱에 효과적이지만 액티비티가 시작할 때 현재 작업내에 배치되지 않고 새 작업이 시작되게 하거나, 액티비티가 시작될 때 백 스택에 맨 위에 새 인스턴스가 Push 되는 대신 액티비티의 기존 인스턴스가 앞으로 나오게 하고 싶을 수가 있습니다.
이런 특별한 경우는 manifest 파일에서 액티비티를 선언할 때 <activity> 요소의 launchMode 속성을 사용해 액티비티가 작업과 어떻게 연결되는지에 대해 지정하는 방법과 Intent 플래그를 사용하는 방법이 있습니다.
이와 관련하여 사용할 수 있는 주요 <activity> 속성은 다음과 같습니다.
- taskAffinity
- launchMode
- allowTaskReparenting
- clearTaskOnLaunch
- alwaysRetainTaskState
- finishOnTaskLaunch
manifest 파일에서 방식 지정
manifest 파일에서 <activity> 요소의 launchMode 속성을 사용해 액티비티가 작업과 연결되어야 하는 방식을 지정할 수 있습니다.
방식의 종류는 아래와 같습니다.
"standard"(기본 모드)
기본값입니다. 시스템은 액티비티가 시작된 작업에 액티비티의 새 인스턴스를 생성하고 인텐트를 인스턴스로 라우팅합니다. 액티비티는 여러 번 인스턴스화될 수 있고 각 인스턴스는 서로 다른 작업에 속할 수 있으며 한 작업에는 여러 인스턴스가 있을 수 있습니다. 지금까지 설명했던 백 스택의 상태가 바로 "standard" 입니다.
"singleTop"
활동의 인스턴스가 이미 현재 작업의 맨 위에 있으면 시스템은 액티비티의 새 인스턴스를 생성하지 않고 onNewIntent() 메서드를 호출하여 인텐트를 기존 인스턴스로 라우팅합니다. 액티비티는 여러 번 인스턴스화될 수 있고 각 인스턴스는 서로 다른 작업에 속할 수 있으며 한 작업에는 여러 인스턴스가 있을 수 있습니다(단, 백 스택의 맨 위에 있는 액티비티가 액티비티의 기존 인스턴스가 아닐 때에만).
예를 들어 작업의 백 스택이 루트 액티비티 A와 액티비티 B 그리고 맨 위의 액티비티 C로 구성되어 있다고 가정합니다(스택은 A-B-C이며 C가 맨 위에 있음).
C 유형 액티비티의 인텐트가 도착합니다. C에 기본 "standard" 실행 모드가 있으면 클래스의 새 인스턴스가 실행되고 스택이 A-B-C-C가 됩니다. 하지만 C의 실행 모드가 "singleTop"이면 C의 기존 인스턴스는 onNewIntent()를 통해 도착한 인텐트를 받습니다. C의 기존 인스턴스가 스택의 맨 위에 있기 때문이며 스택은 계속 A-B-C로 유지됩니다.
그러나 B 유형 액티비티의 인텐트가 도착하면 B의 새 인스턴스가 스택에 추가되며 B의 실행 모드가 "singleTop"이더라도 스택에 추가됩니다.
액티비티의 새 인스턴스가 생성되면 사용자는 뒤로 버튼을 눌러 이전 액티비티로 돌아갈 수 있습니다. 그러나 액티비티의 기존 인스턴스가 새 인텐트를 처리할 때 사용자는 뒤로 버튼을 눌러도 새 인텐트가 onNewIntent()에 도착하기 이전 액티비티 상태로 돌아갈 수 없습니다.
"singleTask"
시스템이 새 작업을 생성하고 새 작업의 루트에 있는 액티비티를 인스턴스화합니다. 그러나 액티비티의 인스턴스가 이미 별도의 작업에 있다면 시스템은 새 인스턴스를 생성하지 않고 onNewIntent() 메서드를 호출하여 인텐트를 기존 인스턴스로 라우팅합니다. 액티비티의 인스턴스가 한 번에 하나만 존재할 수 있습니다.
아래는 실행 모드가 "single Task"인 액티비티가 백 스택에 추가되는 방식을 나타냅니다.
한번 예를 들어보겠습니다.
1️⃣
우리는 Task 1에서 A -> B -> C의 순서로 액티비티를 Task 1 스택에 Push 했습니다. 그리고 홈 버튼을 눌러 새로운 Task 2를 실행하고 D -> E의 순서로 액티비티를 Task 2 스택에 Push 했습니다.
2️⃣
그래서 Task 2가 Focus돼 Foreground에 위치하고 Task 1은 Background에 위치하게 됩니다.
3️⃣
이제 우린 액티비티 E에서 액티비티 B를 시작하려고 합니다. 이때 액티비티 B의 실행모드는 "single Task" 입니다. "single Task"는 가장 먼저 실행하려는 액티비티가 백 스택 상에 존재하는지 확인합니다. 만약 액티비티가 백 스택의 어느 곳에도 없다면, 새로운 인스턴스를 생성하여 백 스택의 맨 위에 추가합니다.
하지만 우리의 예시에서는 액티비티 B가 Background의 Task 1의 백 스택에 존재합니다.
4️⃣
그렇기에 액티비티 B를 화면에 띄울려면 Task 1에 있는 액티비티 B가 스택의 최상단에 위치해야 합니다. 그래서 액티비티 B위에 있는 액티비티 C는 Pop 되고 액티비티 C는 `onDestroy()`가 호출됩니다.
이젠 Task 1가 Focus돼 Foreground에 위치하고 Task 2은 Background에 위치하게 됩니다.
만약 사용자가 뒤로 버튼을 누른다면 사용자는 액티비티 E로 이동하는 것 대신에 액티비티 A로 이동하게 됩니다.
액티비티가 새 작업에서 시작되더라도 사용자는 여전히 뒤로 버튼을 통해 이전 액티비티로 돌아갈 수 있습니다.
"singleInstance"
"singleTask"와 동일하지만 시스템이 인스턴스를 보유한 작업으로는 어떤 다른 액티비티도 실행하지 않는다는 점이 다릅니다. 액티비티는 항상 자체 작업의 단 하나의 유일한 멤버입니다. 이 액티비티로 시작된 모든 액티비티은 별도의 작업으로 열립니다.
인텐트 플래그로 방식 지정
사용할 수 있는 주요 인텐트 플래그는 다음과 같습니다.
FLAG_ACTIVITY_NEW_TASK
액티비티를 새 작업에서 시작합니다. 지금 시작하고 있는 액티비티에 대해 이미 실행 중인 작업이 있으면 그 작업이 마지막 상태가 복원되어 포그라운드로 이동하고 활동은 onNewIntent()의 새 인텐트를 수신합니다.
이 플래그를 사용하면 이전 섹션에서 설명한 "singleTask" launchMode 값과 동일한 동작이 발생합니다.
FLAG_ACTIVITY_SINGLE_TOP
시작 중인 액티비티가 현재 액티비티(백 스택의 맨 위에 있는)면 액티비티의 새 인스턴스가 생성되는 대신 기존 인스턴스가 onNewIntent() 호출을 수신합니다.
이 플래그를 사용하면 이전 섹션에서 설명한 "singleTop" launchMode 값과 동일한 동작이 발생합니다.
FLAG_ACTIVITY_CLEAR_TOP
시작 중인 액티비티가 현재 작업에서 이미 실행 중이면 액티비티의 새 인스턴스가 실행되는 대신 작업의 맨 위에 있는 다른 모든 액티비티가 제거되고 이 인텐트가 onNewIntent()를 통해 액티비티(이제 맨 위에 있음)의 다시 시작된 인스턴스로 전달됩니다.
이 동작을 발생시키는 launchMode 속성 값은 없습니다.
FLAG_ACTIVITY_CLEAR_TOP은 FLAG_ACTIVITY_NEW_TASK와 함께 가장 자주 사용됩니다. 이러한 플래그를 함께 사용하면 또 다른 작업에 있는 기존 액티비티를 찾아 인텐트에 응답할 수 있는 위치에 액티비티를 넣을 수 있습니다.
지정된 액티비티의 실행 모드가 "standard"이면 이 액티비티도 스택에서 삭제되고 대신 새 인스턴스가 실행되어 수신 인텐트를 처리합니다. 실행 모드가 "standard"이면 새 인텐트에 대해 항상 새 인스턴스가 생성되기 때문입니다.
이런 플래그들을 사용한 예시는 아래와 같습니다.
// 데이터를 담은 인텐트 생성
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("key", "Hello from MainActivity")
// 인텐트에 플래그 설정 (예: FLAG_ACTIVITY_NEW_TASK)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
// 다른 액티비티 실행
startActivity(intent)
Reference
'Android' 카테고리의 다른 글
Android의 Build Process (0) | 2023.08.20 |
---|---|
UI 상태 저장과 ViewModel로 상태 저장하기 (0) | 2023.08.05 |
Android의 Intent (인텐트) (0) | 2023.07.23 |
안드로이드의 Context (0) | 2023.07.11 |
안드로이드의 앱 구성요소 (4대 컴포넌트) (0) | 2023.07.11 |