Things take time

[Android] Activity의 LaunchMode에 대하여 본문

Android(기능)

[Android] Activity의 LaunchMode에 대하여

겸손할 겸 2018. 2. 22. 15:55

[개요]

 

오랜만의 안드로이드 포스팅.

이번에 iOS/안드로이드 앱에 외부 앱과의 공유하기 기능을 추가하였다.

그러나 안드로이드의 경우, 외부 앱에서 현재 앱으로 전달되었을 때.. 만약 현재 앱이 백그라운드에 있는 상태였다면, 백그라운드에 있는 앱을 다시 실행시켜 작업하는 것이 아니라, 새로 인스턴스가 생기면서 앱이 새로켜지게 되어버렸다.

 

그래서 현재 백그라운드 앱을 검사하면, 현재 앱의 인스턴스가 2개가 되는 상황

 

 

이처럼 백그라운드 버튼을 누르면 2개의 앱이 실행되게 되는 것이다.

결국은 공식 레퍼런스를 뒤지고, 이것 저것 해보며 알게 되었지만.. 참 한국어 설명이 뭔 소린지 모르겠다. 그래서 정리한다.

 

https://developer.android.com/guide/topics/manifest/activity-element.html?hl=ko

https://developer.android.com/guide/components/tasks-and-back-stack.html?hl=ko

 

두 개의 레퍼런스 사이트를 참고했다.

 

 

[로직]

 

현재 내 앱에는 3개의 액티비티가 있다.

 

A : 처음 실행되는 액티비티로, Intro 화면 및 외부 스키마 처리를 수행한다. (매니페스트의 intent-filter들을 갖고 있어 외부 공유하기로 들어오면 여기를 먼저 탄다)

B : 메인 액티비티로 대부분의 작업을 수행한다.

C : 서브 액티비티로 메인 액티비티에서 필요할 때 호출한다.

 

현재 사용하는 로직은 이렇다.

외부 앱(크롬과 같은 인터넷, 사진첩, 혹은 다른 앱)에서 공유하기를 통해 우리 앱을 호출하면, A 액티비티에서 넘겨받은 데이터를 저장 및 인트로 이미지를 지정 시간동안 띄워준 후, B 액티비티를 호출한다. B 액티비티에서는 넘겨온 값이 있다면(= 외부앱을 통해 실행되었다면), C 액티비티를 호출한다.

 

 

[처리 과정]

 

기본적으로 각 액티비티 속성에는 launchMode라는 옵션이 있다. 해당 액티비티가 호출될 때, 인스턴스를 어떻게 할 것인지.. 스택에 쌓일 때는 어떻게 할 것인지에 대한 속성 값이다.

 

 

만약, 각 액티비티에 launchMode값을 지정하지 않았다면 기본 값인 standard가 들어가게 된다.

 

 

먼저.. launchMode에는 4개의 값이 있으며, 그 중 standard와 singleTop을 하나의 그룹으로 묶고 나머지 singleTask, singleInstance로 묶는다.

 

두 그룹의 차이점은 다중 인스턴스를 지원하는가에 대한 유무이다.

singleTop과 standard는 다중 인스턴스를 지원하지 않고, singleTop만 지정 액티비티가 앱의 스택에 맨 위에 있을 경우엔 onNewIntent()라는 함수를 탄다는 것이다.

 

그러므로 실행하려는 액티비티와 스택의 맨위에 있는 액티비티가 동일하다면, singleTop을 사용하게 되었을 때 인스턴스가 2개 생성되지는 않는다는 것이다.

 

나의 경우에는, 외부 앱의 공유하기를 통해 앱으로 들어오면 무조건 A 액티비티를 타게 되어 있다.(인텐트 필터 설정한 곳) 그런데 일반적으로 현재 보여지고 있는 액티비티는 B거나 C일 것이다. A는 인트로 이미지만 보여주거나, 외부 스키마, 데이터를 처리만 하고 무조건 B를 띄우기 때문이다. 

 

