성능 및 뷰 계층 구조

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

이 페이지에서는 View 기반 레이아웃을 개선하는 방법을 중점적으로 다룹니다. Jetpack Compose 성능 개선에 관한 자세한 내용은 Jetpack Compose 성능을 참고하세요.

레이아웃 및 측정 성능

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

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

이 같은 경우에 시간이 너무 오래 걸리면 허용된 16ms 이내에 프레임이 렌더링되지 않아 프레임이 드롭되고 애니메이션의 품질이 나빠질 수 있습니다.

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

복잡한 레이아웃 관리

Android 레이아웃을 사용하면 뷰 계층 구조에서 UI 객체를 중첩할 수 있습니다. 이러한 중첩으로 레이아웃 비용이 발생할 수도 있습니다. 앱에서 레이아웃 객체를 처리할 때 앱은 레이아웃의 모든 하위 요소에서도 동일한 프로세스를 실행합니다.

복잡한 레이아웃의 경우 시스템에서 레이아웃을 처음 계산할 때만 비용이 발생하는 경우도 있습니다. 예를 들어 앱에서 RecyclerView 객체의 복잡한 목록 항목을 재활용할 때 시스템에서는 모든 객체를 배치해야 합니다. 다른 예를 들면 사소한 변경 시, 변경사항이 상위 요소의 크기에 영향을 미치지 않는 객체에 이를 때까지 상위 요소를 향해 체인을 전파할 수 있습니다.

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

Layout Editor를 사용하여 RelativeLayout 또는 LinearLayout이 아닌 ConstraintLayout을 만드는 것이 좋습니다. 일반적으로 더 효율적이고 레이아웃의 중첩을 줄이기 때문입니다. 그러나 FrameLayout을 사용하여 얻을 수 있는 간단한 레이아웃에는 FrameLayout을 사용하는 것이 좋습니다.

RelativeLayout 클래스를 사용하고 있다면 가중치가 없는 중첩된 LinearLayout 뷰를 대신 사용하여 더 낮은 비용으로 동일한 효과를 달성할 수 있습니다. 그러나 중첩되고 가중치가 적용된 LinearLayout 뷰를 사용하는 경우 레이아웃 비용이 훨씬 커집니다. 여러 레이아웃 패스가 필요하기 때문입니다(다음 섹션 참고).

또한 ListView 대신 RecyclerView를 사용하는 것이 좋습니다. 개별 목록 항목의 레이아웃을 재활용할 수 있어 더 효율적이고 스크롤 성능을 개선할 수 있기 때문입니다.

이중 과세

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

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

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

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

앞서 언급했듯이 ConstraintLayout은 일반적으로 FrameLayout을 제외한 다른 레이아웃보다 효율적입니다. 다중 레이아웃 패스가 발생할 가능성이 낮으며 대부분의 경우 레이아웃을 중첩할 필요가 없습니다.

RelativeLayout 이외의 컨테이너에도 이중 과세를 증가시킬 수 있습니다. 예:

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

다중 레이아웃 및 측정 패스가 반드시 성능에 부담을 주지는 않습니다. 그러나 잘못된 위치에 있으면 부담이 될 수 있습니다. 다음 조건 중 하나가 컨테이너에 적용되는 상황에 주의하세요.

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

뷰 계층 구조 문제 진단

레이아웃 성능은 여러 측면을 가진 복잡한 문제입니다. 다음 도구를 사용하면 성능 병목 현상이 발생하는 위치를 식별할 수 있습니다. 일부 도구는 덜 명확한 정보를 제공하지만 유용한 힌트를 제공할 수 있습니다.

Perfetto

Perfetto는 성능에 관한 데이터를 제공하는 도구입니다. Android 트레이스는 Perfetto UI에서 열 수 있습니다.

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 및 레이아웃 검사로 레이아웃 디버그를 참고하세요.

뷰 계층 구조 문제 해결

뷰 계층 구조에서 발생하는 성능 문제 해결의 기본 개념은 실제로 어려울 수 있습니다. 뷰 계층 구조로 인한 성능 저하를 방지하는 방법은 뷰 계층 구조를 평평하게 하고 이중 과세를 줄이는 것입니다. 이 섹션에서는 이러한 목표를 실행하기 위한 전략을 설명합니다.

중복된 중첩 레이아웃 삭제

ConstraintLayout은 레이아웃 내에서 뷰를 배치하기 위한 다양한 메커니즘이 있는 Jetpack 라이브러리입니다. 이를 통해 하나의 ConstaintLayout을 중첩할 필요성이 줄어들고 뷰 계층 구조를 평면화할 수 있습니다. 일반적으로 다른 레이아웃 유형에 비해 ConstraintLayout을 사용하여 계층 구조를 평면화하는 것이 더 간단합니다.

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

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> 태그가 도움이 될 수 있습니다. 이 태그에 관한 자세한 내용은 <merge> 태그 사용을 참고하세요.

더 저렴한 레이아웃 채택

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

예를 들어 TableLayout은 위치 종속 항목이 많은 더 복잡한 레이아웃과 동일한 기능을 제공한다는 것을 알 수 있습니다. Jetpack 라이브러리 ConstraintLayoutRelativeLayout와 유사한 기능 외에도 더 평면적이고 효율적인 레이아웃을 만드는 데 도움이 되는 추가 기능을 제공합니다.