Android

뷰가 그려지는 과정(View Lifecycle) 이해하기 : [우아한테크코스 5기 AN_베르]

베르_최성훈 2023. 10. 22. 18:36

테스트, 아키텍처, 다 중요하지만 간과하는 부분이 있었다.

 

안드로이드 개발자라면 요구사항에 맞는 View 를 마음껏 그려내고 지연을 줄여 최적화 할 수 있어야한다.

 

오늘은 뷰가 그려지는 과정에 대해 이해해보자.

 

View Lifecycle

<참고>

https://proandroiddev.com/the-life-cycle-of-a-view-in-android-6a2c4665b95e

https://medium.com/android-deep-dive-study/introduce-android-ui-rendering-principle-and-view-optimization-1-f593820c7a50

 

Custom View 를 만들다보면 View Lifecycle 를 마주할 때가 있다.

 

View Lifecycle 에 대해 알아보자.

 

안드로이드 컴포넌트, 프레그먼트와 마찬가지로 View 또한 Lifecycle 을 갖는다.

View Constructor

 

View 를 생성하는 방법은 다음과 같다. xml 파일에서 속성을 set 하고 싶으면 AttributeSet 이 필요하다.

View(Context context) 
View(Context context, @Nullable AttributeSet attrs) 
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) 
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)

기본 속성값, 기본 리소스 값도 생성자로 주입할 수 있다.

 

 

Attachment/ Detachment

window 에 attach 되었는지 detach 되었는지를 의미한다

 

- onAttachedToWindow() : 이때부터 View 가 Active 하다. 따라서 Resource 나 Listener 를 할당할 수 있다.

- onDetachedFromWindow() : 더 이상 View 가 Active 하지 않다. 따라서 할당된 리소스를 해제해줘야 한다. ViewGroup 에서 View 가 지워질 때, 혹은 액티비티의 finish() 가 호출되었을 때 등의 상황에서 호출된다.

 

 

onMeasure()

View 의 크기를 재는 단계이다. ViewGroup 은 하위 노드의 View 크기를 먼저 측정하고 그에 따라 자신의 크기를 결정한다.

 

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

 

- MeasureSpec

뷰의 크기를 측정할 때 부모 뷰에서 자식 뷰로 전달되는 요구사항이다.

 

MeasureSpec.EXACTLY :  자식 뷰의 크기와 상관없이 부모 뷰가 자식 뷰의 정확한 크기를 결정한다.

MeasureSpec.AT_MOST : 자식 뷰는 지정된 크기 안에서 원하는 만큼 커질 수 있다.

MeasureSpec.UNSPECIFIED : 부모 뷰가 자식 뷰에 제한을 두지 않는다. 원하는 만큼 커질 수 있다.

 

onLayout() 

크기 측정을 완료했다면 View 를 Window 에 배치한다.

protected void onLayout(boolean changed, int left, int top, int right, int bottom)

 

onDraw()

크기 계산 및 배치를 완료했다면 이를 기반으로 그려야한다. Canvas 객체를 GPU 에게 전달한다. onDraw() 는 한 프레임을 그리는 함수로 여러 번 호출될 수 있기 때문에 무거운 작업을 하지 않아야한다. 객체를 만들어서 사용한는 것은 금기시한다.

 

invalidate() vs requestLayout()

뷰의 변화를 시스템에게 알린다.

- invalidate() : view 의 속성이나 데이터(모양)이 변했을 때 사용한다. draw 과정이 수행된다.

- requestLayout() : view 의 경계에 변화가 생겼을 때 사용한다. measure -> layout -> draw 단계를 다시 거친다.  

 

이 두 가지 함수는 반드시 UI thread 에서 호출해야 한다!

만약 다른 스레드에서 업데이트 시키고 싶다면 handler 를 이용하자~

 

뷰 최적화 방법

<참고>

https://developer.android.com/training/custom-views/optimizing-view?hl=ko

 

1. ConstraintLayout 을 사용하면 뷰 계층을 단순화 할 수 있어 계층 구조 순회 비용을 줄일 수 있다! 또한 장풍 구조를 단순화하자

2. RecyclerView 의 setHasFixedSize 를 false 로 설정하면 requestLayout() 을 호출한다.

안드로이스 UI 시스템은 뷰의 크기가 얼마나 되는지 알아내기 위해 매번 뷰 계층 구조를 순회해야한다.

이는 비용이 비싸다. 따라서 RecyclerView 의 item 이 변하지 않는다면 setHasFixedSize 를 true 로 변경해주자!

3. 복잡한 UI 라면 custom ViewGroup 사용을 고려해보자. 애플리케이션 별로 하위 요소의 크기 및 모양을 추정할 수 있어 순회하지 않아도 된다.