Intent.FLAG_ACTIVITY_NEW_TASK 사용할 때 조심할 점

in #android7 years ago (edited)

안드로이드 앱에선 액티비티를 시작할 때 intent를 이용한다. 이 intent에는 꽤 여러가지 flag를 옵션으로 줄 수 있는데, 문제는 이 옵션들이 하나같이 어렵고 복잡하다. 단순한 스택 구조라면야 문제될 게 없는데, 노티를 타고 실행하기 / 런처의 즐겨찾기로 실행하기 등이 끼어들면 액티비티와 태스크 구조부터 고민을 해야 한다.

나름 안드로이드 앱 개발을 몇년 해온 짬밥이 있어 이제는 얼추 대부분의 경우에 대응 시나리오가 떠오르는데, 이번에 마주한 문제는 처음 보는 녀석이었다.

  1. 앱을 구동한다. 이 때 첫 화면인 A 액티비티가 실행된다.
  2. 어떤 이슈가 있어 A 액티비티를 종료한다. 비동기로 조금 후 B 액티비티를 시작한다. 이 때 intent 플래그로 FLAG_ACTIVITY_NEW_TASK 를 주었다.
  3. B 액티비티에서 다시 B 액티비티를 시작하거나, B 액티비티에서 C 액티비티로 이동했다가 거기서 B 액티비티를 다시 시작한다.

문제는 3번에서 발생하는데, 분명히 startActivity를 실행했는데도 B 액티비티가 시작되지 않았다. 원인은 task root와 Intent. FLAG_ACTIVITY_NEW_TASK 가 겹쳐서 발생했다.

위 실행단계를 태스크 관점에서 살펴보면 다음과 같다.

  1. 1번 단계에서 A를 시작으로 하는 태스크 1이 생성된다.
  2. A 액티비티가 종료되면서 태스크 1도 사라진다. B 액티비티가 시작되면서 새로운 태스크 2가 시작된다.
  3. 태스크 2 에선 FLAG_ACTIVITY_NEW_TASK 플래그를 주어 B 액티비티를 시작하려는 모든 명령이 무시된다.

B 액티비티를 시작하려 할 경우, 안드로이드 에뮬레이터에선 아무런 로그가 남지 않고, 삼성 디바이스에선 다음과 같은 로그가 남는다.

D/ActivityManagerPerformance: Received MSG_DISABLE_ACT_START, r: ActivityRecord{4c4ff54d0 u0 com.example/.ActivityB t16934}

FLAG_ACTIVITY_NEW_TASK 의 레퍼런스 설명을 잘 보면 이런 대목이 나온다.

When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK for a flag to disable this behavior.

시작하려는 액티비티를 처리하는? 다루는? 태스크가 이미 동작중이라면 액티비티를 시작하는 대신 태스크가 foreground로 올라온다고 한다. 아마도 저기서 어떤 태스크가 액티비티를 처리하는지 여부는 task를 시작한 intent를 가지고 판단하는게 아닌가 싶다. 이건 adb shell dumpsys activity activities 명령을 이용해 task 목록을 조회해보면 확인할 수 있다.

    * TaskRecord{8aa1079d0 #17010 A=com.example U=0 StackId=1 sz=2}
      userId=0 effectiveUid=u0a321 mCallingUid=u0a321 mUserSetupComplete=true mCallingPackage=com.exmaple
      affinity=com.exmaple
      mFullscreen=true mLastNonFullscreenBounds=null mLastDeXBounds=null
      displayId=0
      canMoveTaskToScreen=true
      intent={flg=0x10000000 cmp=com.exmaple/.ActivityB launchParam=MultiScreenLaunchParams { mDisplayId=0 mBaseDisplayId=0 mFlags=0 }}

회피 방법은 간단하다. 그냥 FLAG_ACTIVITY_NEW_TASK 이 플래그를 안쓰면 된다. 하지만 이 플래그는 꼭 써야 하는 상황이 있다.

  1. service, notification 등에서 액티비티를 시작하기 위해선 꼭 붙여야 하고 (붙이지 않으면 특정 os 버전에선 크래시 날 수 있다)
  2. 별도로 taskAffinity를 주어 새로운 task를 시작할 때 붙여야 한다.
  3. 또 다른 유용한 flag인 FLAG_ACTIVITY_CLEAR_TASK 를 이용하려면 꼭 붙여한다.

다행히 FLAG_ACTIVITY_CLEAR_TASK, FLAG_ACTIVITY_CLEAR_TOP 플래그를 쓸 땐 별 문제가 없다 . 따라서 1, 2번 상황만 해결하면 된다. FLAG_ACTIVITY_CLEAR_TOP 도 함부로 쓸 수 없는 상황이라면 결국 task의 root가 될 수 있는 액티비티를 관리하는 방법 밖에 없을 듯 하다. 나라면 TaskLaunchActivity 같은 투명 액티비티를 두어 task의 초기화 작업만을 한 후, 다른 액티비티를 start한 후 바로 finish 하는 방법을 고려해 보겠다.

참고로 FLAG_ACTIVITY_RESET_TASK_IF_NEEDED도 유사하게, startActivity는 호출했지만 액티비티가 시작되지 않는 문제를 일으킬 수 있으므로 조심해서 사용해야 한다.

참고링크
startActivity() 호출 흐름 따라가기
ActivityStackSupervisor. startActivityUncheckedLocked()