View와 View 성능 개선 (Feat. ConstraintLayout / RelativeLayout)

송규빈·2022년 9월 27일
0
post-thumbnail
post-custom-banner

View란?

  • View는 화면의 직사각형 영역을 차지하며 그리기 및 이벤트 처리를 담당
  • View는 UI 구성 요소(버튼, 텍스트 필드 등)를 만드는 데 사용되는 위젯의 기본 클래스
  • ViewGroup은 View의 서브 클래스로 다른 View또는 다른 ViewGroup들을 보유하는 컨테이너 View

View가 화면에 그려지는 과정

View들의 구조는 트리로 되어있는데, 이 트리를 전위 순회하면서 처리해 나갑니다. 또는 하향식으로 처리한다고도 표현이 됩니다.
ViewGroup같은 경우에는 child들을 먼저 탐색하게 됩니다.

View는 3가지 단계를 거쳐서 화면에 나타나게 됩니다.
단계: 측정(Measurement) -> 레이아웃(Layout) -> 그리기(Draw)

  1. 측정(Measurement): view의 크기를 결정하는 단계입니다. 측정은 루트 노드에서 시작해서 반복적으로 호출됩니다.
    한 번 측정하여 원하는 크기를 구한 다음 만약 그 합의 크기가 너무 크거나 너무 작으면 measure를 다시 실행하여 구체적인 값을 구합니다.

  2. 레이아웃(Layout): 각각의 view 크기를 기준으로 view의 위치를 결정하고 이 또한 루트 노드에서 시작해서 리프 노드까지 반복적으로 호출됩니다.

  3. 그리기(Draw): 이 단계에서는 스크린에 자신을 그리는 단계입니다.

예시

LinearLayout을 사용해서 만든 레이아웃의 메서드 호출 과정을 보여드리겠습니다.

orientation을 vertical로 준 LinearLayout을 루트로 하고 Child에는 TextView 3개가 존재합니다.

이를 트리로 나타내보면

이러한 형태를 띄게 됩니다.

위에서 말한대로 전위 순회로 탐색을 해보면 onMeasure 같은 경우(다른 메서드도 동일)
Linear - text1 - text2 - text3 순으로 호출이 돼야 합니다. 라고 생각하실수도 있지만 ViewGroup의 특성을 본다면 child를 먼저 탐색하기 때문에 아래와 같은 결과가 나오게 됩니다.

Double Taxing

꽤나 오래된 얘기이긴 하지만 ConstraintLayout이 나오고부터 RelativeLayout을 사용하지 말라는 얘기가 많았습니다. 지금은 거의 확정적으로 View의 성능 개선을 위해서는 RelativeLayout을 사용하지 말아야하죠.
그렇다면 왜 이렇게까지 RelativeLayout을 사용하지 말아야 할까요?

그것은 바로 'Double Taxing'이라는 것 때문입니다.

일반적으로 안드로이드 프레임워크는 layout또는 measure 단계를 한 번의 작업으로 실행하게 되어있습니다.
(위의 캡쳐본에서 두 번씩 호출하게 되는데 뒤에서 설명하겠습니다.)
하지만 일부 복잡한 레이아웃은 요소를 배치하기전에 여러 작업을 반복하게 될 수도 있죠. 이러한 반복이 2번 이상이 생길 시 이를 바로 Double Taxing이라고 합니다.

그렇다면 실제로 RelativeLayout의 메소드 호출과정을 볼까요?
View구조는 위의 예시와 동일합니다.

TextView가 3개뿐인 RelativeLayout이지만 반복적으로 호출되는 메서드가 상당하다는 것을 볼 수 있습니다. 먼저 이렇게나 많은 호출을 요구하는 이유는 RelativeLayout은 자신을 화면에 그리라는 요청을 받게 되면 자식들에게 자신들을 측정하라는 메세지가 전달됩니다. 그 후 모든 view가 측정될 때까지 이러한 과정이 계속되기 때문입니다.
즉, RelativeLayout 은 레이아웃을 그릴 때 항상 최소 2회 이상의 작업을 거칩니다.

하지만 Double Taxing은 비단 RelativeLayout에서만 일어나는 것이 아니라 아래와 같은 경우에도 일어나게 됩니다..

  • layout_weight 가 존재하는 LinearLayout은 자식 뷰를 두 번 측정해야합니다.
  • GridLayout 의 weights 나 fill gravity 는 전처리의 이점을 가지지 못합니다.

위와 같은 이유로 constraint layout을 사용하지 않을 이유가 없습니다.

wrap_content

이 글을 공부하다가 "어쨌든 view는 측정을 해야하고 정확한 측정이 될 때까지 측정을 반복 하는 것 같은데.. 그러면 wrap_content보다 정확하게 치수를 써넣으면 좀 더 수월하지 않을까?" 라는 생각이 들었습니다.

wrap_content로 설정했을 때

100dp로 설정했을 때

캡쳐본에서 나와있다시피 wrap_content로 설정했을 때는 onDraw이후 다시한 번 onMeasure과 onLayout을 거치는 것을 볼 수 있습니다.

merge / include

레이아웃 xml을 작성하다 보면 코드가 굉장히 길어질 때가 있습니다. 또한, 반복적인 부분도 복붙을 통해 작성할 때도 많구요. 그래서 이러한 과정들을 개선하고 가독성을 좋게 하기 위해 검색을 하다보면 merge와 include라는 키워드를 마주하셨을 겁니다. 그러면 이 둘 중에 무엇을 써야할까도 고민해보셨을 텐데요.

결론부터 말하자면 코드 재사용을 위해 include를 사용하는 것보다 merge를 사용하는 것이 더 성능 측면에서 효율적입니다.

include

include를 사용하여 이 레이아웃을 액티비티에 추가했을 때를 보면 아래와 같습니다.

컴파일러가 include 태그를 레이아웃 파일로 대체하여 중첩된 ConstraintLayout이 생깁니다.
이렇게 중첩이 생기면 Double Taxing이 생길 수 있고 그로인해 성능이 나빠지게 됩니다.

merge

위와 같은 문제를 해결하기 위해서 merge 태그를 사용하면 됩니다.
사용법은 include와 비슷합니다.

ViewStub

로딩 프로그레스바와 같이 사용자에게 보이지 않다가 필요할 때만 보이게 하는 View를 구성할 때는 ViewStub을 사용하면 좋습니다.
우리가 흔히 사용하는 Visible.GONE 등은 어쨌든 뷰 계층구조에 포함이 되는 형태입니다.
하지만 ViewStub은 어떤 것도 그리지 않거나 레이아웃에 관여하지 않는 경량 View이기 때문에 필요한 경우에만 렌더링을 합니다.

이러한 ViewStub은 merge 태그를 지원하지 않는다는 단점이 존재하지만, 상황에 알맞게 잘 쓴다면 레이아웃을 최적화할 수 있을 것입니다.

참고

profile
🚀 상상을 좋아하는 개발자
post-custom-banner

0개의 댓글