[Android] Layout별 성능 비교[Measure 호출횟수 비교] (LinearLayout vs RelativeLayout vs ConstraintLayout)

 UI 구성시 가장 기본이 되는 것은 Layout이다. 이 Layout들 중, 가장 많이 쓰게 되는 것이, LinearLayout과 RelativeLayout이 아닐까 한다. 최근들어 ConstraintLayout에 대해 검토를 하는 사람들이 늘어나게 되었고, Google에서도 성능상의 이점들을 이야기하며, ConstraintLayout의 사용을 권장하고 있다. 
 그렇다면, 정말로 ConstraintLayout이 다른 Layout들에 비해 얼마나 좋은지 확인해보려고 한다. 그 비교의 첫번째로, Measure 호출 횟수를 비교해보려고 한다. 
  물론 UI성능을 확인하는데는 Measure뿐 아니라, 기타 다른 UI들이  

 본격 비교를 하기에 앞서서, 기존에 사용하고 있던 Layout들이 어떤 패턴으로 measure동작을 수행하는지 알아보려고 한다.
 기본적으로 Measure는 자기 자신의 Measure를 수행할 때 필요한 모든 정보가 충족되지 않으면, 필요한 정보를 모두 얻을 수 있을때까지,  Child들의 Measure를 다시 타게 된다. 

※ 기본 Layout들의 Measure 호출 패턴

  1. LinearLayout


   Test 1. LinearLayout 1단 구성
    


   간단한 예제로, Layout내부에 두개의 TextView를 포함한 구조를 만들었다.
   위와 같은 XML구성일 때, Measure() 호출 스택은 아래와 같다.
   
    1) LinearLayout.onMeasure()

    2) TextView1.onMeasure()
    3) TextView2.onMeasure()
  --------------------------------------

    4) LinearLayout.onMeasure()
    5) TextView1.onMeasure()
    6) TextView2.onMeasure()
      
    위의 스택을 보면 알 수 있듯 LinearLayout의 Measure는 두단계로 진행된다. 
    가운데 구분선은, 단계의 구분을 해둔 것이다.
    LinearLayout의 Measure가 한번 진행될 때,
    Child들도 한번씩 Measure가 호출되는 것을 확인할 수 있다. 

    어렵게 이야기를 했는데, XML에 정의된 순서대로 Measure를 타게된다. 
    
    그렇다면 LinearLayout이 중첩되어있다면 어떻게 될까?

  Test 2. LinearLayout 2단 구성
    

      위에서 진행한 테스트 코드를 한번 더 LinearLayout으로 감싼 구조이다.
      그리고 단순히 감싸기만 하면 재미 없어서,
      상위계층에 TextView를 하나 더 두었다.

    
    1) LinearLayout1.onMeasure() [외부]
    2) TextView0.onMeasure()
    3) LinearLayout2.onMeasure() [내부]
    4) TextView1.onMeasure()
    5) TextView2.onMeasure()
   --------------------------------------

    6) LinearLayout1.onMeasure() [외부]
    7) TextView0.onMeasure()
    8) LinearLayout2.onMeasure() [내부]
    9) TextView1.onMeasure()
    10) TextView2.onMeasure()
     
    이 경우 역시, XML의 Layout 구조 순서로 호출되는 것을 알 수 있다.       
    
   그렇다면, 동일한 구성을 갖는 RelativeLayout은 어떨까?


