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
컨테이너를 사용하면 프레임워크는 다음 시퀀스를 실행합니다.
- 레이아웃 및 측정 패스를 실행하면 그동안 프레임워크는 각 하위 요소의 요청을 기반으로 각 하위 요소 객체의 위치와 크기를 계산합니다.
- 이 데이터를 사용하고 객체 가중치를 고려하여 상호 관련된 뷰의 적절한 위치를 파악합니다.
- 두 번째 레이아웃 패스를 실행하여 객체 위치를 결정짓습니다.
- 렌더링 프로세스의 다음 단계로 이동합니다.
뷰 계층 구조에 레벨이 많을수록 성능 저하가 커질 수 있습니다.
앞서 언급했듯이 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를 선택합니다.
다양한 레이아웃 항목에 관한 정보는 Android > Lint > Performance 아래에 표시됩니다. 자세한 내용은 각 항목을 클릭하여 펼치면 화면 오른쪽 창에 추가 정보가 표시됩니다. 그림 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 라이브러리 ConstraintLayout
는 RelativeLayout
와 유사한 기능 외에도 더 평면적이고 효율적인 레이아웃을 만드는 데 도움이 되는 추가 기능을 제공합니다.