Android Task와 ACTIVITY FLAG 정복하기

DoDoBest

·

2024. 4. 7. 15:38

기본 개념 익히기

 

Root Activity에 대한 back Button 효과

 

Android Manifest에서 intent_fliter로 ACTION_MAIN, CATEGORY_LAUNCHER가 설정된 Activity를 Root launcher Activity라고 부르며, 이 Activity는 앱의 진입점이 됩니다.

 

Root launcher에서 back button을 눌렀을 때 효과는 안드로이드 버전에 따라 달라집니다.

 

Android 11 이전에는 시스템이 Activity를 즉시 종료시킵니다.

Android 12 이후부터는 종료시키는 대신, Activity와 Activity가 포함된 task를 백그라운드로 옮깁니다. 이 상태는 홈 버튼이나 최근 앱 목록을 눌렀을 때 도달하는 상태와 같습니다. 그래서 유저는 앱을 다시 실행할 때 이전(cold state)보다 좀 더 빨리(warm state) 실행할 수 있습니다.

 

공식 문서는 뒤로가기 버튼 동작을 커스텀 해야할 때, onBackPressed를 오버라이딩하는 것보다 AndroidX Activity APIs를 사용할 것을 권장합니다. onBackPressed를 오버라이딩 해야 한다면, super.onBackPressed를 호출해서 warm state가 유지되도록 하는 것을 권장합니다.

 

Multiple Task

 

여러 Task가 Background에 존재할 수 있으나, 이로 인해 메모리가 부족해질 경우, 앱은 백그라운드에 있는 task를 강제로 종료시킬 수 있으며, 이에 따라 앱의 state를 잃어버릴 수 있습니다.

 

Activity가 Stop될 때 저장되는 state

 

스크롤 위치, form에 입력된 text 값들(state)이 시스템에 의해 저장되며, Activity가 다시 시작될 때 state를 이용해 이전에 보던 상태를 복원합니다.

 

TaskAffinity

 

Activity가 속하는 Task를 구분하기 위한 값입니다. 별도로 지정하지 않으면 기본 taskAffinity 값을 따릅니다.

단순히 taskAffinity를 다른 값으로 지정한다고 해서, 해당 Activity를 실행할 때마다 해당 taskAffinity를 가진 task를 생성한 후, 그 task에 Activity를 생성하는 것은 아닙니다. FLAG_ACTIVITY_NEW_TASK 플래그를 지정해 실행하거나, launch 모드를 singleTask로 지정해야 합니다.

 

Task 동작 Custom 하기

 

Android는 기본적으로 last in first out 형태로 task를 관리하며, 실행을 요청받은 Activity를 새로 생성한 후 Stack의 맨 위에 쌓아 올립니다.

Android Manifest Activity attributes 설정, startActivity시 전달하는 intent에 flag를 설정함으로써 이 동작을 커스텀할 수 있습니다.

 

Launch Mode는 무엇이 있고, 차이점은 무엇일까?

 

launchMode는 Activity가 task에서 어떻게 동작할지를 나타내는 값으로, manifest file에서 Activity element의 launchMode 속성으로 지정할 수 있습니다.

 

 

📡 아래 로그 사진에서 의미하는 값들은 다음과 같습니다!

Task : task 번호

Affinity : taskAffinity

lifecycle callback 함수 뒤에 있는 숫자 : 같은 종류의 Activity 객체를 구분하기 위한 값

 

또한 아래 Repository에서 직접 실행해보며 로그를 확인해볼 수 있습니다.

https://github.com/DoTheBestMayB/Activity-Launch-Mode-Study

 

GitHub - DoTheBestMayB/Activity-Launch-Mode-Study: launchMode와 FLAG에 따른 Android Activity 학습 Repo

launchMode와 FLAG에 따른 Android Activity 학습 Repo. Contribute to DoTheBestMayB/Activity-Launch-Mode-Study development by creating an account on GitHub.

github.com

 

 

standard

 