2. RelativeLayout
   
   Test 1. RelativeLayout 1단 구성
        위의 LinearLayout 예제를 RelativeLayout으로 변경한 구조이다.
        동일한 UI 구성을 갖기 위해서, layout_below 옵션을 하나 주었다. 




    1) TextView2.onMeasure()
    2) TextView1.onMeasure()
    3) TextView1.onMeasure()
    4) TextView2.onMeasure()
    5) RelativeLayout.onMeasure()
   ----------------------------------------
    6) TextView2.onMeasure()
    7) TextView1.onMeasure()
    8) TextView1.onMeasure()
    9) TextView2.onMeasure()
    10) RelativeLayout.onMeasure()

    LinearLayout과의 차이가 확연하게 보인다.
    LinearLayout과 차이점을 보자면

      - Layout의 Measure가 Linear는 앞에서, Relative는 뒤에서 발생
      - Child의 Measure가 각 단계마다 Linear는 한번씩, Relative는 두번씩 발생
    Child의 Measure가 두번씩 불려지다보니, Measure에 들어가는 시간이

    LinearLayout에 비해 2배정도 더 걸리게 된다.


  Test 2. RelativeLayout 2단 구성
    

    1) TextView2.onMeasure()
    2) TextView1.onMeasure()
    3) TextView1.onMeasure()
    4) TextView2.onMeasure()
    5) RelativeLayout1.onMeasure() [내부]
    6) TextView0.onMeasure()
    7) TextView0.onMeasure()
    8) TextView2.onMeasure()
    9) TextView1.onMeasure()
    10) TextView1.onMeasure()
    11) TextView2.onMeasure()
    12) RelativeLayout1.onMeasure() [내부]
    13) RelativeLayout0.onMeasure() [외곽]
   -----------------------------------------

    14) TextView2.onMeasure()
    15) TextView1.onMeasure()
    16) TextView1.onMeasure()
    17) TextView2.onMeasure()
    18) RelativeLayout1.onMeasure() [내부]
    19) TextView0.onMeasure()
    20) TextView0.onMeasure()
    21) TextView2.onMeasure()
    22) TextView1.onMeasure()
    23) TextView1.onMeasure()
    24) TextView2.onMeasure()
    25) RelativeLayout1.onMeasure() [내부]
    26) RelativeLayout0.onMeasure() [외곽]

   말할것도 없이 훨씬 복잡해졌다. 원래 단계별로 2회씩 호출되었던
   Child들의 Measure는 4회로 증가했다.
   모든 경우에 통용되는 공식인지는 모르겠으나
   위의 테스트 결과만 놓고 본다면, LinearLayout 대비 RelativeLayout은
   그 Child들의 Measure를 두배씩 증가시키는 것으로 보여진다.

   특히 RelativeLayout이 중첩이 되면 제곱으로 증가한다.  
   즉... 어떤일이 있어도 RelativeLayout을 중첩시키는 일은 없어야겠다.



3. 외곽 RelativeLayout, 내부 LinearLayout
    


    1) LinearLayout.onMeasure()
    2) TextView1.onMeasure()
    3) TextView2.onMeasure()
    4) TextView0.onMeasure()
    5) TextView0.onMeasure()
    6) LinearLayout.onMeasure()
    7) TextView1.onMeasure()
    8) TextView2.onMeasure()
    9) RelativeLayout.onMeasure()
   ------------------------------------
    10) LinearLayout.onMeasure()
    11) TextView1.onMeasure()
    12) TextView2.onMeasure()
    13) TextView0.onMeasure()
    14) TextView0.onMeasure()
    15) LinearLayout.onMeasure()
    16) TextView1.onMeasure()
    17) TextView2.onMeasure()
    18) RelativeLayout.onMeasure()
   

    위의 RelativeLayout에서 봤던 내용을 검토해보자.  
    이번 Test는 LinearLayout을 RelativeLayout이 감싸고 있는 형태이다.
    그래서 위의 스택에서 1,2,3번이 LinearLayout으로 인한 스택이고,
    6,7,8번 / 10,11,12번 / 15,16,17번도 마찬가지이다.
    그런 관점에서 RelativeLayout의 Measure가 호출되기 전에는
    LinearLayout의 Measure 스택 두번,
    TextView0의 Measure 두번이 호출되는 구조이다.


4. 
외부 LinearLayout, 내부 RelativeLayout
 

   1) LinearLayout.onMeasure()
   2) TextView0.onMeasure()
   3) TextView2.onMeasure()
   4) TextView1.onMeasure()
   5) TextView1.onMeasure()
   6) TextView2.onMeasure()
   7) 
RelativeLayout.onMeasure()
  -------------------------------------
   8) LinearLayout.onMeasure()
   9) TextView0.onMeasure()
   10) TextView2.onMeasure()
   11) TextView1.onMeasure()
   12) TextView1.onMeasure()
   13) TextView2.onMeasure()
   14) 
RelativeLayout.onMeasure()
  
   같은 구조의 UI를 구성하지만, 단순히 LinearLayout을
   가장 밖으로 빼고, RelativeLayout을 안쪽으로 두었을 뿐인데,
   Measure의 호출이 4회(22%)나 감소하였다.
   만약 조금 더 복잡한 UI였다면, 큰 성능상 손실이 있었을 수도 있겠다.




5. 외부 FrameLayout, 내부 LinearLayout
   : 3번 Test(외곽 RelativeLayout, 내부 LinearLayout)의 개선으로
    RelativeLayout 대신 FrameLayout을 쓰는 경우 Measure 스택을 살펴보자.
    위의 코드와 완벽하게 동일한 UI구성을 나타내는 것은 아니지만
    UI를 중첩해서 노출해야 하는 경우들이 빈번하게 나타나는데,
    그때 RelativeLayout 대신 FrameLayout을 쓰는 것이 더 효율적인지
    알아보기 위함
