이제 Android 11 개발자 프리뷰를 사용할 수 있습니다. 테스트해 보고 의견을 공유하세요.

성능 및 뷰 계층 구조

View 개체의 계층 구조를 관리하는 방법은 앱 성능에 상당한 영향을 미칠 수 있습니다. 이 페이지에서는 뷰 계층 구조가 앱 속도를 저하시키는지를 평가하는 방법을 설명하고 발생할 수 있는 문제의 해결 전략 몇 가지를 제공합니다.

레이아웃 및 측정 성능

렌더링 파이프라인에는 레이아웃 및 측정 단계가 포함되며 이 단계에서 시스템은 뷰 계층 구조의 관련 항목을 적절하게 배치합니다. 이 단계의 측정 부분은 View 개체의 크기와 경계를 결정합니다. 레이아웃 부분은 화면에서 View 개체를 배치할 위치를 결정합니다.

이러한 두 파이프라인 단계에서는 모두 처리하는 뷰 또는 레이아웃당 약간의 비용이 발생합니다. 대부분의 경우 이 비용은 최소화되며 성능에 크게 영향을 미치지 않습니다. 그러나 RecyclerView 개체가 뷰 개체를 재활용하거나 재사용할 때와 같이 앱에서 뷰 개체를 추가하거나 삭제할 때 비용이 커질 수 있습니다. View 개체에서 기본 제약 조건에 크기 조절을 고려해야 한다면 비용은 더 커질 수도 있습니다. 예를 들어 앱에서 텍스트를 래핑하는 View 개체의 SetText()를 호출하면 View는 크기를 조절해야 할 수 있습니다.

이 같은 경우에 시간이 너무 오래 걸리면 허용된 16분 이내에 프레임이 렌더링되지 않도록 하여 프레임이 삭제되고 애니메이션의 품질이 나빠집니다.

이러한 작업을 작업자 스레드로 옮길 수 없기 때문에(앱의 기본 스레드에서 처리해야 함) 작업을 최적화하여 가능한 한 적은 시간이 걸리도록 하는 것이 가장 좋습니다.

복잡성 관리: 레이아웃 문제

Android 레이아웃을 통해 뷰 계층 구조에서 UI 개체를 중첩할 수 있습니다. 이러한 중첩으로 레이아웃 비용이 발생할 수도 있습니다. 앱에서 레이아웃 개체를 처리할 때 앱은 레이아웃의 모든 하위 요소에서도 동일한 프로세스를 실행합니다. 복잡한 레이아웃의 경우 시스템에서 레이아웃을 처음 계산할 때만 비용이 발생하는 경우도 있습니다. 예를 들어 앱에서 RecyclerView 개체의 복잡한 목록 항목을 재활용하면 시스템에서는 모든 개체를 배치해야 합니다. 다른 예를 들면 사소한 변경으로 상위 요소의 크기에 영향을 미치지 않는 개체에 도달할 때까지 상위 요소를 향해 체인을 전파할 수 있습니다.

특히 오랜 시간이 걸리는 레이아웃의 가장 일반적인 경우는 View 개체의 계층 구조가 서로 중첩되어 있을 때입니다. 중첩된 각 레이아웃 개체는 레이아웃 단계에 비용을 추가합니다. 계층 구조가 평평할수록 레이아웃 단계를 완료하는 데 드는 시간이 줄어듭니다.

RelativeLayout 클래스를 사용하고 있다면 중첩된 비가중 LinearLayout 뷰를 대신 사용하여 더 낮은 비용으로 동일한 효과를 달성할 수 있습니다. 또한 앱이 Android 7.0(API 레벨 24)을 타겟팅하고 있다면 특수 레이아웃 편집기를 사용하여 RelativeLayout 대신 ConstraintLayout 개체를 만들 수 있습니다. 이를 통해 이 섹션에서 설명하는 여러 문제를 피할 수 있습니다. ConstraintLayout 클래스는 유사한 레이아웃 컨트롤을 제공하지만 성능이 크게 개선되었습니다. 이 클래스는 자체 제약 조건 해결 시스템을 사용하여 표준 레이아웃과는 매우 다른 방식으로 뷰 사이의 관계를 해결합니다.

이중 과세

