성능 및 뷰 계층 구조

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

레이아웃 및 측정 성능

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

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

이 같은 경우에 시간이 너무 오래 걸리면 허용된 16ms 이내에 프레임이 렌더링되지 않게 되어 프레임이 끊기고 애니메이션의 품질이 나빠집니다.

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

복잡성 관리: 레이아웃의 중요성

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

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

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

이중 과세

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

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

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

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

RelativeLayout 이외의 컨테이너에도 이중 과세가 발생할 수 있습니다. 예:

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

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

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

뷰 계층 구조 문제 진단

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

Systrace

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

GPU 렌더링 프로파일링

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

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

린트

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

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

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

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

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

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

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과 유사한 기능을 상당히 낮은 비용으로 제공합니다.