launchMode를 설정하지 않으면 사용되는 기본 값입니다. 새로운 Activity instance가 생성되며, 생성을 요청한 Activity가 속한 Task에 할당됩니다.

 

  1. Activity는 여러 번 생성(instantiated)될 수 있습니다.
  2. 각 인스턴스는 서로 다른 task에 속할 수 있습니다.
  3. 한 개의 task에 동일한 Activity 인스턴스가 여러 개 존재할 수 있습니다.

 

singleTop

 

Activity instance가 현재 task의 top에 존재할 경우, 새로운 Activity instance를 생성하지 않고, top에 존재하는 instance에게 onNewIntent callback 함수를 통해 intent를 전달합니다.

 

  1. Activity는 여러 번 생성될 수 있습니다.
  2. 각 인스턴스는 서로 다른 task에 속할 수 있습니다.
  3. 한 개의 task에 동일한 Activity 인스턴스가 여러 개 존재할 수 있습니다.(단, 최상단에 존재하는 Activity가 Intent를 이용해 실행하려는 Activity와 동일하지 않은 경우에만)

 

top에 동일한 Activity가 존재할 때 호출되는 lifecycle callback은 다음과 같습니다.

 

onPause → onNewIntent → onResume

 

 

top Activity가 동일한 Activity가 아니면 새로운 Instance를 생성합니다.

 

 

singleTask

 

새로운 task를 생성하고, 새로 생성한 Activity를 해당 task의 root activity로 설정합니다. 동일한 task affinity 이름을 가진 task가 존재한다면, 해당 task에 새로 생성한 activity를 배치합니다.

 

Activity에 해당하는 instance가 이미 존재하면, 시스템은 새로운 Activity instance를 생성하지 않고, 해당 instance에게 onNewIntent callback 함수를 통해 intent를 전달합니다. 만약 해당 instance가 task의 top에 위치하지 않는다면, task 상에서 해당 Activity보다 위에 있는 activity들은 destroy 됩니다.

 

아래 로그는 MainActivity → SingleTaskActivity → MainActivity → SingleTaskActivity 순으로 실행한 결과입니다.

 

SingleTaskActivity를 다시 실행할 때, Task 상으로 상단에 있던 MainActivity가 onDestroy까지 호출된 것을 볼 수 있습니다.

SingleTaskActivity가 top이 아닐 경우에 호출되는 lifecycle callback 순서는 다음과 같은 것을 확인할 수 있습니다.

 

onRestart → onStart → onNewIntent → onResume

 

 

Affinity가 다른 singleTask

 

Affinity가 다른 singleTask를 실행하면 새로운 Task를 생성하며, 새로 생성한 Activity를 해당 Task의 root Activity가 되도록 설정합니다.

 

아래 로그는 MainActivity → SingleTaskActivity → SingleTaskWithOtherAffinityActivity(STWO) 순으로 실행한 결과입니다.

 

 

Manifest 파일에서 STWO의 affinity를 다르게 설정했기 때문에 기존과 다른 task가 생성된 것을 볼 수 있습니다.

taskAffinity를 별도로 명시하지 않으면 manifest application tag에 설정한 taskAffinity와 동일하며, application tag에 taskAffinity를 설정하지 않으면, build gradle의 namespace로 설정됩니다.

 

 

taskAffinity 값이 다르면, 다른 task가 되기 때문에 최근 앱 목록 상에서도 별도로 존재합니다.

 

 

 

현재 상태는 MainActivity → SingleTaskActivity → SingleTaskWithOtherAffinityActivity(STWO) 입니다. 여기서 SingleTaskActivity를 실행하면 STWO는 멈추고, SingleTaskActivity가 포함된 task가 실행됩니다.

 

 

여기서 뒤로가기를 누르면 SingleTaskActivity가 종료되고, task Stack 상 그 다음에 있는 MainActivity가 표시됩니다.

 

 

다시 뒤로가기를 누르면 MainActivity가 종료되고, STWO가 표시됩니다.

 

 

다시 뒤로가기를 누르면 STWO가 종료되고, 앱의 API 버전에 따라 완전 종료되거나 백그라운드 상태로 진입합니다.

 

 

singleInstance

 