일반적으로 프레임워크는 단일 패스로 상당히 빠르게 레이아웃 또는 측정 단계를 실행합니다. 그러나 좀 더 복잡한 레이아웃의 경우 프레임워크는 궁극적으로 요소를 배치하기 전에 해결하기 위해 다중 패스가 필요한 계층 구조 부분에서 여러 번 반복해야 할 수 있습니다. 레이아웃 및 측정 반복을 두 번 이상 실행해야 하는 것을 이중 과세라고 합니다.

예를 들어 다른 View 개체의 위치와 관련하여 View 개체를 배치할 수 있게 하는 RelativeLayout 컨테이너를 사용하면 프레임워크는 다음 작업을 실행합니다.

  1. 레이아웃 및 측정 패스를 실행하고 그러는 동안 프레임워크는 각 하위 요소의 요청에 기반해 각 하위 요소 개체의 위치와 크기를 계산합니다.
  2. 이 데이터를 사용하고 개체 가중치도 고려하여 상호 관련된 뷰의 적절한 위치를 파악합니다.
  3. 두 번째 레이아웃 패스를 실행하여 개체 위치를 결정짓습니다.
  4. 렌더링 프로세스의 다음 단계로 넘어갑니다.

뷰 계층 구조에 레벨이 많을수록 성능 저하가 커질 수 있습니다.

RelativeLayout 이외의 컨테이너에도 이중 과세가 발생할 수 있습니다. 예를 들면 다음과 같습니다.

  • LinearLayout 뷰를 수평으로 만들면 이중 레이아웃 및 측정 패스가 발생할 수 있습니다. 이중 레이아웃 및 측정 패스는 measureWithLargestChild를 추가하면 수직 방향에도 발생할 수 있습니다. 이 경우 프레임워크는 개체의 적절한 크기를 결정하기 위해 두 번째 패스를 실행해야 할 수도 있습니다.
  • GridLayout에도 비슷한 문제가 있습니다. 이 컨테이너도 상대 위치를 허용하지만 일반적으로 하위 뷰 사이의 위치 관계를 사전 처리하여 이중 과세를 방지합니다. 그러나 레이아웃에서 Gravity 클래스로 가중치 또는 fill을 사용하면 사전 처리의 이점이 사라지고 프레임워크는 컨테이너가 RelativeLayout라면 다중 패스를 실행해야 할 수 있습니다.

다중 레이아웃 및 측정 패스 자체는 성능에 부담이 되지 않습니다. 그러나 잘못된 자리에 있다면 성능 부담이 될 수 있습니다. 다음 조건 중 하나가 컨테이너에 적용되는 상황에 주의해야 합니다.

  • 뷰 계층 구조의 루트 요소입니다.
  • 아래에 깊은 뷰 계층 구조가 있습니다.
  • ListView 개체의 하위 요소와 비슷하게 화면을 채우는 인스턴스가 많습니다.

뷰 계층 구조 문제 진단

레이아웃 성능은 여러 측면에서 복잡한 문제입니다. 성능 병목 현상이 발생하는 위치를 확실하게 알려주는 두 가지 도구가 있습니다. 다른 몇 가지 도구는 조금 덜 명확한 정보이긴 하지만 유용할 수 있는 힌트를 제공합니다.

Systrace

성능에 관한 우수한 데이터를 제공하는 도구 중 하나가 Android SDK에 빌드된 Systrace입니다. Systrace 도구를 통해 전체 Android 기기에서 타이밍 정보를 수집하고 검사하여 레이아웃 성능 문제가 언제 성능 문제를 일으키는지 알 수 있습니다. Systrace에 관한 자세한 내용은 시스템 추적 개요를 참조하세요.

프로필 GPU 렌더링

성능 병목 현상에 관한 명확한 정보를 제공할 수 있는 다른 도구는 Android 6.0(API 레벨 23) 이상이 지원되는 기기에서 사용할 수 있는 기기 내 프로필 GPU 렌더링 도구입니다. 이 도구를 사용하면 레이아웃 및 측정 단계에서 각 렌더링 프레임에 걸리는 시간을 확인할 수 있습니다. 이러한 데이터로 런타임 성능 문제를 진단할 수 있고 해결해야 할 레이아웃 및 측정 문제가 있는지를 판단할 수 있습니다.

캡처한 데이터의 그래픽 표현에서 프로필 GPU 렌더링은 파란 색상을 사용하여 레이아웃 시간을 나타냅니다. 이 도구를 사용하는 방법에 관한 자세한 내용은 프로필 GPU 렌더링 둘러보기를 참조하세요.

Lint

