퉁탕퉁탕 만들어보자

Android View LifeCycle 본문

Computer/Android

Android View LifeCycle

호숀티 2022. 5. 7. 21:39
반응형

View의 수명은 3가지로 구성된다.

* Attachment / Detachment

* Traversals

* State save/restore

 

Attachment

이미 window에 속한 ViewGroup에 view가 add될 때, onAttachedToWindow() 콜백을 받게된다. 이때 view가 active되었다 라는 것을 알 수 있다. 그래서 여기서 이제 resource할당이나 listener들을 등록한다.

 

Detachment

Window에 붙어있는 parent로부터 detach될때 onDetachedFromWindow() 콜백을 받게된다.

이를 테면 recyclerview에서 view가 recycle될때나, parent에서 해당 뷰를 없앴을 때, activity가 finish()될때 모든 view들을 전부 window로부터 detach하게된다.

이미 schedule된 이를테면 background thread같은 모든 일들을 멈출 수 있는 곳이기 때문에 중요하다.

 

CustomView를 만들었다면 이때 drawing cache같은 할당된 resource를 제거 할 수 있는 좋은 타이밍임.

이를테면 HW accellerate의 경우, native object들과 붙어있는데, 이제 detach가 되면은 화면에 더이상 표시되지 않기 때문에 이때 native object들을 destroy 할 수 있다.

하지만 이게 temporary하게 detach될 수도 있고 bug도 좀 있을수있기때문에 조심해서 사용해야한다..

 

* view.post된 runnable들을 모두 없앰

* data change들 listen을 멈춤

* resource clean up 함 - Bitmap, Thread

 

View abstraction

View는 Activity나 Fragment보다 하위레벨의 abstraction이다.

View는 Activity의 lifecycle(onPause, onResume)에 대해서 알지 못한다.

따라서 이런게 필요한경우에는 listener를 만들어서 activity단에서 직접 호출하는식으로 구현하는게 바람직하다.

이를테면, OpenGL Rendering을 하거나,, MediaPlayerView같은경우에는 이제 onPause나 onResume에서 멈추거나 시작하거나 해야하는데 이건 Activity에서 해당 lifecycle callback에서 view의 함수를 call하는 방식으로 구현하는게 맞다.

 

Traversals

traversal은 3가지 다른 방식- animation, requestLayout(), invalidate() 에서 부터 schedule된다. 

해당 순서는 반드시 지켜진다. 하지만 layout단계에서 측정값이 있을수도있고, draw에 measure값이 있을 수 있지만 layout에는 없을 수도 있다. 

까다로운 부분은 measure하고 layout은 무조건 같이 일어난다는 점임

 

 

Measurement and Layout

모든 View들은 자기자신을 어떻게 measure해야하는지와 conent를 어떻게 배치해야하는지를 알고있다.

* 자식이 없는 leaf node view는 자기 내부에 display되는 content를 measure해야함. 

* ViewGroup같은 경우이는 자기 자식들 view를 전부 measure하고 layout해야함

 

requestLayout이 이 전체 단계의 시작이다. frame에 대해서 view tree에 대한 traversal 하면서 measure하고 layout을 시작한다. 그리고 requestLayout()은 리커시브임. 이를테면 부모가 wrap content 인 경우, leaf node의 자식뷰 사이즈가 바뀌면 그 부모의 부모의 부모도 사이즈가 변해야 할 수 있음. 따라서 사실 이게 tree가 너무 깊은경우에 성능상 부담이 생길 수 있음.

 

onMeasure()

onMeasure() 콜백이 이제 나의 view와 children의 measure를 수행해야하는 단계이다.

onMeasure는 재귀적으로 자기 자식의 onMeausre를 다 호출해서 이제 가져오고 그거를 가지고 나 자신의 사이즈를 구할 수 있다.

나의 경우에는 video custom view를 만들었는데, 유저가 view의 width와 height를 match_parent, wrap_content으로 만들었을 때 서버에서 보내주는 w/h 값을 가지고 비율에 맞는 w/h를 계산하는 작업을 onMeasure에 구현했다. 

 

MeasureSpec에는 3가지 타입이있다. 

MeasureSpec 사용법 설명
EXACTLY MeasureSpec.makeMeasureSpec(
사이즈, MeasureSpec.EXACTLY);
Fix된 사이즈가 딱 정해져있거나 parent view가 고정크기를 계산해준 경우 LinearLayout에서 LayoutParam이나 weight를 사용할 때
AT_MOST MeasureSpec.makeMeasureSpec(
사이즈, MeasureSpec.AT_MOST);
child와 그 후손들에게 최대값을 줌 view에 wrap_content를 주면 AT_MOST를 사용해서
child를 measure함
UNSPECIFIED MeasureSpec.makeMeasureSpec(
사이즈, MeasureSpec.UNSPECIFIED);
어떻게 measure 해야하는지 충분히 아는게 없는 경우
upper bound가 없음
스크롤뷰
거기에 뭐가 있느냐에 따라 달라짐. 

EXACTLY

 

AT_MOST
UNSPECIFIED

onMeasure()에서는 값을 리턴하는건아니고 setMeasuredDimension()을 리턴하기전에 콜해서 width와 height를 set해줘야한다 (필수- 안하면 앱죽음)

여기서 set된 width와 height값은 getWidth, getHeight가 아닌 getMeasuredWidth, getMeasuredHeight값으로 얻을 수 있다. 

가끔 코드를 짜다보면 getWidth(), getHeight()를 했는데 멀쩡한 view인데 0을 리턴하는경우가 있다. 그래서 해결법으로onGlobalLayout 콜백에서 얻어오게 되는데 이유는 layout이 다 되기 전에는 실제 값이 없기 때문이다. 그래서 child view 의 measured width/height를 가지고 자기의 크기를 결정하거나, 자녀의 메저가 완료된 뒤에 다른 수행을 할 수 있다. 

 

