이번 주제는 SAA입니다. 구글이 소개해서 꽤 유명한 것으로 알고 있지만, 생각보다 대화를 나누다보면 잘 알고 있는 사람은 드물다고 여겨지는 부분입니다.저 역시 유명한 아키텍쳐나 패턴, 코드 컨벤션 등에서 모르는 것들이 많은데, SAA도 다른 사람들에게 그런 토픽일 수도 있겠다 생각하면서 정리해봅니다.
Single Activity Architecture
2018 구글 I/O에서 언급한 Single Activity라는 개념은 기존의 액티비티 기반의 화면 구성이 아닌, 소수의 액티비티와 다수의 프래그먼트를 이용해 화면을 구성하는 구조로 jetpack navigation과 함께 소개되었습니다. 기존에 없던 개념을 구글이 처음 제시한 내용은 아니고, 그 전부터 토론되어 왔던 내용을 구글이 jetpack navigation과 함께 엮어 더욱 편리하게 하나의 액티비티에서 다수의 프래그먼트를 다룰 수 있도록 작성한 것입니다.
이와 비슷한 개념은 React의 SPA(Single Page Application)이 있습니다. 비록 Page와 activity는 전혀 다른 개념이지만, 프론트엔드 개발자와 이야기하다보면 둘의 구조가 비슷하다는 것을 알 수 있습니다.(하지만 구조가 비슷할 뿐 분명 다릅니다.)
이미 충분히 액티비티와 프래그먼트를 이용해 잘 작성되어 있는데 이렇게 UI의 구조를 바꾸는 것에는 어떠한 장점이 있을까요? 가장 기본적인 장점으로는 액티비티보다 가벼운 프래그먼트를 주로 활용하기 때문에, 동일한 화면 플로우를 기준으로 액티비티로 구성된 앱보다 프래그먼트로 구성된 앱이 훨씬 가벼워집니다. 구글 공식문서에서도 나와있지만, 액티비티를 전환하는데 소모하는 리소스가 프래그먼트를 전환하는데 소모하는 리소스보다 훨씬 크기 때문에 이를 줄이기 위해서 프래그먼트를 활용할 수 있습니다.
또한 jetpack navigation이 공식적으로 지원됨으로서 각 액티비티 내의 프래그먼트 플로우를 navigation graph로 관리할 수 있게 되어 가시성있는 화면의 flow를 그릴 수 있습니다. 물론 이는 SAA 그 자체로서의 장점은 아니지만, 구글에서 SAA와 연관성을 제시해주었기 때문에 하나의 추가된 장점이라고 생각할 수 있습니다.
더욱이 하나의 액티비티 내에서 프래그먼트들이 종속되어 움직이는 개념이다보니, 액티비티끼리의 인텐트로 상호작용을 하는 것 보다 좀 더 편리하고(activityViewModel, safeArgs 등) 빠르게(액티비티의 경우 각 액티비티가 다른 프로세스에서 실행되는 것을 염두해두어 메모리 영역을 각각 잡게 되어 데이터를 공유하는데 있어 프로세스간 통신이 이루어지는데 반해, 프래그먼트의 경우 동일한 액티비티의 메모리 영역 내에서 움직이는 것이라 리소스가 훨씬 적게 소모됩니다.) 데이터를 주고받을 수 있다는 것도 장점이 될 수 있습니다.
하나의 액티비티 내부의 프래그먼트들에게 중복되는 UI가 있다면 액티비티에 고정시켜두고 사용할 수도 있어 어떤 UI를 사용하느냐에 따라서는 재활용도 간단해질 수 있습니다.
물론 이렇게 장점만 존재하는 것은 아닙니다.
하지만 액티비티가 아닌 프래그먼트를 기준으로 화면을 작성하는 경우 액티비티만 존재하는 것 보다 훨씬 더 많은 라이프사이클로 인해 제대로 설계하지 못하는 경우 예상하지 못한 방향으로 UI가 진행될 수 있습니다. 이는 하나로 퉁 치기에는 매우 큰 단점인데, 액티비티의 생명주기만 해도 여러가지 예외가 존재하는데(onDestroy의 실행보장이 되지 않는 경우, 각 액티비티의 스택 조율등) 프래그먼트까지 얹게 되면 실행되리라 예상했던 부분이 실행되지 않거나, 생명주기가 꼬여 변수 초기화가 되기 전에 참조되는 현상으로 NPE가 뜬다거나 하는 여러가지 이슈를 볼 수 있습니다.
더욱이 액티비티 내부에서 프래그먼트를 기준으로 움직이다보면 액티비티가 빈 껍데기처럼 남아있게 되는데, 이는 액티비티의 개수가 늘어나게 됨에 따라 불필요한 클래스가 증가하는 현상을 야기합니다.
거기다 직접 프래그먼트를 관리하는 것이 아니라, 여러 관리 기능을 사용해서 프래그먼트를 관리하는 경우 프래그먼트를 관리해주는 fragmentManager와 같은 클래스 내부에서 문제가 발생하는 경우 정확하게 어디서 어떤 경로로 문제가 발생했는지 디버깅이 어려워질 수 있습니다.
그래서 극단적으로 이 SAA를 정말 단일 액티비티에 앱에서 사용하는 모든 프래그먼트를 참조하는 형태로 작성하는 경우도 있습니다. 이는 위에서 언급했던 단점인 라이프 사이클 문제를 꽤나 많이 해결해주는데, 결국 하나의 이미 생성된 액티비티 내에서 프래그먼트를 이용하는 것이기 때문에 특정 몇몇 예외를 제외하면 프래그먼트의 라이프사이클에 전부 맞추게 되어 라이프사이클로 야기될 수 있는 많은 문제를 줄일 수 있습니다. 더욱이 빈 껍데기가 오직 하나의 액티비티만 존재하기 때문에 불필요한 클래스의 사용도 최소화할 수 있습니다.
하지만 상용화되어 이런저런 기능들이 존재하는 앱의 화면이 평균적으로 대략 30 ~ 50개 정도가 되는데 이 모든 화면이 하나의 navigation에 담기게 되어 더 이상 가시성에 대한 이익을 볼 수 없고, 오히려 없는 것 만 못한 상황이 발생할 수 있습니다. 또한, 특정 프래그먼트의 앞뒤에 어떤 프래그먼트가 붙더라도 독립성을 유지할 수 있도록 작성하지 않으면, 동일한 기능에 거의 동일한 UI이더라도 새로운 프래그먼트로 발화할 수 있기 때문에 설계 단계에서 많은 부분을 고려해야 합니다.
결국 SAA도 하나의 아키텍쳐이며 프레임워크에서 지원해주는 설계 방식에 지나지 않으므로 설계하는 단계에서 앱이 이 방식대로 구성되는 것이 효율적인지 아닌지를 고민하고 작성해야 앱이 문제 없이 돌아갈 것입니다. 또한 개발자들이 항상 고민하는 재사용성 문제는 비즈니스 로직을 activity가 아니라 view 단위인 fragment로 쪼개게 되면 더욱 많은 것들을 고려해야하기 때문에 개발에 더 많은 시간을 투자해야 할 수도 있습니다.
물론 모든 아키텍쳐나 디자인 패턴들이 늘 그러하듯, 고민하면 할 수록 더 나은 코드를 발생시킬 확률이 높기 때문에 항상 뭐든 하나에 꽃혀 그것만이 정답이라고 생각하는 편협한 사고에서 벗어나 여러가지를 놓고, 현재 가장 필요한 것이 무엇인지에 대해서 철저하게 고민해야 할 것 같습니다.
'안드로이드 > 기타' 카테고리의 다른 글
안드로이드 버전별 점유율 2024.11(업데이트) (3) | 2024.02.25 |
---|---|
TargetSDK 33 버전으로 업데이트 (0) | 2023.08.30 |
ViewBinding vs DataBinding (0) | 2023.01.02 |
viewLifecycleOwner.lifecycleScope vs lifecycleScope (0) | 2022.10.20 |
안드로이드 쓰레드의 통신 과정(Looper, Handler, MessageQueue) (0) | 2022.09.25 |
댓글