Android 스튜디오의 Lint 도구를 사용하면 뷰 계층 구조에 있는 비효율성을 판단할 수 있습니다. 이 도구를 사용하려면 그림 1과 같이 Analyze > Inspect Code를 선택합니다.

그림 1. Android 스튜디오에서 Inspect Code 찾기

다양한 레이아웃 항목에 관한 정보는 Android > Lint > Performance 아래에 표시됩니다. 각 항목을 클릭하여 펼치면 화면 오른쪽 창에서 자세한 내용을 볼 수 있습니다. 그림 2는 이러한 디스플레이의 예를 보여줍니다.

그림 2. lint 도구에서 식별된 특정 문제에 관한 정보 보기

이 항목 중 하나를 클릭하면 오른쪽 창에 항목과 관련된 문제가 표시됩니다.

이 영역의 특정 주제 및 문제에 관한 자세한 내용은 Lint 문서를 참조하세요.

Layout Inspector

Android 스튜디오의 Layout Inspector 도구를 사용하면 앱의 뷰 계층 구조를 시각적으로 표현할 수 있습니다. 앱의 계층 구조를 탐색하여 특정 뷰의 상위 체인을 시각적으로 분명하게 표현하고 앱에서 생성하는 레이아웃을 검사할 수 있는 좋은 방법입니다.

Layout Inspector에서 제공하는 뷰를 통해 이중 과세로 발생하는 성능 문제도 식별할 수 있습니다. 또한, 이 도구를 사용하면 중첩된 레이아웃의 딥 체인이나 중첩된 하위 요소가 많은 레이아웃 영역(성능 비용의 또 다른 잠재적 원인)을 쉽게 식별할 수 있습니다. 이러한 시나리오에서 레이아웃 및 측정 단계에는 특히 비용이 많이 들기 때문에 성능 문제가 발생할 수 있습니다.

자세한 내용은 layout inspector로 레이아웃 디버그를 참조하세요.

뷰 계층 구조 문제 해결

뷰 계층 구조에서 발생하는 성능 문제 해결의 기본 개념은 개념상으로는 간단하지만 실제로는 더 어렵습니다. 뷰 계층 구조로 인한 성능 저하를 방지하는 데는 뷰 계층 구조를 평평하게 하고 이중 과세를 줄이는 두 가지 목표가 포함됩니다. 이 섹션에서는 이러한 목표를 실행하기 위한 몇 가지 전략을 설명합니다.

중복된 중첩 레이아웃 삭제

개발자는 종종 필요한 것보다 많은 중첩된 레이아웃을 사용합니다. 예를 들어 RelativeLayout 컨테이너에는 역시 RelativeLayout 컨테이너인 단일 하위 요소가 포함될 수 있습니다. 이 중첩은 중복이 되고 뷰 계층 구조에 불필요한 비용을 추가합니다.

Lint에서는 이러한 문제를 신고하는 경우가 많아서 디버깅 시간을 줄일 수 있습니다.

merge/include 채택

중복된 중첩 레이아웃의 한 가지 빈번한 원인은 <include> 태그입니다. 예를 들어 재사용 가능한 레이아웃을 다음과 같이 정의할 수 있습니다.

    <LinearLayout>
        <!-- some stuff here -->
    </LinearLayout>
    

그런 다음 include 태그를 사용하여 이 항목을 상위 컨테이너에 추가합니다.

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/app_bg"
        android:gravity="center_horizontal">

        <include layout="@layout/titlebar"/>

        <TextView android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:text="@string/hello"
                  android:padding="10dp" />

        ...

    </LinearLayout>
    

include는 두 번째 레이아웃 내에 첫 번째 레이아웃을 불필요하게 중첩합니다.

이 문제를 방지하는 데는 merge 태그가 도움이 될 수 있습니다. 이 태그에 관한 자세한 내용은 <include>로 레이아웃 재사용하기를 참조하세요.

더 저렴한 레이아웃 채택

중복 레이아웃이 포함되지 않도록 기존 레이아웃 구성표를 조정하지 못할 수 있습니다. 어떤 경우에는 유일한 해결책이 완전히 다른 레이아웃 유형으로 전환하여 계층 구조를 평평하게 하는 것일 수 있습니다.

예를 들어 TableLayout가 위치 종속성이 많은 더 복잡한 레이아웃과 동일한 기능을 제공한다는 것을 알 수 있습니다. Android의 N 출시에서 ConstraintLayout 클래스는 RelativeLayout와 유사한 기능을 상당히 낮은 비용으로 제공합니다.