멀티 패스 시나리오에서는 한번 더 onMeasure를 타서 measure를 finalize하는게 일반적인 경우다. weight를 정하는경우도 이런경우인데, 만약에 모든 childView를 measure한 다음에도 여전히 남은 가중치를 분해를 해야되기 때문에 EXACTLY를 사용해서 각 자식뷰에 추가의 크기를 고정해야함.

 

ViewGroup.getChildMeasureSpec(spec, padding, childDimension)

* spec : 부모의 MeausreSpec

* padding: 부모가 쓸수없는 추가공간

* childDimension: child가 되고싶은 사이즈

 

예를 들어, parent가 300px이 있고 child가 wrap_contet인 경우 -> child에게 300px AT_MOST를 보냄

 

onLayout()

measure가 완료되고, child view들을 각각의 position에 배치하는 단계이다. 여기서 margin, padding등을 적용한다. onMeausre이 제대로 측정을 하기위해서 여러번 수행된다면, onLayout은 딱 한번 수행된다. 만약에 mearue layout 하는 동안에 좀 복잡한 계산을 해야한다면, layout에서 수행하는게 좀더 efficient하다. 

이 단계에서는 measure이 끝났기 때문에 getMeasuredWidth()와 getMeasuredHeight() 값을 제대로 가져올 수 있다. 

getWidth()와 getHeight()로는 layout() 이 끝난 뒤에 제대로 된 값이 온다.

 

Draw

invalidate()

invalidate하면 view 하이어러키 traversal을 시작한다. RequestLayout과 마찬가지로, child에서 호출되면 부모로 계속 전달된다. 따라서 역시 좀 성능상 부담이 가기 떄문에 자주 호출하는것은 좋지 않다. 만약 view가 보여지는 중이라면 invalidate()를 콜하면 onDraw가 불리게될것이다.

 

 

draw()

view와 그 자식들을 그린다. draw() 함수는 final이라서 재정의할수가 없다. 대신에 onDraw() 콜백을 주는데 그건 우리가 override해서 쓸수있다. onDraw()의 경우, 해당 view가 그려지기 전에 background가 그려진 상태에서 불려진다.  

만약에 setWillNotDraw(true)로 셋하면 onDraw()가 불리지 않는다.

dispatchDraw()

dispatchDraw()를 콜하면 모든 child의 draw()를 부르게된다.

이 dispatchDraw()를 override해서 직접 구현하려면 이제 나의 child view들을 그리는 일을 해야된다. 여기서 모든 alpha, transform, animation, 등등.. 그리고 여기서 직접 child의 drawChild()를 각각 전부 호출해줘야함.

 

onSaveInstanceState & onRestoreInstanceState

여기에서 parcelable을 사용해서 view자체의 값을 저장했다가 사용할 수 있다.

view는 반드시 id를 갖고잇어야함 - 그래야 제대로 된 값을 복원할 수 있음

override해서 구현할 때 super반드시 호출 필요 

null state에서는 onRestoreInstanceState가 불리지 않음

그러면 무엇을 저장할 것인가? scroll position, selection, edittext에서 입력된 text등등 간단한 것을 저장할 수 있다. (큰 데이터 저장 X, 간단한것들)

 

Touch Event

  • ACTION_DOWN : 새로운 제스처 시작 - 첫번째 터치 포인트가 스트림에 닿음
  • ACTION_POINTER_DOWN 또는 ACTION_POINTER_UP 의 페어:  각각 ID가 있음 -> 동일한 touch point 추적에 사용
    • ACTION_POINTER_DOWN은 pointer가 valid해지는 시작임. UP은 이제 pointer가 invalid 되는것임.
    • 그다음으로 ACTION_MOVE가 0이상개로 올 수 있음. 여기에 이제 화면에 있는 모든 valid한 pointer를 갖고있음
  • ACTION_UP또는 ACTION_CANCEL : 현재 gesture progress를 완전히 종료함. - 유저가 interaction을 완료
    • ACTION_UP : 이제 노멀하게 종료되었다는 뜻
      • 여기서 click event를 적용해아할 시점.
    • ACTION_CANCEL : 다른 무언가가 user의 컨트롤을 view로부터 빼앗아갔다는 뜻

 

onTouchEvent에서 ACTION_DOWN에 대해서 true를 리턴하면, 나머지 이벤트 스트림이 그 view로 전달 되게 된다.

 

parent view에서는 onInterceptTouchEvent라는 함수가 있는데, 자식 뷰가 받는 터치 이벤트들에 대해서 call된다. 만약에 이 함수에서 true를 리턴하면 자식 view로부터 event stream을 훔칠수있다. 자식 뷰는 이때 ACTION_CANCEL을 받게된다. 그리고 부모 view는 이제 onTouchEvent에서 나머지 이벤트를 받게 된다.

false를 리턴하면, 나머지 이벤트들을 부모에서도 받고, 또 event들이 자식에게도 전달된다.

 

그런데 requestDisallowInterceptTouchEvent가 있는데, 부모쪽에서 onInterceptTouchEvent가 불리는것을 막게한다.

 

 

출처: https://www.youtube.com/watch?v=NYtB6mlu7vA

728x90
반응형

'Computer > Android' 카테고리의 다른 글

View Binding  (0) 2022.05.08
Android Rendering?  (0) 2022.05.08
Android RecyclerView  (0) 2022.05.05
ViewModel  (0) 2022.04.24
Android Process And Thread  (0) 2022.04.24