안드로이드 View가 렌더링 되는 과정

XML로 작성한 View가 어떻게 최종적으로 화면에 렌더링 되는지 알아보자.

좋은 퍼포먼스를 내기 위해서는 내부의 동작 방식이나 원리를 잘 알고있어야 한다. 만약 하드웨어가 무엇을 어떻게 하고 있는지 잘모른다면, 이를 이용하기도 쉽지 않다. 렌더링을 하는방식에 대해 알아보기전에 View의 생명주기에 대해서 알아보자.

View의 생명주기

많은 개발자들이 View가 생명주기를 가지고 있다는 사실을 고려하지 않고 개발을 하고 있다. 하지만 커스텀뷰를 만들고, 좋은 퍼포먼스를 내는 앱을 만들기 위해서는 뷰의 생명주기를 잘 알고 있어야한다. 다음 이미지를 확인해보자.

각 생명주기 콜백 메서드는 특별한 목적을 가지고 있다. 

– onMeasure() : 요구된 뷰와 차일드뷰의 사이즈가 결정되면 호출된다.
– onLayout() : 뷰와 차일드뷰의 사이즈와 포지션을 적용할 때 호출된다.
– onDraw() : 뷰가 화면에 콘텐츠(텍스트, 이미지 등)를 그릴 준비가 되었을 때 호출 된다.

뷰의 생명주기에 대한 좀 더 자세한 설명은 이전 포스팅을 참고할 수 있다.

Rasterization(래스터화)

래스터화는 문자열, 버튼 또는 도형과 같은 객체들을 픽셀로 변환시키고 스크린상의 텍스쳐로 나타내는 과정을 말한다. 래스터화는 비용이 큰 작업에 속한다. 그러므로 GPU(Graphics Processing Unit)가 래스터화 가속을 위해 사용된다. GPU는 폴리곤, 텍스쳐 등을 계산하고 처리하는데 특화되어 설계되었다(병렬처리)

CPU는 화면에 무언가 그리기 위해 GPU에게 폴리곤이나 텍스쳐의 래스터화를 위임하고 GPU의 최종적인 처리결과가 화면에 나타나게 된다.

이러한 CPU와 GPU의 데이터 전송 처리는 일반적으로 OpenGL ES API의 호출에 의해 일어난다. 안드로이드에서 점,선,면을 포함한 버튼, 이미지 등의 UI객체가 화면에 나타나기 위해서는 항상 OpenGL ES를 거치게 된다.

참고로, OpenGL은 3개의 정점을 이어 하나의 삼각형(면)을 이루고 이를 폴리곤이라한다. 폴리곤이 모여 메쉬를 이룬다.

한가지 예를 들어보자.

아주 간단한 UI, 예를 들어 버튼을 화면에 렌더링하는 과정을 상상해보자. 우선 버튼이 있어야하고, 이를 CPU에 의해 폴리곤과 텍스쳐로 변환시킨 뒤 GPU에게 넘겨지게 된다.

위에서 언급했듯이 이렇게 UI객체를 메쉬로 변환하는 과정은 비용이 큰작업이다. 모든 변환과정이 끝난 뒤 변환된 데이터가 OpenGL ES API에 의해 CPU에서 GPU로 전달되게 된다.

GPU에 업로드된 UI오브젝트 데이터는 GPU에 메모리상에 적재된 상태로 다음 프레임을 그릴때 재사용되므로, 매번 변환과정을 거칠 필요는 없다. 단지 GPU상에 업로드된 데이터를 참조하여 어떻게 그릴지 OpenGL ES에게 알려주기만 하면된다.

결국 렌더링 퍼포먼스를 최적화한다는 의미는 GPU 메모리에 데이터를 얼마나 많이 적재하고, 제거하며, 이를 얼마나 잘 참조하냐는 것이다.

디스플레이 리스트(Display List)

뷰의 렌더링이 필요할때 디스플레이 리스트가 생성되며, 디스플레이 리스트에서는 렌더링이 필요한 뷰에 대한 그리기 명령들이 포함되어있다.

뷰가 렌더링 된 이후에 뷰의 속성에 대한 변화가 있다면, 간단히 디스플레이 리스트를 변경하여  렌더링할 수 있지만, 비주얼 적인 변화가 크다면 이전 디스플레이 리스트는 더이상 유효하지 않으므로 새롭게 디스플레이 리스트를 생성하고 화면 갱신이 필요하다.