singleTask와 동일하게 동작하지만, task 상에서 singleInstance로 설정된 Activity만 존재한다는 차이가 있습니다. SingleInstance는 항상 task의 유일한 1개의 Instance로만 존재합니다. 같은 Affinity를 가진 Activity일지라도 별도의 task를 생성하고, 해당 task에서 실행되도록 합니다.

 

아래 Log는 MainActivity → SingleInstanceActivity를 실행한 결과입니다. 둘은 동일한 Affinity를 가짐에도 별도의 task가 생성됐습니다.

 

 

이때 MainActivity를 실행하면, 새로운 MainActivity를 생성한 후, 기존에 MainActivity가 존재하던 Task 169에 배치합니다.

 

 

아래 Log는 MainActivity(0) → SingleTask → MainActivity(1) → MainActivity(2) → SingleInstance → SingleTask 순으로 실행한 결과입니다.

SingleTask Intent가 호출될 때, MainActivity(2), MainActivity(1)이 destory되는 것을 확인할 수 있습니다.

 

 

아래 Log는 뒤로가기를 눌러서 Activity를 계속 종료시켰을 때의 결과입니다.

 

 

 

Android의 Task와 관련된 FLAG는 무엇이 있고, 어떤 차이가 있을까요?

 

Task와 관련된 Flag는 startActivity에 전달하는 Intent에 포함할 수 있으며, task에 대한 Activity 동작을 수정하는 값입니다.

 

FLAG_ACTIVITY_NEW_TASK

 

Activity를 새로운 task에서 실행하는 flag입니다. 이미 Activity를 실행하는 Task가 있다면, 해당 Task를 foreground로 가져오고, Activity는 onNewIntent를 통해 Intent를 전달받습니다.

이 flag를 이용해 새로운 task에서 Activity가 실행되게 하려면 해당 Activity가 현재 task의 taskAffinity와 다른 값을 가져야 합니다.

 

아래 로그는 다음과 같은 순서로 Activity를 실행한 결과입니다. FLAG_ACTIVITY_NEW_TASK를 포함해서 Activity를 실행했음에도 동일한 task에서 생성된 것을 볼 수 있습니다.

 

MainActivity → Standard Activity → MainActivity → MainActivity → Standard Activity(FLAG_ACTICITY_NEW_TASK)

 

 

 

아래 로그는 다음과 같은 순서로 Activity를 실행한 결과입니다. manifest 파일에서 다른 taskAffinity를 명시한 Activity에 대해 FLAG_ACTIVITY_NEW_TASK를 포함해서 실행하면 별도의 task가 생성되는 것을 확인할 수 있습니다.

 

MainActivity → OtherAffinityActivity → Standard Activity → Standard Activity → OtherAffinityActivity(FLAG_ACTICITY_NEW_TASK)

 

 

아래 로그는 최근 앱 목록에서 이전 task 화면으로 이동해 Standard Activity가 Restart 되게 만들고, 다시 OtherAffinityActivity를 FLAG_ACTIVITY_NEW_TASK와 함께 실행한 결과입니다. 이미 해당 taskAffinity를 가진 task가 백그라운드에 있기 때문에, 새로운 task를 생성하지 않고 이미 존재하는 task에 있는 Activity가 restart 되는 것을 확인할 수 있습니다.

 

MainActivity → OtherAffinityActivity → Standard Activity → Standard Activity → OtherAffinityActivity(FLAG_ACTICITY_NEW_TASK) 최근 앱 목록에서 이전 Task의 Standard Activity로 전환 → OtherAffinityActivity(FLAG_ACTICITY_NEW_TASK)

[onNewIntent가 찍히지 않았는데, 이는 로그 상의 누락이 아니고, 실제로 호출되지 않았습니다.]

 

 

아래 로그는 이전 상태에서 Standard Activity를 실행한 후, 최근 앱 목록에서 이전 Task로 전환해서 다시 OtherAffinityActivity를 FLAG_ACTIVITY_NEW_TASK와 함께 실행한 결과입니다. taskAffinity에 해당하는 task로 전환은 됐지만, OtherAffinityActivity가 아닌 해당 task의 top에 존재하는 StandardActivity가 다시 시작된 것을 볼 수 있습니다.

 

