본 포스트는 Android에서 View의 생명주기 해당 블로그의 글을 필자가 쉽게 이해하고 기록하기 위한 용도로 작성한 글입니다. 자세한 내용은 해당 블로그를 참고해주세요.
안드로이드 앱에서 View
는 유저 인터페이스를 구성요소를 갖고있는 가장 기본적인 단위 클래스이다. View
는 화면 상에서 사각형 영역을 가지며 그리기 및 이벤트 처리 등을 처리할 수 있다. 또한 View
는 버튼이나 텍스트 필드와 같은 유저 상호작용 요소들의 집합인 Widget
의 가장 기초 클래스이다.
View
의 서브 클래스인 ViewGroup
은 레이아웃들의 기초 클래스이며 View
들(또는 다른 ViewGroup
)을 담는 보이지 않는 컨테이너 역할을 한다.
안드로이드의 Activity
나 Fragment
에 생명주기가 존재하는 것처럼 View
에도 역시나 생명주기가 존재한다. 하지만 많은 개발자들이 View
의 생명주기를 제대로 알지 못하고 개발을 한다. (사실 나도 그랬다...)
이번 포스트에서는 View
의 생명주기와 각 단계에서 일어나는 동작에 대해 알아보고자 한다.
화면에 렌더링 된 View는 다음 그림과 같은 생명주기 메서드를 거쳐 화면에 그려진다.
위 생명주기의 각 단계들은 모두 중요한 역할을 하고 있다. 각각의 단계에서 어떤 일이 일어나는지 알아보자.
커스텀 뷰(Custom View)를 만들때 생성자를 어떤 걸 써야하는지 난감할 때가 많다. View
의 생성자가 여러 종류가 있는데다 각각 어떤 차이점이 있는지 명확히 모르기 때문이다.
View(Context context)
코드에서 View
를 동적으로 만들 때 사용하는 간단한 생성자다. 여기서 매개 변수 context
는 View
가 실행될 때 현재 테마, 리소스 등을 구성하는데 사용된다.
View(Context context, @Nullable AttributeSet attrs)
XML에서 View
를 전개(Inflate)할 때 호출되는 생성자로, XML 파일에서 지정된 속성을 제공하여 XML 파일에서 View
를 구성 할 때 호출된다. 이 생성자는 기본 스타일을 사용하므로 Context
의 테마 및 지정된 AttributeSet
의 속성 값만 적용된다.
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
XML을 통해 전개(Inflate)를 하고 테마 속성에서 클래스별 기본 스타일을 적용한다. 이 생성자는 서브 클래스가 전개(Inflate)할 때 자체 기본 스타일을 사용할 수 있도록 한다.
예를 들어, Button
클래스의 생성자는 수퍼 클래스 생성자를 호출하고 defStyleAttr
에 R.attr.buttonStyle
을 제공한다. 이를 통해 테마의 버튼 스타일은 모든 기본 View 속성 (특히 배경)과 Button 클래스의 속성을 수정할 수 있다.
View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)
XML을 전개하고 테마 속성 또는 Style 리소스에서 클래스 별 기본 스타일을 적용한다. 이 생성자는 서브 클래스가 전개(Inflate)할 때 자체 기본 스타일을 사용할 수 있도록한다. 위와 유사하다.
매개변수 defStyleRes
는 View
의 defStyleAttr
가 0이거나 테마에서 찾을 수 없는 경우에만 기본값을 제공하는 Style 리소스 ID다. 기본값을 찾지 않으려면 0 으로 지정한다.
View
가 Window에서 연결되거나 분리 될 때의 단계다. 이 단계에는 적절한 작업을 수행하기 위해 콜백을 받는 몇 가지 방법이 있다.
onAttachedToWindow()
View
가 Window에 연결되면 호출된다. View
가 활성화 될 수 있고, 드로잉 할 표면이 있음을 알고있는 단계다. 따라서 리소스 할당을 시작하거나 리스너를 설정할 수 있다.
onDetachedFromWindow()
View
가 Window에서 분리 될 때 호출된다. 이 시점에서 더 이상 드로잉을 할 표면이 없다. 예약 된 자원을 정리하거나 정리하는 모든 종류의 작업을 중지해야하는 곳이다.
이 메소드는 ViewGroup에서 View 제거를 호출하거나 액티비티가 Destroyed될 때 호출된다.
onFinishInflate()
이 메소드는 View
가 전개(Inflate)가 끝날 때 호출된다. 레이아웃의 경우 모든 Child View가 추가 된 후에 호출된다.
View
계층 구조는 부모 노드(ViewGroup)에서 분기가 있는 리프 노드(Child Views)의 트리 구조와 같기 때문에 순회 단계라고 한다. 따라서 각 메소드는 부모에서 시작하여 마지막 노드까지 순회하여 제약 조건을 정의한다.
Measure 단계와 Layout 단계는 항상 위와 같이 순차적으로 진행된다.
onMeasure()
메소드는 View
의 크기를 확인하기 위해 호출된다. ViewGroup
의 경우 계속해서 각 Child View에 대한 측정을 하고, 그에 대한 결과로 자신의 사이즈를 결정한다.
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
// @param widthMeasureSpec 부모 View에 의해 적용된 수평 공간 요구사항
// @param heightMeasureSpec 부모 View에 의해 적용된 수직 공간 요구사항
onMeasure()
는 값을 반환하지 않고, setMeasuredDimension()
을 호출하여 너비와 높이를 명시적으로 설정한다.
MeasureSpec
은 부모에서 자식으로 전달되는 레이아웃 요구 사항을 캡슐화한다. 각 MeasureSpec
은 너비 또는 높이에 대한 요구 사항을 나타낸다. MeasureSpec은 크기와 모드로 구성되며, 세 가지 모드가 있다.
MeasureSpec.EXACTLY: 부모가 자식의 정확한 크기를 결정한다. 자식의 사이즈와 관계없이 주어진 경계 내에서 사이즈가 결정된다.
MeasureSpec.AT_MOST: 자식은 지정된 크기까지 원하는 만큼 커질 수 있다.
MeasureSpec.UNSPECIFIED: 부모가 자식에 제한을 두지 않기 때문에, 자식은 원하는 크기가 될 수 있다.
View
위치를 측정하여 화면에 배치 한 후에 호출된다.
크기와 위치는 이전 단계에서 계산되므로 View
는 그것들을 기준으로 그려진다. onDraw(Canvas canvas)
메서드에서 생성된 캔버스 객체에는 GPU로 보낼 OpenGL-ES 명령목록(displayList)이 있다.
onDraw()
는 여러번 호출되므로 이곳에서 객체를 만들면 안된다.
특정 View
의 속성이 변경되었을 때 실행되는 두 가지 메서드가 있다.
invalidate()
invalidate()
는 변경 사항을 보여주고자 하는 특정 View
에 대해 강제로 다시 그리기를 요구하는 메소드이다. View
모양이 변경되면 invalidate()
를 호출해야한다고 할 수 있다.
requestLayout()
어떤 시점에서 View
의 경계가 변경되었다면, View
를 다시 측정하기 위해 requestLayout()을 호출하여 Measure및 Layout 단계를 다시 거칠 수 있다.
View
에서 메소드를 호출 할 때는 항상 UI 스레드내에서 수행해야한다. 다른 스레드에서 작업하고 있고, 해당 스레드에서 View
의 상태를 업데이트 하려는 경우 핸들러를 사용해야한다.