이다. 

    


    1) TextView0.onMesaure()
    2) LinearLayout.onMeasure()
    3) TextView1.onMesaure()    4) TextView2.onMesaure()
    5) FrameLayout.onMeasure()
   ------------------------------------
    6) TextView0.onMesaure()
    7) LinearLayout.onMeasure()
    8) TextView1.onMesaure()    9) TextView2.onMesaure()
    10) FrameLayout.onMeasure()

    결과로 놓고 보면, 중첩되는 UI를 구성해야하는 경우에는 FrameLayout을
    사용하는 것이 훨씬 유리한 것으로 보인다.
    다만 이렇게 구성하게 되면 GPU Over Draw가 RelativeLayout에 비해서 
    많이 발생할텐데, 이 점에 대해서 충분히 고려를 하고
    작업을 해야할 것 같다.

 간단한 UI 구성 Test

  이제는 위에서 살펴본 내용을 토대로 아주 간단한 UI를 통해 각 Layout들의 효율을 확인해보려고 한다. 
   
  1. 목표
     : 간단한 UI 구성을 하는데 호출되는 onMeasure()의 순서와 횟수 확인
      

  2. UI 구성

    맨 위에 TextView가 하나 있고,
    그 아래로 두개의 TextView가 수평으로 붙어있는 형태



  3. LinearLayout으로 구성
    : 오로지 LinearLayout으로만 구성하려다 보니,
     어쩔수 없이 아래와 같은 LinearLayout 2단 구성이 필요했다.


    1) LinearLayout1.onMeasure()
    2) TextView0.onMeasure()
    3) LinearLayout2.onMeasure()
    4) TextView1.onMeasure()
    5) TextView2.onMeasure()
   --------------------------------------

    6) LinearLayout1.onMeasure()
    7) TextView0.onMeasure()
    8) LinearLayout2.onMeasure()
    9) TextView1.onMeasure()
    10) TextView2.onMeasure()
      
    위와 같이, onMeasure()는 두번씩 호출이 된다.
    앞서 살펴봤던 내용이지만, 2 Layer로 구성이 되어있어서,
    불필요한 내부 Layout의 onMeasure()가 호출된다는 단점이 있다.
    총 10회의 onMeasure()


  2. RelativeLayout으로 구성
    : 일반적으로 LinearLayout에서 Depth를 줄이기 위해
     아래와 같이 RelativeLayout을 쓰게 되는데, 이 경우는 어떨까?
   1) TextView1.onMeasure()
   2) TextView2.onMeasure()
   3) TextView0.onMeasure()
   4) TextView0.onMeasure()
   5) TextView1.onMeasure()
   6) TextView2.onMeasure()
   7) RelativeLayout.onMeasure()
  -----------------------------------
   8) TextView1.onMeasure()
   9) TextView2.onMeasure()
   10) TextView0.onMeasure()
   11) TextView0.onMeasure()
   12) TextView1.onMeasure()
   13) TextView2.onMeasure()
   14) RelativeLayout.onMeasure()
   
   Layer는 1단으로 줄어들었지만, 상호간의 관계를 파악하기 위해
   Layout의 Measure 1회당 ChildView는 2번의 Measure를 수행한다. 

   총 14회의 onMeasure()
   LinearLayout대비 40%가 증가한 모양이다.
   분명 Depth는 줄었지만, Measure는 늘어난... 



3. ConstraintLayout
   : UI 구성은 아직 속성들에 대한 이해가 부족해서
    Android Studio에서 제공하는 GUI Tool을 이용해서 구성하였다.
    (그러다보니 코드에 불필요한 내용들이 포함되어있기도 하다)

   1) TextView0.onMeasure()
   2) TextView1.onMeasure()
   3) TextView2.onMeasure()
   4) ConstraintLayout.onMeasure()
  -------------------------------
   5) TextView0.onMeasure()
   6) TextView1.onMeasure()
   7) TextView2.onMeasure()
   8) ConstraintLayout.onMeasure()

    전체적으로 Measure는 2단계로 진행되지만,
    ChildView들은 각 단계마다 1회씩만 호출되는 모습을 보임
    (앞에서 살펴본 2-Layer LinearLayout 구성에서 내부 Layout만 제거된 형태와 같다.)
    총 8회의 onMeasure()

    어떻게 보면 가장 이상적인 형태의 Measure Stack이 아닐까 싶다.
    딱 순서대로 한번씩만 호출이 되는 구조이니까...
    


3. 결론
   Measure만 놓고 보자면, ConstraintLayout의 압승이 아닐까 싶다. 
   과연 다른 부분들에서는 어느정도 차이가 나게될지 확인해야할 듯 하다.

댓글

이 블로그의 인기 게시물

[Android] DataBinding의 동작방식 - 4. include Tag 혹은 ViewStub 사용시의 Binding

[Android] DataBinding의 동작방식 - 5. Listener, Callback (CustomView의 Callback을 람다식으로 Binding하기)

[Android] DataBinding의 동작방식 - 2. BindingAdapter의 기본 및 사용 시점