[Android] View가 렌더링되는 과정 📱

Jay·2021년 6월 15일
2

Android

목록 보기
38/39
post-thumbnail

위 글의 대부분의 내용은 안드로이드 View가 렌더링 되는 과정 해당 글을 참고하여 적었습니다.

Intro

안드로이드의 화면은 아래와 같은 단위로 구성된다. 화면을 구성하는 최소단위는 View이며 최대 단위는 Window이다.

Window > Surface > Canvas > View

Window

  • 화면 구성의 가장 상위 요소로 무언가를 그릴 수 있는 화면 상 사각 영역이다.
  • 어플리케이션에서는 각각의 Window에 Surface를 만들어 어플리케이션에 전달하고, 어플리케이션은 이를 통해 화면을 렌더링한다.
  • 터치 이벤트, 키 이벤트 등 사용자 이벤트를 받아 처리할 수 있다.
  • statusBar
  • SoftKey

Surface

  • Surface는 화면에 합성되는 픽셀을 보유한 객체
  • 화면에 표시되는 모든 Window(StatusBar, Dialog, Activity etc)는 자신만의 Surface를 가지고 있으며
  • Surface Flinger가 여러 소스로부터 그래픽 데이터 버퍼를 받고, 그것들을 합성하여 display로 보낸다.

Canvas

  • 모든 드로인 메서드를 포함하고 있는 클래스
  • 각종 도형, 선 등을 그리기 위한 모든 로직이 Canvas에 포함되어 있다.
  • Bitmap 또는 OpenGL 컨테이너 위에 그려진다.

View 렌더링 과정

XML로 작성한 View가 어떻게 최종적으로 화면에 렌더링 되는 지 ?

Android에서는 Activity가 Focus를 얻게 되면 자신의 Layout을 그린다.

이때 그리고자 하는 Layout의 Root Node를 요청하게 되는데, 우리가 setContentView()를 호출할 때 Root Node가 정해진다.

Root Node가 결정되면 Root Node를 따라 Child Node를 따라가면서 차례대로 View를 그리게 된다.


그리는 순서

Layout을 그리는 건 측정과 순서란 과정을 따른다.

measure() > onMeasure() > layout() > onLayout()

  • measure(widthMeasureSpec :Int, heightMeasureSpec:Int)
    • 뷰의 크기를 알아내기 위해 호출되며, 실제 크기 측정을 위해 onMeausre()를 호출한다.
    • widthMeasureSpec : Parent가 뷰여한 필요한 가로 공간
    • heightMeasureSpec : Parent가 부여한 필요한 세로 공간
  • onMeasure(widthMeasureSpec :Int, heightMeasureSpec:Int)
    • 실제 뷰의 크기를 측정한다.
    • widthMeasureSpec : Parent가 뷰여한 필요한 가로 공간
    • heightMeasureSpec : Parent가 부여한 필요한 세로 공간
  • layout(left:Int, top:Int, right:Int, bottom:Int)
    • 뷰의 위치를 할당하기 위해 호출되며, 실제 할당을 위해 onLayout()을 호출한다.
    • left : Parent에 대한 왼쪽 포지션
    • top : Parent에 대한 위쪽 포지션
    • right : Parent에 대한 오른쪽 포지션
    • bottom : Parent에 대한 하단 포지션
  • onLayout(changed:Boolean, left:Int, top:Int, right:Int, bottom:Int)
    - 실제 뷰의 크기 및 위치를 할당한다.
    • changed : 새로운 사이즈나 위치인지 맞으면 True/False
    • left : Parent에 대한 왼쪽 포지션
    • top : Parent에 대한 위쪽 포지션
    • right : Parent에 대한 오른쪽 포지션
    • bottom : Parent에 대한 하단 포지션

View의 생명주기

measure() > layout() > draw()

onAttachToWindow()

  • 뷰가 윈도우에 붙었을때 호출
  • Parent View가 addView(view:View)를 호출하면 해당 View가 window에 연결된다. 이 단계부터 id를 통해 접근 가능하다.

measure()

  • 🌟뷰들의 사이즈를 측정(결정)

onMeasure()

  • View의 사이즈를 측정
  • 요구된 View와 Child View의 사이즈가 결정되면 호출된다.

layout()

onLayout()

  • Child 뷰의 사이즈 및 위치 할당
  • View와 ChildView의 사이즈와 포지션을 적용할 때 호출
  • 🌟뷰를 측정하여 화면에 배치한 후 호출된다.

dispatchToDraw()

onDraw()

  • Canvas와 Paint를 사용해서 뷰를 그리기 시작
  • 뷰가 화면에 컨텐츠(텍스트,이미지 등)을 그릴 준비가 되었을 때 호출
  • Canvas 객체를 생성하고 뷰를 그린다.

View Update

  • 특정 속성이 변경되었을 때 호출되는 메서드

invalidate()

  • 변경사항을 보여주고자 하는 특정 뷰에 대해 강제로 다시 그리기를 요구하는 메소드.
  • 뷰 모양이 변경되면 invalidate()를 호출해야 한다.

requestLayout()

  • 어떤 시점에서 뷰의 경계가 변경되었다면, View를 다시 측정하기 위해 requestLayout()을 호출하여 Measure 및 Layout 단계를 다시 거칠 수 있다.

Rasterization (래스터화)

문자열, 버튼 또는 도형과 같은 객체들을 픽셀로 변환시키고 스크린 상의 텍스처로 나타내는 과정을 말한다.

  • 레스터화는 비용이 큰 작업에 속한다.
  • 그러므로 GPU가 래스터화 가속을 위해 사용된다.
  • CPU는 polygon, texture 등을 계산하고 처리하는 데 특화되어 설계되었다.(병렬 처리)

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

이러한 CPU와 GPU의 데이터 전송 처리는 일반적으로 OpenGL ES API의 호출에 의해 일어난다.
안드로이드에서 점,선,면을 포함한 버튼, 이미지 등의 UI 객체가 화면에 나타나기 위해서는 항상 OpenGL ES를 거치게 된다.
OpenGL은 3개의 정점을 이어 하나의 삼격형을 이루고 이를 PolyGon이라고 한다.
Polygon이 모여 메쉬를 이룬다.

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

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

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


UI 퍼포먼스

OverDraw > UI 퍼포먼스를 저하

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

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

  • 레이아웃에서 불필요한 배경 제거
  • 뷰 계층을 평탄화 하기
    • 이때 ConstraintLayout의 큰 효과를 볼 수 있다.
    • ConstraintLayout은 레이아웃 내에 또 다른 레이아웃을 포함시킬 필요 없이 간단한 제약조건으로 평탄한 계층 구조를 만든다.
    • 평탄한 뷰 계층을 구성하여 퍼포먼스를 향상하자.
  • 투명도 줄이기

Ref

profile
developer

0개의 댓글