우리가 매일 같이 보고 조작하는 익숙한 사용 패턴이다.
안드로이드에서 위와 같은 일련의 행위가 수행될 때, 내부적으로 Activity stack이라는 개념이 활용된다.
이전 포스팅에서 Activity의 개념과 수명 주기 단계 및 상태에 대해 살펴보았듯,
안드로이드는 여러 개의 Activity가 일련의 생성, 일시중지, 소멸 등의 주기를 따라 상태가 변경되는데, 그 과정에서 앱이 우리 눈 앞에 보여지거나 안보여지거나 또는 백그라운드에서 무언가를 실행하거나 하는식으로 구동된다.
이때 실행되는 Activity Task는 Stack에 일시적으로 저장된다.
Activity Stack은 일종의 대기표 관리소이다.
사진 속 장소는 내가 가장 좋아하는 평양냉면집 우래옥의 주차장(출처:네이버지도)인데, Stack을 설명하기 아주 좋은 실사례이다.
냉면을 다 먹고 우리 셋은 차를 몰고 북악스카이웨이로 드라이브를 가려고 한다.
주차요원분께 나의 1번 대기표를 주면? 아저씨는 화를 낼 것이다.
이것이.. First In Last Out이다..
Activity1을 Start하면 Stack에 Activity1이 쌓인다.
Activity2, 3을 순차적으로 Start하면 Stack에도 시작된 순서대로 2, 3가 쌓인다.
(자꾸만 생각나는 철수와 영희의 차..)
이때 사용자가 Back 버튼을 누르면?
가장 상단에 있는 3이 사라지고, 이제 최상단 Activity는 2가 된다.
이러한 일련의 동작은 Activity의 Fragment에 대해서도 동일하게 작동한다.
Home 버튼을 눌러서 Home으로 나갈 때에도 Stack에는 현재 실행하고 있는 Activity에 가장 상단에 쌓이게 된다.
즉, 현재 실행 중인 Activity가 Stack의 최상단에 위치하게 된다.
Stack 안에서의 정렬 처리는 불가능하기 때문에, 단일 Activity를 여러 번 Stack에 쌓는 식으로 처리한다.
만약 동일 Activity가 여러 번 Stack에 쌓이는 것을 막고싶다면 이 동작을 수정하면 된다고 한다.
(이번 글에서는 일단 기본적인 Stack의 동작 방식에 대해서만 정리해본다.)
위에서 안드로이드가 Activity를 FILO Stack에 배치하여 Task와 Back Stack을 관리한다는 것에 대해 살펴보았다.
<activity>
매니페스트 요소의 속성과startActivity()
에 전달하는 인텐트의 플래그를 사용하여 여러 작업을 수행 및 관리할 수 있다.
먼저 launchMode에 대해서 살혀보자. 어떻게 Task를 관리할 수 있을까?
Activity 선언 시, 시작될 때 Task과 어떻게 연결되는지 지정한다.
이 방법은 launchMode
속성을 사용하며, 5개의 할당 가능한 모드가 있다.
standard
singleTop
onNewIntent()
메서드를 호출하여 새로운 인텐트를 해당 Activity에 전달한다.| D |
| C |
| B |
| A | - root activity
만약 Stack이 이렇게 쌓여있다고 가정해보자.
이때 Activity D에 대한 인텐트가 도착한다.
만약, D가 standard
모드라면?
| D | <- NEW!
| D |
| C |
| B |
| A | - root activity
D라는 새로운 인스턴트가 생성되고 최상단에 쌓인다.
만약, D가 singleTop
모드라면?
| D |
| C |
| B |
| A | - root activity
Stack의 구조가 onNewIntent()
를 통해 유지된다.
근데..! 만약 Activity B에 대한 인텐츠가 도착했다면?
| B | <- NEW!
| D |
| C |
| B |
| A | - root activity
B의 모드가 singleTop
이여로 새로운 B 인스턴스가 Stack에 추가된다.
singleTask
onNewIntent()
메서드를 호출하여 기존 인스턴스로 전달한다.어피니티란 매니페스트 파일에서 Activity에 지정된 속성 중 하나이다. android:taskAffinity 속성으로 지정한다.
| D |
| C |
| B |
| A | - root activity
여기에서 D의 모드가 singleTask
인 인텐트가 들어오면 Stack은 이렇게 바뀐다.
| D | - root activity
singleInstance
singleTask
와 동일하다.| D |
| C |
| B |
| A | - root activity
여기에서 D의 모드가 singleInstance
인 인텐트가 들어오면 Stack은 이렇게 바뀌는데,
| D | - root activity
singleTask
와 다르게 아예 새로운 독립적인 공간에서 D를 실행한다.
singleInstancePerTask
singleTask
모드와 달리 FLAG_ACTIVITY_MULTIPLE_TASK
또는 FLAG_ACTIVITY_NEW_DOCUMENT
플래스가 설정되어 있으면 서로 다른 Task의 여러 인스턴스에서 시작될 수 있다.자.. 이게 무슨 말인가 하면..
App : 지도
Activity1 : 지도 표시 기본 뷰
Activity2 : 도시 정보 뷰
이렇게 구성된 App에서, singleInstancePerTask
모드로 Activity1을 설정한다고 가정해보자.
이를 스택으로 표현하면,
| 2-2 | | 2-1 | | 1 | - root activity
이 경우 1은 하나의 Stack 안에서 한 번만 실행될 수 있다.
2는 여러 인스턴스가 Stack 내에 있을 수 있다.
첫 번째 Stack을 보면 1, 2이 들어가있다.
이때, 모드가 singleTask
인 Y가 Stack에 추가된다면?
Y만 가져오는 것이 아니라, background task에 있는 Y, X를 떠서 가져온다. 즉, 전체 Stack을 가져온다.
그 이후 Back 버튼을 누르면 가장 상단에 있는 Activity부터 pop되어 종료된다.
startActivity()`를 호출할 때 새 Activity가 현재 Task와 연결되는 방식을 선언하는 FLAG를 인텐츠에 포함시킨다.
FLAG_ACTIVITY_NEW_TASK
singleTask
모드와 동일한 동작을 수행한다.FLAG_ACTIVITY_SINGLE_TOP
singleTop
모드와 동일한 동작을 수행한다.자.. 이게 무슨 말인고..하면,
아까 위에서 사례로 서술한 지도 앱을 다시 떠올려보자.
Task 1 : | 2-2 | | 2-1 | | 1 | - root activity
Task에 Stack이 이렇게 쌓여있다.
이때 사용자가 지도 앱을 다시 시작하면 1이 시작될 것이다.
(새로운 Task2가 생성되고 1이 root가 된다)
이미 지도 앱이 실행 중이며 새로운 Task에 1 인스턴스를 열게 된다.
FLAG_ACTIVITY_CLEAR_TOP
플래스가 사용된다.Task 2 : | 1 | - root activity
즉, Task2에는 새로운 1 인스턴스를 생성하지 않고 이전에 Task1에서 실행 중이던 1 인스턴스를 Task2로 가져온다.(Task1에도 여전히 존재)
어피니티 : Activity가 소속되기를 '선호'하는 Task
위에서 한 번 언급했는데, <activity>
요소의 taskAffinity
속성을 사용하여 어피니티를 수정할 수 있다.
어피니티는 다음 두 가지 상황에서 사용될 수 있다.
FLAG_ACTIVITY_NEW_TASK
가 포함되어 있는 경우기본적으로 새로운 Activity는 startActivity()
를 호출한 Activity의 Task로 시작된다(동일한 Stack으로 push).
그러나,
startActivity()
에 전달된 인텐트에FLAG_ACTIVITY_NEW_TASK
가 포함되어 있으면?
=> 새로운 Activity를 수용할 다른 Task를 찾는다.
이때 새 Activity와 동일한 어피니티를 가진 기존 Task가 있다면?
=> Activity는 해당 Task로 시작된다
다만, 이 플래그로 Activity가 새로운 Task로 시작되었을 때 다시 예전 Task로 돌아가야할 필요가 있다면 어떻게 할까?
=> startActivity()
에 전달하는 인텐트에 FLAG_ACTIVITY_NEW_TASK
를 정의한다.
자.. 이것을 메신저 앱을 예시로 이해해보자..
FLAG_ACTIVITY_NEW_TASK
플래그를 사용하여 메신저 앱의 Activity를 시작한다.FLAG_ACTIVITY_NEW_TASK
로 인해 새로운 Task에 메신저 앱의 새로운 Activity가 시작된다.FLAG_ACTIVITY_NEW_TASK
로 시작된 Task로 다시 돌아갈 수 있는 독립적인 방법이 필요하다!allowTaskReparenting
속성이 true
로 설정된 경우이건 무슨 말이냐면,
Task 1: | B | | A | - root activity
Task 2: | C | - root activity (현재 화면에 실행 중)
이렇게 Stack이 있다고 치자,
이때 사용자가 B를 시작하면 이렇게 된다.
Task 1: | A | - root activity
Task 2: | B | | C | - root activity (현재 화면에 실행 중)
B는 Task2로 이동되었다. 이는 allowTaskReparenting
속성이 true라서 가능한 것이다!
사용자가 장시간 Task를 떠나면 시스템은 root를 제외한 모든 Activity를 Task에서 삭제한다.
이러한 동작도 속성으로 수정할 수 있다.
alwaysRetainTaskState
을 true로 설정clearTaskOnLaunch
을 true로 설정alwaysRetainTaskState
와 반대이다.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
를 설정해야 최종적으로 적용된다.finishOnTaskLaunch
을 true로 설정clearTaskOnLaunch
와 비슷하지만 Task 전체가 아닌 단일 Activity에서 작동한다.터미널에 adb shell "dumpsys activity activities"
을 입력하면 된다고 해서 해봤는데,
뭔가.. 무시무시한게 나왔다.. 스크롤도 꽤 길다.. 이거.. 뭐가 나왔다고 거기서.. 겁나 험한게..
내가 위에서 공부하면서 상상한 Stack은 이런 모습이 아녔는데..
다행히도 "dumpsys activity activities | grep -i Hist"
이렇게 입력하면 Stack에 대한 정보만 필터링 할 수 있다고 한다!
이런 Sign in 화면이 있고 별도로 Sign up 화면이 있을때,
Sign in -> Sign up -(back)-> Sign in 화면으로 이동을 하면 스택은 어떻게 쌓이고 처리될까?
Hist 부분이 쌓인 스택이라고 보면 된다.
최하단의 HIST #0은 기본 반찬격인 launcher 항목인 것 같다.
[TIL-240328]