안드로이드 앱에선 액티비티를 시작할 때 intent를 이용한다. 이 intent에는 꽤 여러가지 flag를 옵션으로 줄 수 있는데, 문제는 이 옵션들이 하나같이 어렵고 복잡하다. 단순한 스택 구조라면야 문제될 게 없는데, 노티를 타고 실행하기 / 런처의 즐겨찾기로 실행하기 등이 끼어들면 액티비티와 태스크 구조부터 고민을 해야 한다.
나름 안드로이드 앱 개발을 몇년 해온 짬밥이 있어 이제는 얼추 대부분의 경우에 대응 시나리오가 떠오르는데, 이번에 마주한 문제는 처음 보는 녀석이었다.
문제는 3번에서 발생하는데, 분명히 startActivity를 실행했는데도 B 액티비티가 시작되지 않았다. 원인은 task root와 Intent. FLAG_ACTIVITY_NEW_TASK 가 겹쳐서 발생했다.
위 실행단계를 태스크 관점에서 살펴보면 다음과 같다.
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 이 플래그를 안쓰면 된다. 하지만 이 플래그는 꼭 써야 하는 상황이 있다.
다행히 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()