Dagger 2 소개, 안드로이드에서 Dependency Injection 사용하기전에

이 글은 Janishar Ali가 작성한 Introduction to Dagger 2, Using Dependency Injection in Android: Part 1을 번역하였습니다.

Android에서 Dagger2 사용법을 이해하려면 왜 필요한지를 먼저 알아야 합니다. 중요한 질문은 다음과 같습니다.

왜 의존성 주입(Dependency Injection)이 필요한가?
의존성 주입은 Inversion of Control 개념을 바탕으로합니다. 클래스가 외부로부터 의존성을 가져야합니다. 간단히 말해 클래스는 다른 클래스를 인스턴스화해야하지만 구성 클래스에서 인스턴스를 가져와야합니다.
Java 클래스가 new 연산자를 통해 다른 클래스의 인스턴스를 생성하면 해당 클래스와 독립적으로 테스트하고 사용할 수 없으며 이를 하드종속성이라고합니다.

그렇다면 클래스 외부에서 종속성을 제공하면 어떤 이점이 있을까요?
가장 중요한 장점은 클래스를 재사용 할 가능성을 높이고 다른 클래스와 독립적으로 클래스를 테스트 할 수 있다는 것입니다.
이것은 비즈니스 로직의 특정 구현이 아닌 클래스를 생성하는데 매우 효과적입니다.
이제 조금은 이해 했으므로 종속성 삽입 탐색을 진행할 수 있습니다.

큰 문제는 바로 DI(Dependency Injection)를 어떻게 할 것인가입니다.
이 질문에 답하기 위해 우리는 과거를 되돌아 봐야합니다.
종속성 컨테이너라는 프레임워크 클래스는 클래스의 종속성을 분석하는데 사용되었습니다. 이 분석을 통해 Java Reflection을 통해 클래스의 인스턴스를 만들고 정의 된 종속성에 객체를 삽입 할 수있었습니다. 이로 인해 어려운 의존성이 제거되었습니다. 그런 식으로 클래스를 독립적으로 테스트 할 수 있습니다. mock 객체를 사용합니다. 이것은 Dagger 1이었습니다.

이 과정의 주요 단점은 두 가지입니다.
첫째, Reflection 자체가 느림.
번째로, 런타임에 종속성 해결을 수행하여 예기치 않은 충돌 발생.

이것은 Dagger2의 탄생으로 이어졌습니다. Dagger2는 Google의 Square Dagger1에서 분기되었습니다.

Dagger2에서 가져온 큰 변화는 주석 처리기(Annotation Processor)를 사용하여 종속성 그래프를 생성하는 것이 었습니다. 의존성을 제공하는 클래스는 이제 javax injection 패키지를 사용하여 빌드시 생성됩니다. 이것은 응용 프로그램이 실행되기 전에 가능한 오류 검사를 용이하게합니다. 생성된 코드는 직접 작성한것 처럼 보기 쉽습니다.

참고: 주석 처리기는 프로젝트에서 사용할 소스코드 파일을 생성하기 위해 컴파일하는 동안 컴파일 된 파일을 읽는 방법입니다.
그래도 잘 이해 되지 않는다면 다음 파트2의 예제를 보기위해 기다리면 됩니다.

DI작동 방식에 대한 몇가지 정보를 드리겠습니다.
클래스의 종속성을 설명하기위한 표준 Java 주석은 Java Specification Request 330(JSR 330)에 정의되어 있습니다.

Injection 모드 :
1. Constructor Injection: 생성자 삽입.
2. Field Injection: 멤버 변수 삽입 (비공개이면 안됨).
3. Method Injection: 메소드 매개 변수 삽입.

JSR330에 따른 종속성 주입 순서
1. Constructor
2. Field
3. Method

@Inject로 주석처리된 메소드나 필드가 호출되는 순서는 JSR330에 의해 정의되지 않습니다. 메서드나 필드가 클래스에서 선언 된 순서대로 호출된다고 가정 할 수 없습니다.
생성자가 호출 된 후에 필드 및 메서드 매개 변수가 삽입되므로 생성자에서 삽입 된 멤버 변수를 사용할 수 없습니다.

Dagger2를 사용하여 종속성 주입 프로세스를 시각화하는 경향이 있습니다.

종속성 소비자는 커넥터를 통해 종속성 공급자의 종속성(Object)을 필요로합니다.

  1. Dependency provider: @Module로 주석 된 클래스는 삽입 할 수있는 객체를 제공합니다. 이러한 클래스는 @Provides로 주석 된 메소드를 정의합니다. 이 메소드의 리턴 된 오브젝트는 종속성 삽입에 사용 가능합니다.
  2. Dependency consumer: @Inject 어노테이션은 의존성을 정의하는데 사용된다.
  3. Connecting consumer and producer: @Component 주석이 달린 인터페이스는 객체 제공자(모듈)와 의존 관계를 표현하는 객체 사이의 연결을 정의합니다. 이 연결에 대한 클래스는 Dagger에 의해 생성됩니다.

Dagger2의 한계 :
1. Dagger2는 필드를 자동으로 주입하지 않습니다.
2. 비공개 필드를 주입 할 수 없습니다.
3. 필드 주입을 사용하려면 @Component 주석이 달린 인터페이스에서 멤버 변수를 삽입 할 클래스의 인스턴스를 취하는 메소드를 정의해야합니다.

ConstraintLayout으로 아름다운 애니메이션하기

이 글은 Jinyan Cao가 작성한 Beautiful animations using Android ConstraintLayout 번역글입니다.

ConstraintLayout은 날이 갈 수록 인기를 더해가고 있습니다. 수평적인 뷰 계층 구조와 성능을 향상시키고, 임의의 경계 규칙을 지원합니다. 이전 레이아웃의 단점을 모두해결 할 것입니다. ConstraintLayout의 이점 중 하나는 매우 적은 코드로 멋진 애니메이션을 수행 할 수 있습니다. 이는 대부분의 개발자들이 알지못하며, 공식 문서에도 아무것도 언급되어 있지 않습니다.

방법?

ConstraintLayout의 기본 사항을 알고 있다고 가정합니다 (예: app:layout_constraintLeft_toLeftOf 및 다른 속성). 대부분의 문서또는 사이트에서는 새로 개선 된 Android Studio 레이아웃 디자인 패널을 사용하여 다양한 Constraint를 드래그/드롭/시각화 하는 방법만 소개되어 있습니다. 애니메이션의 목적을 위해서는 Constraint를 정확하게 이해하고 있어야만 조작이 가능합니다.