MainActivity → OtherAffinityActivity → Standard Activity → Standard Activity → OtherAffinityActivity(FLAG_ACTICITY_NEW_TASK) → 최근 앱 목록에서 이전 Task의 Standard Activity로 전환 → OtherAffinityActivity(FLAG_ACTICITY_NEW_TASK) Standard Activity → 최근 앱 목록에서 이전 Task의 Standard Activity로 전환 → OtherAffinityActivity(FLAG_ACTICITY_NEW_TASK)

 

 

 

FLAG_ACTIVITY_SINGLE_TOP

 

실행하려는 Activity가 task의 back stack 최상단에 존재하는 현재 Activity와 동일할 경우, 새로운 Activity를 생성하는 대신, 현재 Activity가 onNewIntent 함수를 통해 intent를 수신합니다.

 

아래 로그는 MainActivity → StandardActivity → StandardActivity(FLAG_ACTIVITY_SINGLE_TOP) 순으로 실행한 결과입니다. Activity가 새로 생성되지 않고 기존의 Activity가 그대로 top에 존재하는 것을 확인할 수 있습니다.

 

 

FLAG_ACTIVITY_CLEAR_TOP

 

실행하려는 Activity가 현재 task에 존재할 경우, 새로운 Activity를 생성하는 대신, task 상에서 기존 Activity 위에 있는 Activity를 모두 제거하여 기존 Activity가 보이도록 만듭니다.

이 flag는 실행하려는 activity의 launchMode가 standard인지에 따라 달라집니다.

 

standard인 A Activity에 대해 실행할 경우, stack에서 A activity와 그 위에 있는 activity들을 destroy시키고, A Activity를 다시 생성합니다.

 

아래 로그는 MainActivity → StandardActivity → MainActivity → MainActivity → StandardActivity(FLAG_ACTVITY_CLEAR_TOP)의 실행 결과입니다.

 

 

그 외 launchMode를 가진 Activity에 대해 실행할 경우, stack에서 해당 Activity보다 위에 있는 Activity들을 destroy 시키고, 기존의 Activity는 onNewIntent를 통해 intent를 전달받습니다.

 

아래 로그는 MainActivity → SingleTopActivity → StandardActivity → StandardActivity → SingleTopActivity (FLAG_ACTVITY_CLEAR_TOP)의 실행 결과입니다.

 

 

참고자료

 

taskAffinity를 확인하기 위해 아래 코드를 사용했습니다.

fun getTaskAffinity(activity: Activity): String? {
    val am = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val appTaskList = am.appTasks
    var taskId: Int
    for (appTask in appTaskList) {
        val taskInfo = appTask.taskInfo

        // Get the id of the task
        taskId = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            taskInfo.id
        } else {
            taskInfo.taskId
        }

        // Detect the task that the activity is belonged to
        if (activity.taskId == taskId) {
            val rootActivity = taskInfo.baseActivity
            return try {
                val pm = activity.packageManager
                val ai = pm.getActivityInfo(rootActivity!!, PackageManager.GET_META_DATA)
                ai.taskAffinity
            } catch (e: PackageManager.NameNotFoundException) {
                null
            }
        }
    }
    return null
}

 

https://stackoverflow.com/questions/65001967/is-it-possible-to-get-the-name-taskaffinity-which-the-activity-belongs-to-during

 

Is it possible to get the name taskAffinity which the Activity belongs to during runtime?

I was wondering, is it possible to get the name of taskAffinity which the Activity belongs to, during runtime? I search through but unable to find a way to do so.

stackoverflow.com

https://developer.android.com/guide/components/activities/tasks-and-back-stack

 

작업 및 백 스택  |  Android 개발자  |  Android Developers

작업은 사용자가 앱에서 어떤 작업을 하려고 할 때 상호작용하는 활동의 모음입니다. 이러한 활동은 스택(백 스택)에 각 활동이 열린 순서대로 정렬됩니다.

developer.android.com