그러므로 A 액티비티에 singleTop을 걸어준다면.. 앱이 외부앱에 의해 실행될 때 호출되는 액티비티는 A지만, 현재 인스턴스 액티비티 중 맨 위에 있는 액티비티는 A가 아니다. 현재 실행중인 인스턴스 중, B또는 C 액티비티가 스택의 상위에 있기 때문(=대상 작업의 맨 위에 존재하는 경우)에 onNewIntent()를 탈 수가 없다. 그러므로 인스턴스가 외부앱에 의해 실행될 때마다 재사용 되지 않고, 새로 호출된다.

 

그렇다면 B, C에 singleTop을 건다면?

기본적으로 singleTop은 백그라운드의 맨위에 있는(스택 맨 위) 액티비티와 호출하는 액티비티가 일치해야지 onNewIntent()를 수신한다. 

앱이 실행되지 않은 상태라면 A->B(필요 시 B->C)이므로 singleTop과 상관 없이 onNewIntent()는 호출되지 않는다. 이 상태에서 앱을 종료하지 않고, 외부에서 앱을 실행하여 B 혹은 C를 호출한다면 onNewIntent()를 타게 된다.

 

그러므로 코드에서는, onResume(혹은 onCreate)에서 getIntent()할 때(= 앱이 처음 켜지는 경우))과 onNewIntent에서 intent를 받을 때(앱이 실행중인 상태에서 다시 재실행 된 경우)하는 작업을 중복적으로 사용해야하며, 필요한 경우 flag로 구분해서 사용하면 된다.

 

 

이제 저 singleTask와 singleInstance를 사용하게 되면, 지정 액티비티가 루트 액티비티가 된다. B 액티비티에 singleTask를 걸었다고 생각하자.

 

상황을 살펴보자. 그냥 앱을 실행시켰다.

A 액티비티가 메인이므로 실행되었고, 인트로 시간이 지나 B 액티비티를 호출했다.

여기서 일반적인 standard라면 A -> B의 스택으로 쌓여있겠지만, singleTask로 한 경우 인스턴스 액티비티는 B만 남아있게 된다. 그리고 앱을 백그라운드에 있는 상태(홈버튼)로 두고 외부의 앱에서 데이터를 전달하여 들어온 경우, 호출되는 것은 A 액티비티(인텐트 필터)다. A 액티비티는 launchMode가 따로 없으므로(standard), 인트로 처리를 하고 아무렇지 않게 B 액티비티를 호출한다. 이 때, B 액티비티는 이미 액티비티 인스턴스가 되었으므로 onNewIntent()를 타게 된다. singleTask, singleInstance는 맨위에 있는 액티비티가 무엇인지 비교하지 않는다.

 

즉, 나는 B, C 액티비티는 기존 것을 재활용하면서, 하나의 인스턴스만 있길 바라므로 B에 singleTask를 걸어주면 된다. C까지 걸어주지 않아도 B에서 이미, 기존 인스턴스를 재활용하기 때문에 B에만 걸면 된다.

 singleInstance 가 있는 액티비티의 경우에는 다른액티비티를 시작할 수 없다 = startActivity를 할 수 없다.

(추가 댓글 확인 필요)

 

A에다 singleTask를 건다면?

A가 먼저 호출되고 새 작업의 루트를 A로 잡는다. 그리고 B 액티비티를 실행한다. 인스턴스 액티비티는 A다.

그런 상황에서 홈버튼을 눌러 나가고, 바탕화면에 있는 앱을 실행하면.. A의 onCreate()가 돌게된다. 

 

현재 보이는 액티비티가 B인데, 인스턴스 액티비티는 A므로 새로운 루트로 작업을 시작해야 하기 때문이다.

실행 -> B가 루트 -> 홈버튼 -> 실행 -> A가 루트 아니므로 재시작 -> B가 루트 -> 반복.

 

 

singleTop은 루트 액티비티가 있다면 그 액티비티를 보전하고, 자기부터 재 사용시 onNewIntent()

singleInstance는 루트 액티비티는 이 값을 가진 액티비티가 되므로, 이전 액티비티가 있어도 루트는 자신이 되며, 자기가 재 사용시 onNewIntent()

 

여기서도 잘못 이해해 틀린 부분이 있어 수정해야할 부분도 있을 것이다. 추후 참고하면서 계속 수정해야겠다.