가장 간단한 형식인 TransitionManager(API 19 이상 또는 서포트 라이브러리에서 사용가능)를 통해 두 가지 Constraint 집합간에 애니메이션을 적용 할 수 있습니다. 긴 설명 보다 간단한 예제를 살펴 보겠습니다.

예제

Activity 실행시 초기화되는 XML 레이아웃부터 살펴 보겠습니다.

이것은 화면의 너비와 일치하는 화면 상단에 ImageView를 정의하는 기본 XML 파일입니다. (ConstraintLayout은 match_parent를 지원하지 않습니다.) 이제 하나의 추가 Constraint를 가진 대체 XML 레이아웃을 정의합시다.

여기서 유일한 차이점은 새 XML 레이아웃이 부모의 높이와 일치하는 높이로 설정하는것 입니다. 결과적으로 ImageView는 수직으로 가운데 정렬됩니다.
이제 두 개의 서로 다른 제약 조건 세트 (하나는 ImageView를 수직으로 중심에 배치하지 않고, 하나는 수직으로 가운데 배치)간에 애니메이션을 적용하려면 다음 코드를 Activity에 추가해야합니다.
(참고: 여기서는 Kotlin에 쓰여 있습니다. Kotlin에 익숙하지 않은 분은 Java 코드와 완전히 역 호환되는 동시에 최신 프로그래밍 언어의 많은 이점을 제공하므로 시작하는 것이 좋습니다. 이제 Android에서 공식적으로 지원되는 언어입니다!)

constraintSet1과 constraintSet2는 비 수직 가운데 정렬과 수직 가운데 정렬에 대응하도록 설정합니다. 먼저 TransitionManager에 ConstraintLayout에서 지연된 전환을 시작하라고 지시합니다. 그런 다음 ConstraintLayout에 다른 Constraint 집합을 적용합니다. TransitionManager는 자동으로 애니메이션을 수행하여 Constraint의 변경 사항을 표시합니다.

XML 레이아웃을 복제합니까?

무엇을 생각하는지 알고 있습니다. 이 접근법은 Constraint 변경을 위해 레이아웃 파일을 복제해야합니다. 아무도 중복 된 코드를 좋아하지 않습니다.
이것은 실제로 생각만큼 나쁘지 않습니다. 전환을 위해 대체 XML파일 생성하는 경우 레이아웃의 모든 속성(예 : textSize)을 생략 할 수 있습니다. ConstraintSet은 각 뷰의 Constraint의 속성을 제외한 나머지 속성을 무시합니다. 이렇게하면 두 파일에서 일관된 스타일을 유지할 필요가 없습니다.(예: 원래 XML에서 textSize를 변경하면 대체 XML에서 이를 변경할 필요는 없습니다.)
XML 코드 복제를 하고 싶지 않는 경우, 코드에서 동적으로 속성을 변경하면 됩니다.
위의 예를 하나의 레이아웃 XML로 어떻게 변경하는지 살펴 보겠습니다.

위의 코드에서 constraintSet2에 속성을 코드에서 변경하여 애니메이션을 수행하고 있습니다. 이렇게하면 우리는 Constraint 속성을 그대로 유지하고 코드 복제를 피할 수 있습니다.

하지만 이미 Transition 프레임워크를 사용하여 동일하게 사용할 수 있다!
이것은 전혀 새로운 방식이 아닙니다. Transition 프레임워크 또는 animateLayoutChanges와 같은 속성을 사용하여 동일한 작업을 수행 할 수 있습니다. 그러나 Constraint를 지정할 수 있기때문에 훤씬 강력합니다.
또 다른 이 점은 많은 요소를 애니메이션으로 만들려고 할 때입니다. 이 애니메이션을 살펴 보겠습니다.

Robinhood가 ConstraintLayout을 사용하여 주문 애니메이션을 만듭니다.
Robinhood (Android)의 주문 흐름 애니메이션입니다. 페이지의 모든 단일 요소(카드, 사용자 정의 키패드, FAB 등)를 수동으로 애니메이팅 하도록 구현되어 있습니다. 이 코드는 특히 앞뒤 애니메이션을 따로 작업한다는 점을 감안할 때 읽기에 약간의 문제가 있습니다.
대신이 애니메이션에 ConstraintLayout을 사용하는 샘플 앱을 만들었습니다. 이 구현은 훨씬 간단합니다. 변경되어야 할 속성만 바꾼다음 대체 XML 레이아웃 파일을 지정하면 애니메이션 프레임워크가 모든 것을 애니메이션으로 변환합니다. UI에서 이 애니메이션을 처리하는 코드는 ~250줄에서 ~30줄 간단해졌습니다.

 

더 있다!

ConstraintLayout 애니메이션을 시작하는 데 사용하는 코드를 기억하십니까?

두번째 파라미터를 이용하여 애니메이션을 커스터마이징 할 수있습니다!(기본은 내부에 구현된 기본 Transition 사용) 예를들어 애니메이션 속도를 쉽게 변경할 수 있습니다.

사소한주의 사항

ConstraintLayout 애니메이션으로 사용해본 후, 나는 애니메이션을 구현할 때 고려해야 할 몇 가지주의 사항을 발견했습니다.

  1. ConstraintLayout은 핸들링하는 자식에 대한 속성변경을 알고 있기때문에 직접 자식에 대해서만 애니메이션을 수행합니다. 중첩 된 ViewGroups인 경우 잘 처리 되지 않음을 의미합니다. 위의 예제에서 CardView 내부의 텍스트는 외부 ConstraintLayout에 의해 처리되지 않으므로 코드에서 수동으로 애니메이션을 적용해야합니다. 이것은 아마도 중첩 된 ConstraintLayout을 사용하여 해결할 수 있지만 여기서는 사용하지 않았습니다.

  2. ConstraintLayout은 레이아웃 관련 변경 사항 만 애니메이션으로 나타냅니다. 대체 XML 파일에서 다른 속성 (예: elevation, text)을 읽을 수 없으며 프레임워크가 모든 것을 처리 합니다. ConstraintSet.clone()은 레이아웃/Constraint 변경 사항을 복사하고 다른 모든 항목은 삭제합니다.

  3. constraint-layout:1.0.2에서 ConstraintLayout 속성을 동적으로 변경하면 업데이트 된 속성을 고려하지 않고 애니메이션됩니다.(예: translationY). 즉, 애니메이션을 실행하면 변경전의 속성의 값으로 되돌아 간뒤 새로운 값으로 애니메이션이 적용됩니다.

