(원문: http://developer.android.com/guide/components/tasks-and-back-stack.html)

(위치: Develop > API Guides > App Components > Activities
> Tasks and Back Stack
)

(전체 번역 글 목록으로 이동하기)


태스크와 백스택


앱은 보통 여러개의 액티비티로 구성됩니다. 각 액티비티는 특정한 목적을 갖는 화면을 보여주며, 다른 액티비티들을 실행할 수 있습니다. 예를 들어, 이메일앱은 읽지 않은 이메일 목록을 보여주는 액티비티가 있으며, 목록에서 이메일을 선택하면 그 이메일 내용을 보여주는 액티비티도 있습니다. 

액티비티는 디바이스에 설치된 다른 앱의 액티비티도 실행할 수 있습니다. 예를 들어, 내 앱에서 이메일 보내기가 되도록 하고 싶다면, Intent.ACTION_SEND 액션값과 이메일 주소 및 메시지를 담고 있는 인텐트를 만들어서 실행하면 됩니다. 그러면 디바이스에 설치된 모든 앱 중에서 해당 인텐트에 대응되는, 즉 이메일을 비롯한 데이터를 보내는 기능을 수행하는 액티비티가 실행될 것입니다. 만약 그러한 액티비티가 2개 이상이라면 사용자가 선택할 수 있도록 다이얼로그를 띄워 줍니다. 이메일 보내기 작업이 완료되면 이메일 쓰기 액티비티가 종료되면서 내 앱의 액티비티가 다시 resumed 상태가 되는데, 이는 마치 이메일 쓰기 액티비티가 내 앱의 일부인 것처럼 보이게 합니다. 이러한 경우, 비록 액티비티들이 서로 다른 앱에 속해 있긴 하지만, 같은 태스크에 있음으로써 사용자에게 자연스러운 사용자 경험(UX)을 제공할 수 있는 것입니다. 

태스크는 사용자에 의해 실행된 액티비티들의 모음입니다. 액티비티들은 생성되면서 "백스택"이라 불리는 스택에 쌓이게 됩니다.

디바이스의 홈 화면은 거의 모든 태스크들의 시작점입니다. 사용자가 앱 런처에서 앱 아이콘을 터치하거나, 홈 화면의 바로가기 아이콘을 터치하면, 앱의 태스크가 전면(foreground)으로 나오게 됩니다. 만약 앱을 최근에 실행한 적이 없어서 태스크가 없다면, 새 태스크가 생성되며 앱의 "메인" 액티비티가 실행되고 태스크의 root가 됩니다.

현재 액티비티가 다른 액티비티를 실행하면, 실행된 새 액티비티가 스택의 top에 들어가면서 포커스를 획득하며, 이전 액티비티는 여전히 스택에 들어가 있기는 하지만 stopped 상태가 됩니다. 액티비티가 정지되더라도(stop), 시스템은 액티비티의 사용자 인터페이스(UI)의 현재 상태를 보존하며, 사용자가 뒤로가기버튼을 눌렀을 때, 현재의 액티비티를 스택에서 꺼내고(popped. 이때 액티비티는 종료됩니다), 이전 액티비티를 resumed 상태로 만듭니다(정지되기전 UI 상태가 복구됩니다). 스택에 있는 액티비티의 배열은 절대 바뀌지 않습니다. 단지 스택에 넣고(push) 꺼내는(pop) 동작만 있을 뿐입니다. 액티비티가 생성될 때 스택에 넣고, 사용자가 뒤로가기버튼을 눌러서 종료될 때 꺼내는 것입니다. 이러한 백스택의 자료구조를 "후입선출"이라고 표현합니다. 아래의 그림1은 액티비티들이 백스택에 어떻게 들어가고 나오는지를 보여줍니다.


그림 1. 이 그림은 태스크의 새 액티비티가 백스택에 어떻게 추가되는지와, 뒤로가기버튼을 눌렀을 때 현재 액티비티가 종료되며 이전 액티비티가 다시 resumed 상태가 되는 것을 보여줍니다.


만약 사용자가 계속 뒤로가기버튼을 누른다면, 스택의 top에 있는 액티비티가 제거되면서 이전 액티비티가 resumed 상태로 스택의 top이 될 것이고, 결국은 홈화면으로 빠져나갈 것입니다(또는 해당 태스크가 시작되기 전의 마지막 액티비티가 실행될 것입니다). 태스크의 모든 액티비티가 제거되면, 그 태스크도 없어집니다.


그림 2. 두개의 태스크: 태스크 B는 전면(foreground)에서 사용자와 상호작용을 하지만, 태스크 A는 후면(background)에서 다시 resumed 상태가 되기를 기다립니다.


태스크는 사용자가 다른 태스크를 시작하거나, 홈버튼을 눌러서 홈화면으로 전환될 때, "후면(background)"으로 이동합니다. 후면에 있을 때, 태스크의 모든 액티비티들은 stopped 상태가 되지만, 태스크의 백스택은 액티비티들을 잘 유지하며, 그림2에서 보이는 바와 같이 다른 태스크에게 자리를 내주고 포커스만 잃게 되는 것입니다. 태스크는 다시 "전면(foreground)"으로 나설 수 있으며, 사용자는 해당 태스크의 마지막 화면을 볼 수 있습니다. 예를 들어, 현재의 태스크(Task A)가 스택에 3개의 액티비티를 담고 있는데, 사용자가 홈버튼을 눌러 홈화면으로 이동한 후 앱 런처에서 다른 앱을 실행한다고 가정해 봅시다. 홈화면으로 이동할 때, Task A는 후면으로 이동합니다. 그리고 다른 앱을 실행할 때, 시스템은 그 앱의 태스크인 Task B를 시작하는데 그것은 자신의 액티비티들을 담을 스택을 따로 갖습니다. 그 앱에서 사용자가 할 일을 마치고 홈화면으로 돌아갔다가 다시 이전의 앱을 실행하면, Task A가 다시 전면으로 나설 것이며, 스택에 있던 3개의 액티비티는 모두 무사하고, 스택의 top에 있는 액티비티(사용자가 Task A에서 마지막으로 실행한 액티비티)가 다시 resumed 상태로 될 것입니다. 홈화면으로 이동할 때 Task B는 후면으로 이동합니다. 이전 태스크를 다시 전면으로 가져오는 방법은 해당 앱의 아이콘을 클릭하거나, 홈버튼을 길게 누르면 보이는 최근 태스크 목록에서 태스크를 선택하면 됩니다. 이것은 안드로이드 멀티태스킹의 한 예입니다.

메모: 여러개의 태스크가 동시에 후면에 있을 수 있긴 하지만, 메모리가 부족할 경우 시스템이 메모리를 회복하기 위해 후면에 있는 태스크의 액티비티들을 종료할 수도 있습니다. 본 문서에서는 액티비티의 상태를 저장하는 방법에 대해서도 학습할 것입니다. 


그림 3. 하나의 액티비티가 여러개의 서로 다른 객체로 생성될 수 있습니다.


백스택의 액티비티들은 절대 재배치되지 않기 때문에, 만약 내 앱에서 특정 액티비티가 여러개의 다른 액티비티로부터 실행될 수 있다면, 그 액티비티는 생성될 때마다 새로운 객체로 스택에 추가될 것입니다(기본적으로는, 스택에 있는 동일한 액티비티의 객체를 재사용하지 않습니다). 위의 그림3에서 보이는 바와 같이 하나의 액티비티가 여러번 객체로 생성될 수 있는 것입니다. 그리고 그림3과 같은 상황에서 사용자가 뒤로가기버튼을 연이어 누른다면, 백스택의 top에 있는 액티비티부터 차례대로 (해당 액티비티의 현재 UI상태가) 화면에 출력될 것입니다. 하지만, 액티비티의 객체가 한번만 생성되고 그것이 재사용되도록 할 수도 있으며, 자세한 내용은 태스크 관리하기 섹션에서 학습하실 수 있습니다.

액티비티와 태스크에 관한 기본적인 동작을 요약하면 아래와 같습니다:

  • 액티비티 A가 액티비티 B를 실행하면, 액티비티 A는 stopped 상태가 되지만, 시스템은 (스크롤 위치나 입력 영역에 입력된 문자열 등의) 액티비티 A에 대한 현재 상태를 유지합니다. 그리고 액티비티 B가 전면(foreground)에 있는 상태에서 뒤로가기버튼을 누르면 액티비티 A가 이전 상태를 유지하면서 다시 resumed 상태가 됩니다.

  • 사용자가 홈 버튼을 눌러서 현재 태스크로부터 빠져나가면, 현재 액티비티는 stopped 상태가 되고 태스크는 후면(background)으로 이동하지만, 시스템은 태스크의 액티비티들에 대한 상태를 유지해 줍니다. 그리고 만약 사용자가 런처에서 해당 앱 아이콘을 눌러 다시 앱을 실행하고자 하면, 해당 태스크가 다시 전면(foreground)으로 나오면서 백스택의 top에 있는 액티비티가 다시 resumed 상태가 됩니다.

  • 사용자가 뒤로가기버튼을 누르면, 현재 액티비티가 백스택에서 빠지면서 종료되고, 이전 액티비티가 resumed 상태로 변경됩니다. 액티비티가 종료되면, 시스템은 더이상 종료된 액티비티의 상태를 유지해주지 않습니다.

  • 액티비티는 (어떤 태스크에서건) 여러번 객체로 생성될 수 있습니다. 

네비게이션 설계: 안드로이드에서 앱의 네비게이션이 어떻게 동작하는지에 대한 자세한 내용은 안드로이드 디자인 영역의 네비게이션 가이드에서 학습하실 수 있습니다.


액티비티의 상태 저장하기

위에서 학습한 바에 따르면, 시스템은 기본적으로 액티비티가 stopped 상태로 될 때, 그 상태를 유지해줍니다. 이대로라면, 사용자가 뒤로가기버튼을 눌러 이전 액티비티로 이동할 때, 보여지는 상태는 사용자가 그 액티비티에서 벗어날 때의 마지막 상태가 됩니다. 하지만, 액티비티는 사용자의 의도와 상관없이 시스템에 의해 종료 및 재생성 되는 경우가 있기 때문에, 액티비티의 콜백 메소드를 이용하여 상태를 저장해야 합니다.

액티비티가 stopped 상태로 변경되면(새 액티비티를 실행하거나, 태스크가 후면(background)으로 이동하거나 하는 경우), 시스템은 메모리를 확보하기 위해 액티비티를 완전히 종료시켜버릴 수도 있습니다. 이러한 경우, 액티비티의 상태에 대한 정보는 모두 잃게 되지만, 백스택에서도 해당 액티비티가 빠지는 것은 아닙니다. 따라서, 시스템에 의해 종료된 액티비티가 백스택의 top에 오게 되면, (재사용할 액티비티 객체가 종료되었으므로) 액티비티를 다시 생성해야 합니다. 이러한 경우 액티비티의 상태를 복구하기 위해서는, 액티비티의 onSaveInstanceState() 콜백 메소드에서 상태를 저장하도록 구현해야 합니다. 

이와 관련하여 더 자세한 내용은 액티비티의 상태 저장하기 문서에서 학습하실 수 있습니다.


태스크 관리하기 

위에서 학습한 바와 같이 안드로이드는 기본적으로, 연이어 생성되는 액티비티들을 같은 태스크 내에 그리고 "후입선출" 스택에 집어 넣습니다. 태스크와 백스택을 관리하는 안드로이드의 이러한 방식은 대부분의 앱에서 훌륭하게 작동하며, 보통은 액티비티들이 어떤 태스크에 속하고 어떤 백스택에 들어가 있는지를 신경쓸 필요가 없습니다. 하지만, 내 앱에서는 이러한 안드로이드의 기본 동작과는 다르게 구현하고 싶을 수 있겠죠. 액티비티가 생성될 때 현재 태스크가 아닌 새로운 태스크를 시작하고 싶을 수도 있고, 현재 태스크에 액티비티의 객체가 있다면 액티비티 객체를 새로 생성하지 않고 이미 있는 액티비티 객체를 재사용하고 싶을 수도 있을 것입니다. 또는 사용자가 현재 태스크에서 벗어날 때, root에 있는 액티비티를 제외하고 나머지는 모두 제거하고 싶을 수도 있을 것입니다.

이러한 요구사항들은, 매니페스트 파일의 <activity> 요소에 관련 속성을 추가하거나, 소스코드의 startActivity()에 넘길 인텐트에 플래그를 추가함으로써 구현할 수 있습니다.

<activity> 요소의 주요 속성들:

taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch

인텐트의 주요 플래그들:

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP

이어지는 섹션에서는, 액티비티들이 어떤 태스크에 속하고 어떤 백스택에 들어가게 될지를 정의하기 위해서, 매니페스트의 속성들 및 인텐트의 플래그들을 사용하는 방법에 대하여 학습할 것입니다.

주의사항: 대부분의 앱에서는 액티비티와 태스크에 대한 기본 동작을 변경할 필요가 없습니다. 만약 변경할 필요가 있다면, 앱이 실행되는 동안의 사용성과 다른 액티비티 및 태스크에서 뒤로가기버튼을 눌러 돌아오는 동작 등에 대하여 충분히 테스트를 함으로써, 사용자가 기대하는 동작과 어긋나지 않도록 신경을 써야 합니다.


시작(launch) 모드 정의하기

시작 모드는 액티비티의 새 객체가 현재의 태스크에 어떻게 속하게 될지를 결정해 줍니다. 아래와 같이 2가지 방식으로 시작 모드를 지정할 수 있습니다:

매니페스트 파일 사용하기
메니페스트 파일에 액티비티를 선언할 때, 액티비티 생성시 태스크에 어떻게 속하게 될지를 지정할 수 있습니다.

인텐트 플래그 사용하기
소스코드에서 startActivity() 호출할 때, 인텐트에 플래그를 추가함으로써 액티비티가 태스크에 어떻게 속하게 될지를 지정할 수 있습니다.

액티비티 A가 액티비티 B를 실행하는 경우에, 액티비티 B는 매니페스트 파일에 속성을 추가함으로써 자신이 현재 태스크에 어떻게 속하게 될지를 지정할 수 있고, 액티비티 A는 액티비티 B를 실행하는 인텐트에 플래그를 추가함으로써 같은 역할을 할 수 있습니다. 이 둘은 모두 액티비티 B와 태스크의 관계를 지정하는 것이며, 액티비티 A의 인텐트 실행이 액티비티 B의 매니페스트 파일 지정보다 우선합니다.

메모: 매니페스트 파일에 지정되는 일부 시작 모드는 인텐트를 통해 구현될 수 없고, 반대로 인텐트 플래그로 지정되는 일부 시작 모드는 매니페스트 파일에 지정될 수 없습니다.


매니페스트 파일 사용하기

매니페스트 파일에 액티비티를 선언할 때, <activity> 요소에 launchMode 속성을 추가함으로써 액티비티가 태스크와 어떤 관계를 맺게 될지를 지정할 수 있습니다.

launchMode 속성은 액티비티가 태스크에 어떤 방식으로 추가될 지를 지정합니다. 아래에 4가지 launchMode 속성값에 대해 간단히 설명하겠습니다:

"standard" (기본값)
시스템은 액티비티를 실행하려고 인텐트를 넘겨주고 있는 그 태스크에서 액티비티의 새 객체를 생성합니다. 액티비티는 여러번 객체를 생성할 수 있고, 각각은 서로 다른 태스크에 들어갈 수도 있으며, 하나의 태스크가 (한 액티비티에 대한) 여러개의 객체를 포함할 수 있습니다.

"singleTop"
어떤 액티비티의 객체가 이미 현재 태스크의 top에 있는데 그 액티비티에 대하여 startActivity()를 호출할 경우, 시스템은 액티비티의 새 객체를 생성하는 대신에 태스크의 top에 있는 액티비티 객체의 onNewIntent()를 호출함으로써 재사용합니다. 위의 standard와 마찬가지로, 액티비티는 여러번 객체를 생성할 수 있고, 각각은 서로 다른 태스크에 들어갈 수도 있으며, 하나의 태스크가 (한 액티비티에 대한) 여러개의 객체를 포함할 수 있습니다. 하지만, 태스크의 top에 하나의 액티비티에 대한 2개의 객체가 들어가는 상황만은 허용하지 않으며, 이미 있는 객체를 재사용하도록 하는 것입니다.

예를 들어, 태스크의 백스택이 A, B, C, D라는 액티비티로 채워져있다고 가정해 봅시다(스택은 A-B-C-D이며, D가 top입니다). 그리고 시스템에게 D를 실행해 달라는 인텐트가 왔다고 합시다. 만약 D의 시작 모드(launchMode)가 "standard"라면, D의 새 객체가 생성되어 백스택에 추가되고 스택은 A-B-C-D-D가 될 것입니다. 하지만 D의 시작 모드가 "singleTop"이라면, 백스택의 top에 이미 D의 객체가 있기 때문에 그 객체가 onNewIntent()를 통해 해당 인텐트를 받을 것이고 스택은 그대로 A-B-C-D가 될 것입니다. 하지만 시작되는 액티비티가 D가 아닌 B라면 시작 모드가 "singleTop"이라 하더라도, 새 객체가 생성되어 백스택에 추가될 것이고 스택은 A-B-C-D-B가 될 것입니다. 

메모: 액티비티의 새 객체가 생성되어 태스크에 추가되었을 때, 사용자가 뒤로가기버튼을 누르면 이전 액티비티로 돌아갈 수 있었습니다. 그러나 시작 모드가 "singleTop"이라서 태스크의 top에 있는 액티비티가 재사용된 상황이라면, 사용자가 뒤로가기버튼을 눌렀을 때, 재사용되기 전 상태로 돌아가는 것이 아니라, 현재 액티비티가 종료되고 그 이전의 액티비티가 재시작될 것입니다.

"singleTask"
시스템은 새 태스크를 생성하고, 태스크의 root에 액티비티의 새 객체를 생성합니다. 하지만, 태스크에 이미 해당 액티비티의 객체가 존재한다면, 액티비티의 새 객체를 생성하지 않고 기존 객체의 onNewIntent()를 호출함으로써 재사용합니다. 하나의 액티비티에 대한 객체는 오직 하나만 존재할 수 있습니다.

메모: 비록 액티비티가 새 태스크에 생성되었다 하더라도, 뒤로가기버튼을 누르면 이전 액티비티로 돌아갈 수 있습니다.

역자의 추가글: singleTask에 대한 설명이 오해의 소지가 많아 몇가지 설명을 덧붙입니다. 시스템은 시작 모드가 singleTask인 액티비티를 실행할 때 taskAffinity가 같은 태스크가 있는지를 확인하여, 만약 있다면 그 태스크에 액티비티의 객체를 생성하여 넣으며, 이때의 위치는 태스크의 root가 아니겠죠. 그리고 만약 taskAffinity가 같은 태스크가 없다면, 새 태스크를 생성하고 그 태스크의 root에 액티비티의 새 객체를 생성합니다. taskAffinity에 대한 자세한 내용은 아래의 친밀도(affinity) 다루기에서 학습하실 수 있습니다.

"singleInstance"
자신이 속한 태스크에 다른 액티비티의 객체들을 들어오지 못하게 한다는 점을 제외하고는 "singleTask"와 비슷합니다. 하나의 액티비티에 대한 객체는 오직 하나이고, 자신이 속한 태스크에는 오직 자신만 있을 뿐입니다. 이 액티비티에서 다른 액티비티를 실행한다면, 실행된 액티비티는 다른 태스크에 속하게 됩니다.

다른 예로, 인터넷 브라우저 앱은 브라우저 액티비티의 시작 모드를 singleTask로 지정했기 때문에, 내 앱에서 브라우저를 실행할 때 브라우저 액티비티가 내 앱의 태스크에 들어가지 않습니다. 대신에 새 태스크를 생성하거나, 이미 존재하는 브라우저의 태스크가 후면(background)에 있다면 전면(foreground)으로 가져와서 재사용합니다. 

액티비티가 새 태스크에 들어갔건, 기존의 태스크에 들어갔건 상관없이 사용자가 뒤로가기버튼을 누르면 현재 액티비티가 종료되고 이전 액티비티가 실행됩니다. 하지만, 시작 모드가 singleTask인 액티비티를 실행하는데, 그 액티비티는 이미 후면에 있는 태스크에 존재하고 그 태스크에는 다른 액티비티도 있는 경우, 시스템은 후면에 있던 태스크를 통째로 전면으로 가져옵니다. 이 때, 현재의 백스택의 top에다가, 후면에서 가져온 태스크의 액티비티들을 모두 집어넣습니다. 아래의 그림4가 이 과정을 보여줍니다.


그림 4. 시작 모드가 "singleTask"인 액티비티를 백스택에 추가하는 모습을 보여줍니다. 만약 액티비티가 후면에 있는 다른 태스크에 이미 존재한다면, 그 태스크가 통째로 현재의 태스크의 top으로 옮겨오게 됩니다.

매니페스트 파일에 시작 모드를 설정하는 방법에 대한 더 자세한 내용은, <activity>의 android:launchMode에서 학습하실 수 있습니다.

메모: 매니페스트 파일에서 지정한 launchMode는, 액티비티를 실행할 때 넘겨주는 인텐트에 포함된 플래그값으로 덮어씌워질 수 있습니다. 다시 말해서, 인텐트에 포함된 (시작 모드에 대한) 플래그값이 매니페스트 파일에 지정된 시작 모드보다 우선합니다.


인텐트의 플래그 사용하기

액티비티를 실행할 때, startActivity()에 넘겨주는 인텐트에 플래그를 설정함으로써 액티비티의 시작 모드를 바꿀 수 있습니다. 그 플래그값들은 아래와 같습니다:

FLAG_ACTIVITY_NEW_TASK
실행하려는 액티비티의 객체가 존재하지 않으면 새 태스크의 root에 액티비티의 새 객체가 들어갑니다. 하지만 후면(background)에 이미 그 액티비티를 담고 있는 태스크가 있다면, 전면(foreground)으로 가져와서 마지막 상태를 복구하고 인텐트를 onNewIntent()에 넘겨줍니다. 하지만 후면에 있던 태스크의 top에 있던 액티비티가 실행되기 때문에, 실행하고자 했던 액티비티와 실행된 액티비티가 다를 수도 있습니다. 따라서 대부분의 경우 FLAG_ACTIVITY_CLEAR_TOP과 함께 사용합니다.
이것(FLAG_ACTIVITY_NEW_TASK + FLAG_ACTIVITY_CLEAR_TOP)은 위에서 학습한 "singleTask"의 역할과 같습니다.

FLAG_ACTIVITY_SINGLE_TOP
현재 실행중인 액티비티(현재 태스크의 top에 있는)에서 다시 동일한 액티비티를 실행하려고 하는 경우, 새 객체를 생성하는 대신 현재 액티비티의 onNewIntent()를 호출함으로써 액티비티를 재사용합니다.
이것은 위에서 학습한 "singleTop"의 역할과 같습니다.

FLAG_ACTIVITY_CLEAR_TOP
만약 실행하려고 하는 액티비티의 객체가 이미 현재 태스크에 존재한다면, 새 객체를 생성하는 대신, 그 액티비티 객체의 위에 있는 다른 액티비티들을 모두 제거하여 그 액티비티가 태스크의 top이 되도록 합니다. 
이것과 역할이 같은 launchMode값은 없습니다.

다시 말하지만, FLAG_ACTIVITY_CLEAR_TOP은 대부분의 경우 FLAG_ACTIVITY_NEW_TASK와 함께 사용됩니다. 이 경우, 실행하려고 하는 액티비티가 후면(background)에 있는 태스크에 존재한다면, 그 태스크를 전면(foreground)으로 가져오고, 해당 액티비티 위에 다른 액티비티들이 있다면 모두 제거하여 그 액티비티가 실행되도록 합니다.

메모: 인텐트의 플래그가 FLAG_ACTIVITY_CLEAR_TOP인데, 만약 실행하려는 액티비티의 시작 모드가 "standard"이면, 기존에 있던 액티비티의 객체를 제거후 다시 생성하여 태스크에 넣습니다. 시작 모드가 "standard"일때는 시스템이 새 액티비티를 일단 생성하고 보기 때문입니다. 


친밀도(affinity) 다루기

친밀도(affinity)는 액티비티가 어떤 태스크에 속하고 싶어하는가를 지정할 수 있습니다. 기본적으로 같은 앱에 속한 액티비티들은 같은 친밀도(패키지명)를 가지고 있기 때문에, 같은 태스크에 속하고 싶어하지만, 액티비티의 친밀도를 바꿀 수 있습니다. 서로 다른 앱의 액티비티들이 같은 친밀도를 가질 수도 있고, 하나의 앱의 액티비티들이 다른 친밀도를 가질 수도 있습니다. 

친밀도는 매니페스트 파일의 <activity> 요소에 taskAffinity 속성을 통해 지정할 수 있습니다.

taskAffinity 속성의 기본값은 <manifest>에 선언된 패키지명이며 이것이 앱의 친밀도이기 때문에, 내 앱에서 다른 태스크를 생성하고자 taskAffinity를 지정하는 것이라면 패키지명과 다른 문자열로 지정해야 합니다.

친밀도는 아래의 2가지 상황에서 작동합니다:

  • 액티비티를 실행하는 인텐트에 FLAG_ACTIVITY_NEW_TASK가 포함되어 있을 때.
    기본적으로 새 액티비티는, 생성될 때 startActivity()를 호출한 액티비티와 같은 태스크에 들어갑니다. 하지만, startActivity()에 넘긴 인텐트에 FLAG_ACTIVITY_NEW_TASK가 포함되어 있다면, 시스템은 액티비티의 친밀도와 같은 태스크를 찾습니다. 만약 그러한 태스크가 존재한다면 그 태스크에 새 액티비티를 넣고, 존재하지 않는다면 새 태스크를 생성합니다. 
    만약 이 플래그를 이용하여 새 태스크를 만들었는데 사용자가 홈버튼을 눌러 빠져나간 경우 다시 그 태스크로 가고 싶다면, 그 태스크로 이동할 수 있는 방법이 존재해야 한다는 것을 개발시 고려해야 합니다. (노티피케이션 매니저와 같은) 일부 요소들은 항상 다른 태스크에서 액티비티를 실행하기 때문에, startActivity()에 넘기는 인텐트에 항상 FLAG_ACTIVITY_NEW_TASK를 설정합니다. 만약 이러한 방식으로 액티비티가 실행되었다면, 사용자가 그 태스크로 이동할 수 있는 독립적인 방법이 있어야 합니다. 예를 들면, 태스크의 root에 있는 액티비티가 매니페스트에 선언될 때 인텐트 필터에 CATEGORY_LAUNCHER가 선언되어 있다면, 런처화면에 아이콘이 제공되어 그 아이콘을 누르면 그 태스크로 이동하게 됩니다. 태스크를 시작하는 것과 관련한 자세한 내용은 태스크 시작하기에서 학습하실 수 있습니다.

  • <activity>의 allowTaskReparenting 속성값이 "true"일 때.
    이 경우에, 액티비티가 현재는 자신이 시작된 태스크에 있다하더라도, 자신과 친밀도가 같은 태스크가 전면(foreground)으로 나올 때, 그 태스크로 이동할 수 있습니다. 
    예를 들어, 어떤 여행 앱에, 선택된 도시의 날씨 정보를 보여주는 액티비티가 있다고 가정해 봅시다. 그 액티비티는 여행 앱의 다른 모든 액티비티들과 같은 친밀도를 가지고 있고, allowTaskReparentingtrue로 지정되어 있습니다. 내 앱에서 그 날씨 정보 액티비티를 실행하면, 그 액티비티는 그것을 실행한 내 액티비티의 태스크에 들어갈 것입니다. 하지만, 여행 앱의 태스크가 전면(foreground)으로 나올 때, 그 날씨 정보 액티비티는 여행 앱의 태스크로 옮겨질 것입니다.

팁: 만약 하나의 .apk파일이 사용자 관점에서 2개 이상의 "앱"으로 구성되어 있다면, 각 "앱"에 해당하는 액티비티들에 대하여 각각의 친밀도를 부여할 수도 있을 것입니다.


백스택 비우기

만약 사용자가 태스크에서 오랜 시간 벗어나 있었다면, 시스템은 그 태스크의 root 액티비티만 남기고 나머지는 모두 제거합니다. 사용자가 다시 그 태스크에 돌아왔을때는 root에 있던 액티비티 하나만 남아있겠죠. 시스템이 이렇게 동작하는 이유는, 사용자가 어떤 태스크에서 벗어나 오랜 시간이 지났다면 아마도 이전에 뭘했는지를 잊었을 것이고 다시 돌아왔을때는 새로 시작하는 것이 나을 것이라고 판단하기 때문입니다.

하지만 이러한 동작도 액티비티의 몇가지 속성을 통해 바꿀 수 있습니다:

alwaysRetainTaskState
태스크의 root 액티비티의 이 속성이 "true"이면, 오랜 시간이 지나도 액티비티들을 제거하지 않고 모든 액티비티들을 유지합니다.

clearTaskOnLaunch
태스크의 root 액티비티의 이 속성이 "true"이면, 사용자가 이 태스크에서 벗어났다가 다시 돌아올 때, (벗어나 있던 시간과 상관없이) root 액티비티만 남기고 나머지는 모두 제거합니다. 달리 말하면, alwaysRetainTaskState 속성과 반대되는 속성이라고 할 수 있습니다. 사용자가 아주 잠깐 벗어났다가 돌아오더라도 태스크는 초기 상태가 되는 것입니다. 

finishOnTaskLaunch
이 속성은 clearTaskOnLaunch와 비슷하지만, 태스크가 아닌 하나의 액티비티(root 액티비티 포함)에 대해서 동작합니다. 이 속성이 "true"일 때, 액티비티는 오직 태스크가 전면(foreground)에 있을 때만 태스크 안에 유지될 수 있습니다. 사용자가 태스크에서 벗어났다가 돌아올 경우에 그 액티비티는 제거됩니다.


태스크 시작하기

태스크의 진입점을 나타내기 위해, 액티비티에 액션값이 "android.intent.action.MAIN"이고 카테고리가 "android.intent.category.LAUNCHER"인 인텐트 필터를 선언할 수 있습니다. 예제 코드:

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

위의 인텐트 필터는 액티비티에 설정된 아이콘과 이름을 앱의 런처화면에 보이게 해줍니다. 이는 사용자가 그 액티비티를 실행할 수 있도록 하고, 액티비티 실행후 태스크에서 벗어나더라도 언제던지 런처를 통해 태스크로 돌아올 수 있게 합니다. 

위에서 두번째로 얘기한, 태스크로 돌아올 수 있게 한다는 기능이 중요합니다: 사용자들은 태스크에서 벗어났다가 액티비티 런처를 통해 다시 돌아올 수 있어야 합니다. 이 때문에, 새 태스크를 생성할 수 있는 2가지 시작 모드인 "singleTask""singleInstance"는, 액티비티가 ACTION_MAINCATEGORY_LAUNCHER 필터를 가지고 있을 때만 사용하는 것이 좋습니다. 이러한 필터가 없이 실행된 경우를 상상해 보세요: 시작 모드가 "singleTask"인 액티비티를 실행하여 새 태스크가 생성되었고, 사용자가 거기서 홈버튼을 눌러 벗어났다면, 이제 그 태스크는 후면(background)으로 들어가게 되고 더이상 보이지 않습니다. 그 액티비티에는 위의 인텐트 필터가 없다고 했기 때문에 런처 화면에 보이지 않을 것입니다. 따라서 (내 앱이 태스크를 실행하지 않는 한) 이제는 사용자가 그 태스크로 돌아갈 방법이 없습니다.


Posted by 개발자 김태우
,