결국 뷰의 복잡도가 결국 성능에 영향을 미치기 때문에, 디스플레이 리스트가 반복적으로 재생성되고 재실행되는 것을 제어하는것이 퍼포먼스를 향상 시킬 수 있는 방법 중 하나이다.

사이즈가 변경되는 뷰가 있다고 가정하자. 그러면 onMeasure() 단계에서 뷰 계층 모든 뷰들의 새로운 사이즈를 계산하게 되고, 만약 뷰의 포지션이 변경된다면 requestLayout()메서드를 호출하게 된다. 또는 뷰그룹이 차일드뷰들을 재배치하는경우 onLayout()이 호출되고 뷰 전체 계층이 다시 배치되게 된다.

각 뷰의 생명주기 단계는 그다지 큰 시간이 걸리지 않아 퍼포먼스에 영향이 없으나, 다른 View와 ViewGroup이 많이 연관되어 있다면 성능에 영향을 주게 된다.

더 나은 성능의 앱을 위해서는 View의 계층을 플랫하게 유지해야할 필요가 있다. 그래야 종속된 뷰들이 영향을 받지 않아 뷰가 갱신 되는데 시간이 줄어들게 된다. View의 계층을 플랫하게 유지하는데는 ConstraintLayout을 사용하면 좋다.

퍼포먼스

개발자는 앱의 퍼포먼스를 체크해야할 의무가 있다. UI 퍼포먼스는 애플리케이션 성능에서 주요한 비율을 차지한다. UI 퍼포먼스를 저하시키는 오버드로잉에 대해서 알아보자.

오버드로우(Overdraw)

오버드로우는 시스템이 단일 렌더링 프레임에서 같은 픽셀에 여러번 덧 그리는 것을 말한다. 이러한 GPU의 리소스 낭비로 인해 퍼포먼스가 떨어지고, 사용자에게 나쁜경험을 제공할 수 있다.

오버드로잉을 줄이기 위한 몇 가지 방법이 있다.

1. 레이아웃에서 불필요한 배경을 제거하기

레이아웃에 사용자가 절대 볼 경우가 없는 불필요한 배경을 제공하는 다음과 같은 경우가 있다.

<LinearLayout 
    android:id="@+id/parent" 
    android:background=”@android:color/black”>
    <android.support.v4.widget.NestedScrollView>
        <LinearLayout 
             android:id="@+id/child" 
             android:background=”@android:color/black”>
             ...
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</LinearLayout>

위의 코드는 LinearLayout과 ScrollView의 배경색이 둘다 검정색이다. 이경우 사용자 입장에서는 리니어 레이아웃만 검정색으로 배경을 지정해도 똑같이 보이므로 LinearLayout에서 배경을 지정하여 오버드로잉을 발생시킬 필요가 없다.

2. 뷰 계층을 평탄화하기

뷰 계층을 평탄화 하기 위해 ConstraintLayout을 사용하면 최상의 퍼포먼스를 낼 수 있다. ConstraintLayout은 레이아웃 내에 또 다른 레이아웃을 포함시킬 필요없이 간단한 제약조건으로 평탄한 계층구조를 만든다. 평탄한 뷰 계층을 구성하여 퍼포먼스를 향상하자.

3. 투명도를 줄이기

화면에서 투명한 픽셀을 렌더링하는 것을 알파 렌더링이라고 한다. 페이드 아웃 및 그림자 같은 시각적 효과와 같은 투명한 애니메이션에는 투명도가 포함되므로 오버 드로우가 크게 발생한다. 이러한 상황에서 렌더링되는 투명 객체의 수를 줄임으로써 오버드로잉 비용을 줄일 수 있다.

예를 들어 TextView 에서 알파 값이 설정된 검정색 텍스트를 그려 회색 텍스트를 얻을 수 있는데, 이는 단순히 텍스트를 회색으로 그리는것보다 훨씬 비용이 많이 들기 때문에 그냥 투명도 없는 회색을 적용하는 편이 퍼포먼스를 높이는데 도움이 된다.

카테고리: Graphics

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.