마치며..

ConstraintLayout을 사용하는 페이지에서 애니메이션을 구현할 함으로 Transition 프레임 워크 보다 많은 기본 레이아웃 변경 애니메이션을 수행 할 수 있습니다. 이를 통해 Activity/Fragment에서 UI/애니메이션 로직을 압축하고 XML로 통합 할 수 있습니다. 또한 더 읽기 쉬운 애니메이션 논리를 만듭니다. (아무도 프로그래밍 방식으로 생성 된 애니메이션을 읽는 것을 좋아하지 않습니다).

안드로이드 어플리케이션 아키텍처

본 글은 Android Application Architecture를 번역한 글입니다.
RxJava입문자부터 MVP 기반의 아키텍처에 대해 알고 싶으신 분들이 보시기에 좋습니다.

안드로이드 개발 생태계는 매우 빠르게 움직입니다. 매주 새로운 툴이 만들어지며 라이브러리가 업데이트되며 블로그의 게시물이 올라오며 커뮤니티에는 많은 문제들로 활발히 논의중입니다. 한 달간 휴가를 다녀온다면 새 버전의 서포트 라이브러리가 당신을 반길 것입니다.

나는 3년 넘게 ribot팀에서 안드로이드 앱 개발을 해왔습니다. 이 기간 동안 사용된 아키텍처와 기술을 지속적으로 발전시켜 왔습니다. 이 글에서는 이러한 아키텍처를 적용하면서 얻은 노하우와 학습방법을 설명할 계획입니다.

이전

2012년 코드 베이스는 기본 구조를 따랐습니다. 네트워킹 라이브러리를 사용하지 않고 AsyncTask를 사용하여 직접 구현하였습니다. 아래 다이어그램은 아키텍처의 대략적인 모습을 보여줍니다.


이 코드는 두 개의 레이어로 구성되어 있습니다.

  • Data Layer: REST API 및 데이터 저장소에서 데이터를 검색/저장하는 역할을 담당.
  • View Layer: UI에서 데이터를 처리하고 표시.

API Provider는 Activity 및 Fragment를 REST API와 쉽게 상호 작용할 수 있게 하는 메서드를 제공합니다. 이러한 메서드는 URLConnection 및 AsyncTask를 사용하여 별도의 스레드에서 네트워크 호출을 수행하고 콜백을 통해 결과를 변환합니다.

비슷한 방식으로 CacheProvider는 SharedPreferences 또는 SQLite에서 데이터를 검색하고 저장하는 역할을 합니다. 또한 콜백을 사용하여 결과를 Activity로 다시 전달합니다.

문제점

이 접근 방식의 큰 문제점은 View Layer가 너무 많은 책임을 가지고 있는 것입니다. 앱이 블로그 게시물의 목록을 로드하고 SQLite 데이터베이스에 저장하고 불러오게끔 캐싱 한 다음 ListView에 표시하는 간단한 일반적인 시나리오를 생각해보십시오.

Activity는 다음과 같은 작업을 합니다.

  1. APIProvider에서 loadPosts(callback) 메서드를 호출합니다.
  2. APIProvider 성공 콜백을 기다린 다음 CacheProvider에서 savePosts(callback)를 호출합니다.
  3. CacheProvider 성공 콜백을 기다렸다가 ListView에 게시물을 표시합니다.
  4. APIProvider 및 CacheProvider에서 발생할 수 있는 두 가지 에러 콜백을 별도로 처리합니다.

 

이것은 아주 간단한 예입니다. 실제 시나리오에서는 REST API가 뷰에서 필요로 하는 데이터만 주지 않습니다. 따라서 Activity는 데이터를 표시하기 전에 필요로 하는 정보로 재 가공해야 합니다. 또 다른 일반적인 경우는 loadPosts() 메서드가 PlayServices SDK에서 제공하는 이메일 주소와 같이 다른 곳에서 가져와야 하는 매개 변수를 사용하는 경우입니다. SDK가 콜백을 사용하여 이메일을 비동기적으로 반환할 가능성이 있습니다. 이런 시나리오인 경우 3번의 중첩된 콜백이 됩니다. 복잡성을 계속 더해가면 이러한 접근방식은 콜백 지옥이 됩니다.

요약하면:

  • Activity와 Fragment의 코드가 많아질수록 유지보수가 어렵습니다.
  • 중첩된 콜백이 많으면 코드를 변경하거나 새로운 기능을 추가하기 어렵고 이해하기가 어렵습니다.
  • Activity와 Fragment에 많은 로직들이 구현되어 있어 유닛 테스팅은 가능하지만 어렵습니다.

 

RxJava가 주도하는 새로운 아키텍처

우리는 약 2년 동안 이전의 접근 방식을 사용해왔습니다. 그동안 위에서 설명한 문제를 약간 완화한 몇 가지 개선 사항을 만들었습니다. 예를 들어 Activity와 Fragment의 코드를 줄이기 위해 몇 가지 Helper 클래스를 추가했으며 APIProvider에서 Volley를 사용하기 시작했습니다. 이러한 변화에도 불구하고 코드는 아직 테스트 친화적이지 못했으며 콜백 지옥 문제는 여전히 자주 발생했습니다.

2014년 RxJava에 대한 기사를 읽기 시작했습니다. 몇 가지 샘플 앱을 만들어 보면서 이것이 중첩된 콜백 문제에 대한 해결책이 될 수 있다는 생각을 했습니다. 반응형 프로그래밍에 익숙하지 않다면 이 소개글을 읽어 보세요. 즉, RxJava를 사용하면 비동기 스트림을 통해 데이터를 관리할 수 있으며, 데이터를 변환 및 필터링 또는 결합을 위해 많은 Operations를 사용할 수 있습니다.

지난 몇 년간 우리가 겪었던 고통을 고려해 볼 때 새로운 앱의 아키텍처가 어떻게 보이는지 그려지기 시작했습니다. 그래서 우리는 이것을 도입하였습니다.

첫 번째 접근 방식과 마찬가지로 이 아키텍처는 Data와 View Layer로 분리될 수 있습니다. Data Layer는 DataManager와 Helper를 포함합니다. View Layer는 Activity, Fragment, ViewGroup 등과 같은 Android 프레임 워크의 구성 요소로 구성됩니다.

Helper 클래스(다이어그램의 3번째 열)는 매우 구체적인 책임을 지며 간결한 방식으로 구현합니다. 예를 들어 대부분의 프로젝트에는 REST API 액세스, 데이터베이스에서 데이터 읽기 또는 SDK와의 상호 작용 위한 Helper가 있습니다.

가장 일반적인 것은 다음과 같습니다.

  • PreferencesHelper: SharedPreferences에서 데이터를 읽고 저장합니다.
  • DatabaseHelper: SQLite 데이터베이스 액세스를 처리합니다.
  • Retrofit Service: REST API에 대한 호출을 수행합니다. RxJava를 지원하기 때문에 Volley 대신 Retrofit을 사용하기 시작했습니다. 또한 사용하는 것이 더 좋습니다.

Helper 클래스 안에 있는 public 메서드의 대부분은 Observable을 리턴합니다.
DataManager는 아키텍처의 뇌에 해당합니다. RxJava 연산자를 광범위하게 사용하여 Helper클래스에서 검색 한 데이터를 결합, 필터링 및 변환합니다. DataManager의 목적은 변환이 필요하지 않은 데이터를 제공하여 Activity나 Fragment에서 작업량을 줄이는 것입니다.

아래 코드는 DataManager 메서드가 어떻게 보이는지 보여줍니다. 아래 예제 메서드는 다음과 같이 작동합니다.

  1. Retrofit Service를 호출하여 REST API에서 블로그 게시물 목록을 로드합니다.
  2. DatabaseHelper를 사용하여 캐싱을 위해 로컬 데이터베이스에 게시물을 저장합니다.
  3. View Layer에서 표시하고자 하는 블로그 게시물만 필터링하므로 오늘 작성된 블로그 게시물만 표시됩니다.


Activity 또는 Fagrment과 같은 View Layer의 구성 요소는 이 메서드를 호출하고 리턴된 Observable를 통해 RecyclerView에 직접 표시할 수 있습니다.

이 아키텍처의 마지막 요소는 이벤트 버스입니다. 이벤트 버스를 사용하면 Data Layer에서 발생하는 이벤트를 브로드캐스트 할 수 있으므로 View Layer의 여러 구성 요소가 이러한 이벤트를 캐치(Subscriptions) 할 수 있습니다. 예를 들어, Observable이 완료되면 DataManager의 signOut() 메서드는 브로드캐스트 이벤트 보내고 이 브로드캐스트를 수신하고 있는 Activity는 UI를 변경하여 로그아웃 상태를 표시할 수 있습니다.

이 방법이 왜 더 좋습니까?
RxJava Observable과 Operators는 중첩된 콜백이 필요 없습니다.

  • DataManager는 이전에 View Layer의 일부였던 책임을 담당합니다. 따라서 Activity와 Fagrment를 더욱 가볍게 만듭니다.
  • Activity, Fagrment에서 DataManager, Helper로 코드를 이동하면 유닛 테스트 작성이 쉬워집니다.
  • DataManager는 Data Layer의 유일한 상호 작용점이기 때문에 테스트 친화적입니다. Helper 클래스 또는 DataManager는 쉽게 변경 가능합니다.

 

여전히 문제점이 있습니까?

  • 크고 복잡한 프로젝트의 경우 DataManager가 너무 커져 유지 관리가 어려울 수 있습니다.
  • Activity 또는 Fagrment와 같은 View Layer의 구성 요소가 더 가벼워졌지만 RxJava Subscriptions 관리, 에러 탐지 등과 관련하여 상당한 양의 작업을 처리해야 합니다.

 

Model View Presenter 통합

작년(2014년)에 MVP 또는 MVVM과 같은 몇 가지 아키텍처 패턴이 Android 커뮤니티에서 인기를 얻었습니다. 샘플 프로젝트기사에서 이러한 패턴을 조사한 결과, MVP가 기존 접근법에 매우 중요한 개선을 가져올 수 있다는 것을 발견했습니다. 현재 아키텍처가 두 개의 레이어(View Layer, Data Layer)로 나누어졌기 때문에 MVP를 추가하는 것이 자연스러웠습니다. 새로운 Presenter Layer를 추가하고 코드의 일부를 View에서 Presenter로 옮겨야 했습니다.

Data Layer는 그대로 유지되지만 패턴 이름과의 일관성을 유지하기 위해 이제는 Model이라고 부릅니다.

Presenter는 Model에서 데이터를 로드하고 결과가 준비되면 View의 설정된 메서드를 호출하는 역할을 담당합니다. 데이터 관리자가 리턴한 Observables에 subscribe 합니다. 따라서 schedulerssubscriptions과 같은 것들을 처리해야 합니다. 또한 필요에 따라 에러 코드 탐지를 하거나 데이터를 재가공하는 등 추가 작업을 할 수 있습니다. 예를 들어 일부 데이터를 필터링해야 하고 이 필터를 다른 곳에서 재사용할 가능성이 없는 경우 데이터 관리자가 아닌 Presenter에서 구현하는 것이 더 적합할 수 있습니다.

아래에서 Presenter의 public 메서드는 어떤 것이 있는지 확인할 수 있습니다. 이 코드는 이전 섹션에서 정의한 dataManager.loadTodayPosts() 메서드에서 반환 한 Observable을 subscribe 합니다.

mMvpView는 이 Presenter가 지원하는 View 구성 요소입니다. 일반적으로 MVP View는 Activity, Fragment 또는 ViewGroup의 인스턴스입니다.
이전 아키텍처와 마찬가지로 View Layer에는 ViewGroup, Fragment 또는 Activity와 같은 안드로이드 프레임 워크 구성 요소가 포함되어 있습니다. 주요 차이점은 이러한 구성 요소가 Observables에 직접 subscribe하지 않는다는 것입니다. 대신 MvpView 인터페이스를 구현하고 showError() 또는 showProgressIndicator()와 같은 간결한 메서드 목록을 제공합니다. 또한 View 구성 요소는 클릭 이벤트와 같은 사용자 상호 작용을 처리하고 그에 따라 설정된 메서드를 Presenter에서 호출하여 작동합니다. 예를 들어 게시물 목록을 로드하는 버튼이 있는 경우 Activity는 onClickListener에서 presenter.loadTodayPosts()를 호출합니다.

이 MVP기반 아키텍처의 전체 예제를 보려면 GitHub의 Android Boilerplate 프로젝트를 확인하거나 ribot의 아키텍처 가이드라인에서 자세한 내용을 읽을 수 있습니다.

이 접근법이 더 좋은 이유는 무엇인가요?
Activity와 Fragment는 매우 가볍습니다. 이것의 유일한 책임은 UI를 설정하거나 업데이트를 하고 사용자 이벤트를 처리합니다. 따라서 유지 관리가 더 쉬워집니다.
이제 View Layer를 변경해도 Presenter를 위한 단위 테스트를 쉽게 작성할 수 있습니다. 이전 코드는 View Layer의 일부가 포함되어 단위 테스트가 쉽지 않았습니다. 전체 아키텍처는 매우 테스트 친화적입니다.
DataManager가 점점 커질 경우 일부 코드를 Presenter로 이동하여 이 문제를 완화할 수 있습니다.

여전히 문제점이 있습니까?
코드가 매우 크고 복잡해지면 단일 데이터 관리자를 갖는 것이 여전히 문제가 될 수 있습니다. 이것이 실제로 문제가 되는 지점에 도달하지 못했지만, 일어날 수 있다는 것을 알고 있습니다.

이것은 완벽한 아키텍처가 아니라는 점은 명백한 사실입니다. 모든 문제를 영원히 해결할 수 있는 완벽한 것이 있다고 생각하지 않는 것이 속 편할 것입니다. Android 생태계는 빠른 속도로 발전해 나갈 것이며 우수한 Android 앱을 계속 개발할 수 있는 더 나은 방법을 찾을 수 있도록 계속 실험해야 합니다. 이 글을 즐겁게 읽었으면 좋겠습니다.

안드로이드의 메모리 누수 패턴

Memory Leak Patterns in Android를 번역한 글입니다.

메모리 누수란?

모든 앱은 작업을 수행하는데 필요한 리소스로 메모리가 필요합니다. Android의 각 앱에 충분한 메모리가 있는지 확인하려면 Android 시스템에서 메모리 할당을 효율적으로 관리해야 합니다. Android 런타임은 메모리가 부족한 경우 가비지 수집(GC)을 트리거합니다. GC의 목적은 더 이상 유용하지 않은 객체를 정리하여 메모리를 회수하는 것입니다. 다음 3단계로 진행됩니다.

  1. GC 루트에서 메모리에 있는 모든 객체 참조를 나열하여 GC 루트의 참조가 있는 활성 객체를 표시합니다.
  2. 나열되지 않아 표시된 모든 객체는 메모리에서 지워집니다.
  3. 살아있는 객체를 다시정렬합니다.
GC 루트에서 나열되는 활성화된 객체 표시

간단히 말해서, 사용자에게 서비스를 제공하는 모든 것을 메모리에 기록해야 하며, 리소스를 확보하기 위해 메모리에서 모든 것을 지워야 합니다.
그러나 사용되지 않는 객체가 사용되는 객체에서 어떻게든 참조되는 좋지 않은 코드로 인해 GC는 사용되지 않은 객체를 유용한 객체로 표시하고 객체를 제거할 수 없게 됩니다. 이를 메모리 누수라고합니다.

메모리 누수

 

왜 메모리 누수는 좋지 않은가?

어떤 객체도 오랫동안 메모리에 기록되어야 합니다. 사용자들에게 실질적인 가치를 제공하기 위해 사용될 수 있는 소중한 자원을 차지합니다. 안드로이드의 경우 다음과 같은 문제가 발생합니다.

1] 메모리 누수가 발생하면 사용 가능한 메모리가 부족하게 됩니다. 결과적으로 안드로이드 시스템은 빈번하게 GC이벤트를 호출합니다. GC이벤트는 모든 이벤트를 멈추게 합니다. GC가 발생하면 UI렌더링과 이벤트 처리가 중단됩니다. 안드로이드는 화면을 16ms로 그립니다. GC가 오래 걸리면 안드로이드는 프레임을 잃어버리기 시작합니다. 일반적으로, 100~200ms이상일 때 사용자가 앱이 느리다는 것을 인지하게됩니다.

안드로이드 화면 그리기
빈번한 GC로 인한 프레임 손실

 

안드로이드에서 애플리케이션 응답은 Activity 매니저와 Window 매니저 시스템 서비스로 모니터링됩니다. 안드로이드는 다음 조건중 하나를 감시하면 특정 응용 프래그램에 대한 ANR 다이얼로그를 표시합니다.

  • 5초 내에 입력 이벤트(키 누름 또는 화면 터치 이벤트)에 대한 응답이 없음 경우.
  • BroadcastReceiver가 10초 내에 실행을 완료하지 않을 경우.
ANR

어떤 사용자도 앱이 응답지연 다이얼로그를 보고 싶어 하지 않는 것을 확신합니다.

2] 앱에 메모리 누수 있는 경우, 객체는 메모리에서 반환될 수 없습니다. 결과적으로 안드로이드 시스템은 더 많은 메모리를 요청합니다. 그러나 한계가 있습니다. 결국 시스템은 앱에 더 많은 메모리를 할당하는 것을 거부합니다. 이렇게 되면 앱은 메모리 부족으로 인해 강제 종료됩니다. 물론 강제 종료를 아무도 좋아하지 않습니다. 사용자는 당장 앱을 제거하거나 앱 리뷰를 나쁘게 줄 것입니다.

3] 메모리 누수 문제는 QA 테스트로 찾기 어렵습니다. 재현하기도 어렵습니다. 그리고 안드로이드 시스템이 메모리 할당을 거부할 때 언제 어디서나 발생할 수 있기 때문에 크래쉬 리포트로 추론하기가 어렵습니다.

 

메모리 누수 확인방법?

메모리 누수를 찾기 위해 GC가 어떻게 작동하는지 잘 이해해야 합니다. 코드 작성과 리뷰를 부지런히 노력해야 합니다. 그러나 안드로이드에는 일부 코드가 의심스러울 때 누수를 예측할 수 있는 유용한 도구가 있습니다.

1] Square에서 만든 Leak Canary라는 메모리 누수를 감지하는데 유용한 도구가 있습니다. 앱의 액티비티들에 대해 약한 참조를 만듭니다. (다른 객체에 감시 기능을 추가하여 커스컴 할 수도 있습니다.) 그런 다음 GC후에 참조가 지워졌는지 확인합니다. 그렇지 않으면. hprof파일로 힙을 덤프 하고 분석하여 누수가 발생했는지 확인합니다. 있는 경우 알림이 표시되고 별도의 앱을 통해 누수가 발생된 위치를 트리 형태로 표시됩니다.

개발자/테스트 빌드에만 Leak Canary를 설치하는 것이 좋습니다. 사용자 빌드를 만들기 전에 개발자와 QA가 미리 메모리 누수를 찾기 위해서입니다.

Leak Canary

2] 안드로이드 스튜디오에는 메모리 누수를 감지하기 위한 좋은 툴이 있습니다. 앱의 일부 Activity가 누수되는 것이 의심된다면 이를 수행하면 됩니다.

1단계: 컴퓨터에 기기 또는 에뮬레이터에서 디버그 모드로 실행합니다.
2단계: 의심스러운 Activity로 이동하고 이전으로 돌아간 뒤 다시 실행합니다.
3단계: 안드로이드 모니터 창의 메모리 섹션에서 GC 시작(Initiate GC) 버튼을 누릅니다. 그 후 Java 힙 덤프(Dump Java Heap) 버튼을 누릅니다.

4단계: Java 덤프 버튼을 누르면 안드로이드 스튜디오에서 덤프 된. hprof 파일을 엽니다. hprof파일 뷰어에는 메모리 누수를 확인할 수 있는 몇 가지 방법이 있습니다. 오른쪽 상단에 있는 Analyzer Tasks 도구를 사용하여 누수되는 Activity를 자동으로 탐지할 수 있습니다. 또는 왼쪽 상단 Class List View를 Package Tree View로 변경하여 Destory 해야 하는 Activity를 찾을 수 있습니다. Activity 객체의 총개수를 확인하세요. 인스턴스가 하나 이상 있으면 누수가 있음을 의미합니다.

5단계: 누수되는 Activity를 찾았다면 하단의 참조 트리(reference tree) 창에서 Activity를 참조하고 있는 객체를 찾으세요.

더 많은 정보는 ‘HPROF Viewer and Analyzer‘에서 확인하실 수 있습니다.

 

일반적인 누수의 패턴은?

안드로이드에서 메모리 누수가 발생하는 이유는 여러 가지가 있습니다. 요약하면 3가지 카테고리로 나눌 수 있습니다.

  1. 정적 참조에 대한 Activity 누수
  2. 작업 스레드에 대한 Activity 누수
  3. 스레드 자체 누수

Github의 SinsOfMemoryLeaks에서 다양한 방식으로 메모리 누수를 시키는 간단한 앱을 만들었습니다.

LEAK 브랜치에서는 메모리 누수가 되는 코드를 볼 수 있습니다. 앞에서 언급한 안드로이드 스튜디오에서 모니터링 툴을 통해 누수를 추적할 수도 있습니다. FIXED 브랜치에서는 누수가 어떻게 수정되었는지 확인할 수 있습니다. 확신이 들지 않는다면 앞서 언급한 도구를 사용하여 실제로 수정되었는지 확인할 수 있습니다. 두 가지 브랜치는 서로 다른 앱 ID이기 때문에 동일한 기기에 설치하여 나란히 사용할 수 있습니다.

다양한 원인을 3가지 카테고리로 나누었는데 하나씩 알아보겠습니다.

정적 참조에 대한 Activity 누수

정적 참조는 앱이 메모리에 있는 한 계속 유지됩니다. Activity는 일반적으로 여러 번 파괴되고 다시 생성되는 생명주기를 가지고 있습니다. 정적 참조에서 Activity를 참조하는 경우 Activity는 생명주기에 의해 Destory 된 후에 GC 되지 않습니다. Activity는 콘텐츠에 따라 수 킬로 바이트에서 많은 경우 메가바이트까지 다양합니다. 복잡한 뷰 계층 구조나 고해상도의 이미지의 경우 많은 양의 메모리가 누수될 수 있습니다.

이 카테고리에서는 다음과 같은 항목이 있습니다.

정적 뷰에 Activity를 참조

정적 변수에 Activity를 참조

싱글톤 객체에 Activity를 참조

Activity의 내부 클래스를 정적으로 참조

작업 스레드에 대한 Activity 누수

Activity는 작업 스레드보다 오래 지속될 수 있습니다. Activity보다 더 오래 작업하는 스레드에서 Activity를 참조하면 누수가 발생합니다. 이 카테고리에도 다음과 같은 몇 가지 항목이 있습니다.

스레드에서 Activity 참조

Handler에서 Activity 참조

AsyncTask에서 Activity참조
스레드에서 Activity 참조와 동일하게 AsyncTask의 기술인 스레드풀, ExecutorService에도 동일한 원칙이 적용됩니다.

스레드 자체 누수

Activity에서 스레드를 시작할 때마다 스레드를 직접 관리해야 합니다. 스레드는 Activity보다 오래 작업할 수 있기 때문에 Activity가 소멸되면 스레드를 중지시켜야 합니다. 이렇게 하지 않으면 스레드가 누수될 위험이 있습니다.

 

까다로운 메모리 누수?

이상적으로 메모리 누수를 일으키는 코드를 작성하지 말아야 하며 존재하는 메모리 누수 문제를 수정해야 합니다. 그러나 실제로 다른 작업으로 인해 메모리 누수 수정의 우선순위를 판단하기 힘든데, 다음 3가지 목록을 통해 심각도를 평가할 수 있습니다.

1. 누수된 메모리는 얼마나 큰가?
모든 메모리 누수는 동일하지 않습니다. 일부는 몇 킬로바이트만 누설합니다. 일부는 많은 양의 메가바이트까지 유출할 수 있습니다. 앞서 언급한 도구를 사용하여 누설되는 메모리 크기를 측정하여 사용자의 기기에서 얼마나 중요한 용량인지 여부를 통해 결정할 수 있습니다.

2. 누수된 객체는 얼마나 오랫동안 메모리에 상주하는가?
스레드를 통한 누수는 스레드의 작업이 완료될 때까지 지속됩니다. 스레드가 최악의 시나리오에서 얼마나 오랫동안 지속되는지 검사를 해야 합니다. 예제에서 스레드는 무한루프이기 때문에 누수되는 객체는 영원히 메모리에 상주합니다. 실제로 대부분의 스레드가 파일 시스템에 액세스 하거나 네트워크 호출을 하는 등의 작업을 수행하기 때문에 일반적으로 시간이 제한되어 있는 짧은 시간일 것입니다. 발생할 수 있는 최대 시간은 메모리 누수를 수정의 우선순위를 결정할 때 고려해야 할 사항입니다.

3. 얼마나 많은 객체가 누수되는가?
예제의 정적 참조와 같이 메모리 누수는 하나의 객체에서만 나타납니다. 새로운 Activity가 생성되자 말자, 레퍼런스는 새로운 Activity를 참조되기 시작됩니다. 이전 Activity는 GC수집 대상이 됩니다. 따라서 최대 누출은 개수는 Activity 인스턴스 한 개입니다. 그러나 누수가 있는 경우 새 객체가 생성될 때마다 Activity 인스턴스 개수가 늘어납니다. 예제에서도 Activity가 생성될 때마다 스레드에 Activity에 참조가 걸려 누수가 발생됩니다. 따라서 기기를 20번 회전시키는 동안 20개의 스레드가 누수됩니다. 새로운 인스턴스가 GC에 의해 정리되지 않는 다면 사용 가능한 모든 메모리가 점점 줄어들기 때문에 정말로 나쁜 상황이 생길 수 있습니다.

 

고치거나 피하는 방법은?

Activity 클래스에서 정적 변수를 사용할 때 매우 주의해야 합니다. 정적 변수가 Activity를 직접 또는 간접적으로 참조할 가능성이 있는 경우 onDestory에서 참조를 끊어야 합니다. Manager 인스턴스 또는 싱글톤 객체에 리스너로 Activity를 전달할 때, 전달한 Activity 인스턴스로 다른 객체가 무엇을 하는지 알고 있어야 합니다. 필요한 경우 onDestory에서 리스너를 null로 설정하세요.

Activity 클래스에 내부 클래스를 만들 때 가능한 정적으로 만듭니다. 내부 클래스와 익명 클래스에는 암시적 참조가 있습니다. 따라서 내부/익명 클래스의 인스턴스가 포함된 클래스보다 오랫동안 유지되면 문제가 발생합니다. 누수의 위험을 피하기 위해 내부/익명 클래스가 아닌 정적 클래스를 사용하면 됩니다.

싱글톤 또는 Manager클래스를 만들 경우 Listener 인스턴스의 참조를 저장하여 관리할 수 있게 해야 하며, 사용자에 의해 참조를 관리할 수 없는 경우 Listener를 WeakReference로 관리하게 합니다. WeakReference는 GC에서 해당 대상을 지우지 않고 다시 회수하지 못하게 합니다. 이 기능은 메모리 누수를 막는데 큰 도움이 되지만 참조된 객체가 필요할 때 사용하지 못하는 부작용도 있을 수 있습니다. 따라서 메모리 누수를 수정하기 위해 가장 마지막 수단으로 사용해야 합니다. Activity에서 시작한 스레드 작업은 onDestory에서 항상 종료하세요.

 

마치며

우리는 메모리 누수가 무엇인지, 어떻게 발생하는지, 안드로이드 시스템에서 어떤 결과가 발생하는지에 대해 알아보았습니다. 메모리 누수를 탐지하고 식별하는 도구와 안드로이드의 일반적인 메모리 누수 패턴을 검사하는 방법, 심각도를 평가하는 방법과 피하거나 수정하는 방법을 소개하였습니다. Github저장소에서 일반적인 메모리 누수 패턴 및 수정에 대한 코드 예제를 한 번씩 확인해보세요. 모두 행복한 안드로이드 앱을 만드세요:)

 

참고:
https://developer.android.com/training/articles/perf-anr.html
https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works/
https://developer.android.com/studio/profile/am-hprof.html
https://developer.android.com/reference/java/lang/ref/WeakReference.html
https://medium.com/google-developer-experts/finally-understanding-how-references-work-in-android-and-java-26a0d9c92f83#.h9w7hp13h

안드로이드 개발력 향상하기

안드로이드 앱개발에 필요한 팁과 학습방법에 관한 글입니다. 아래 정보를 잘 활용하여 안드로이드 개발 학습에 도움이 되거나 실무에 적용하여 업무에 도움이 되기를 바랍니다.

안드로이드 스튜디오의 ‘라이브 템플릿’을 사용하여 개발 향상

아래 링크는 안드로이드 스튜디오 팁을 모아둔 곳입니다.
https://plus.google.com/u/0/collection/wtO0PB
여기서 가장 쉽고 빠르게 쓸 수 있는 기능 중 하나는 라이브 템플릿 기능으로, 반복되는 메서드를 임의로 지정한 약자를 입력하면 풀네임으로 자동변경되는 기능입니다.
ex) fbc + Enter -> findViewById로 변환

라이브 템플릿 기능에 대해서 아래 링크를 참고하면 좀 더 많은 정보를 확인할 수 있습니다.
https://www.bignerdranch.com/blog/android-studio-live-templates

안드로이드에서 기본적으로 지원해주는 라이브 템플릿외에도 사용자가 커스텀하게 만들 수도 있습니다. 아래 링크는 많이 사용되는 메서드들에 대한 라이브 템플릿을 커스텀하여 공개하고 있습니다.
https://github.com/keyboardsurfer/idea-live-templates

라이브 템플릿은 반복되는 메서드 입력을 최대한 줄여 개발 시간을 단축시킬 수 있는 가장 좋은 방법입니다.
https://medium.com/@aditlal/must-have-tools-for-android-development-d76ae66f409f#.qhhck9bvk

앱을 디버깅하는 동안 안드로이드 스튜디오와 함께 사용할 수 있는 도구

Library methods count – 안드로이드의 DEX파일 포맷 구조상 65만 개의 메서드로 제한되어 있습니다. 해당 툴을 이용하면 안드로이드 라이브러리의 메서드 개수를 확인 할 수 있습니다.

Stetho – 페이스북에서 만든 안드로이드 앱을 쉽게 감시할 수 있는 라이브러리입니다. 네트워크 트래픽을 디버깅하는데 가장 좋습니다. 이뿐만 아니라 SQLite 데이터베이스, 쉐어드 프리퍼런스도 쉽게 감시할 수 있습니다. 크롬 브라우저의 인스팩트 기능을 이용하기 때문에 웹 개발경험이 있다면 훨씬 쉽게 사용할 수 있습니다.

LeakCanary – 메모리 릭을 감지해주는 라이브러리로 코드 한 줄만으로 쉽게 사용 가능합니다. 릭 탐지 시 별도의 UI화면으로 발생되는 위치와 경로를 알려줍니다.

Gradle, Please – Gradle기반의 라이브러리를 쉽게 찾아줍니다. 라이브러리 이름만 입력하면 최신 버전으로 라이브러리를 컴파일할 수 있는 구문을 찾아줍니다.

Android Arsenal – 안드로이드와 관련된 라이브러리를 한 번에 볼 수 있습니다. 항상 최신의 버전을 유지하며 카테고리 기반으로 빠르게 업데이트됩니다.

Android UI OpenSource – 안드로이드 UI와 관련된 오픈소스를 한 번에 볼 수 있습니다.

AndroidTool Mac – 맥 개발자들을 위한 안드로이드 툴입니다. UI 환경에서 빠르게 화면 캡처, 비디오 캡처, APK 끌어서 설치를 할 수 있습니다.

ButterKnifeZelezny – 안드로이드 스튜디오 플러그인으로 선택한 레이아웃 XML에서 버터나이프 인젝션으로 변환해줍니다.

Adb-idea – ADB명령을 쉘이 아닌 안드로이드 스튜디오의 액션 창에서 작동할 수 있는 플러그인입니다.

AndroidWeekly – 매주 최신의 안드로이드 관련 소식을 메일링으로 받아 볼 수 있습니다.

Android Developers Youtube Channel – 구글 안드로이드 공식 유튜브 채널이며, 가장 최신의 안드로이드 기술을 접할 수 있습니다.

완성된 앱으로 학습하기 

Plaid – 디자인 뉴스와 영감을 제공하기 위한 안드로이드 앱입니다. 안드로이드 UI처리에 대해 전반적인 학습을 할 수 있습니다.

Kickstarter – 구글의 킥스타터 앱으로 예술, 디자인, 영화, 게임 음악 등으로 구성된 수천 개의 프로젝트를 탐색할 수 있습니다. 디자인 가이드라인부터 최신 기술을 한 번에 공부할 수 있습니다.

CoordinatorLayout과 Behavior의 관계

머트리얼 디자인 가이드 라인중 스크롤시 다양한 반응을 위한 테크닉인 Behavior라는 개념이 도입 되었습니다. 기본적으로 액션바를 확장하여 스크롤시 액션바를 줄여들게 하도록 AppBarLayout의 ScrollingViewBehavior와 스크롤시 하단으로 숨기게 하기위해 BottomSheetBehavior를 서포트라이브러리에서 제공하고 있습니다.

  • android.support.design.widget.AppBarLayout$ScrollingViewBehavior
  • android.support.design.widget.BottomSheetBehavior

Behavior를 사용하기 위해서는 CoordinatorLayout을 통해서 사용되는데, CoordinatorLayout은 자식뷰의 스크롤의 변화 상태를 다른 자식뷰들에게 전달 해주는 역할을 합니다. 좀더 쉽게 말해 NestedScrollView나 RecyclerView등에 스크롤의 상태를 판단하여 정의된 반응을 하기위한 View에 Behavior를 등록하면 됩니다.

이해를 돕기위해 안드로이드 서포트 라이브러리에서 제공해주는 Behavior를 한번 보겠습니다. NestedScrollView에 layout_behavior에 AppBarLayout$ScrollingViewBehavior가 정의가 되어있습니다. NestedScrollView의 반응에 따라 AppBarLayout이 반응됩니다.

CoordinatorLayout는 NestedScrollView가 스크롤시 layout_behavior에 정의된 레이아웃으로 스크롤 정보를 전달 하는 역할을 합니다. 그럼 AppBarLayout의 ScrollingViewBehavior가 정보를 받아서 AppBarLayout 자신을 변형하도록 하는 구조입니다.

CoordinatorLayout이 스크롤되는 것은 Behavior에 구현된 NestedScrollingParent를 통해 전달 됩니다. 즉, CoordinatorLayout는 NestedScrollingParent가 구현되어 있으며 스크롤 되는 View들은 NestedScrollingChild가 구현되어 있어야 Behavior가 전달 됩니다. 그렇기 때문에 기존의 ScrollView나 ListView는 NestedScrollingChild가 구현되어 있지 않아 Behavior를 통해 스크롤 정보전달이 되지 않습니다.

이렇게 CoordinatorLayout의 역할과 Behavior의 관계를 알고 있다면 Behavior를 커스텀해서 구현하는데 전혀 문제 없을 것입니다.

레이아웃 비동기로 인플레이트하기

안드로이드 서포트 라이브러리 리비전 24부터 레이아웃을 비동기로 인플레이트 할 수 있는 클래스가 추가되었다.

AsyncLayoutInflater

어싱크 레이아웃 인플레이터를 사용하면 무거운 인플레이션이 수행되는 동안 UI스레드에 대한 응답성을 보장할 수 있다. 기존의 레이아웃 인플레이터를 사용하는 경우 UI스레드에서 인플레이션 작업이 수행되어 무거운 레이아웃의 경우 화면이 끊기거나 심한 경우 ANR이 발생된다. 이러한 문제를 보완하기 위해 AsyncLayoutInflater를 제공한다. 별도의 스레드에서 인플레이션을 수행하고 생성된 뷰를 UI스레드로 콜백 받을 수 있다.

사용방법은 레이아웃 인플레이터와 동일하게 AsyncLayoutInflater로 클래스를 생성 후 inflate() 메서드를 호출하면 된다.

AsyncLayoutInflater(@NonNull Context context)

inflate() 메서드 호출 시 인플레이트 대상의 레이아웃 리소스와 인플레이트 된 뷰를 속하게 하고 싶은 뷰 그룹과 콜백 받기 위한 OnInflateFinishedListener의 파라미터가 필요하다.

inflate(@LayoutRes int resid, @Nullable ViewGroup parent, @NonNull OnInflateFinishedListener callback)

생성이 완료되면 OnInflateFinishedListener의 onInflateFinished()를 통해 뷰를 전달된다.

AsyncLayoutInflater은 UI스레드가 아닌 별도의 스레드를 통해서 레이아웃을 인플레이트 하기 때문에 무거운 레이아웃으로 인해 화면의 응답성이 떨어지는 경우 사용할만하다. 액티비티 시작 시 레이아웃이 너무 큰 경우라도 화면의 전환 속도가 느리다면 로딩화면을 심플하게 구현 후 다음 비동기 처리를 통해 뷰를 생성하는 것도 하나의 방법이 될 수 있을 것이다.

구현 예시