* 본 게시물은 구글의 안드로이드 개발자 블로그 게시물을 번역(일부 의역)하여 게재한 게시물입니다.

(원문 보기 : https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html)




ConstraintLayout의 성능 이점에 대한 이해

(Understanding the performance benefits of ConstraintLayout)



지난해 구글 I/O에서 ConstraintLayout이 소개된 이후, 우리는 레이아웃의 안정성 향상과 레이아웃 에디터의 지원을 이어가고 있습니다.

또한 우리는 체인기능(introducing chains : 리니어 그룹과 체인으로 연결법 소개-역자 주-)과 비율에 따른 크기 설정(setting size as a ratio)과 같이, 개발자가 다양한 종류의 레이아웃들을 만드는데 도움을 줄만한 ConstraintLayout 특유의 새로운 기능을 추가하고 있습니다.


이런 기능 뿐만 아니라, ConstraintLayout을 이용하면 주목할만한 성능향상을 얻을 수 있습니다. 이번 게시물에서는 개발자들이 어떻게 그런 성능 향상을 얻을 수 있는지에 대해 이야기 해보겠습니다.



안드로이드는 뷰를 어떻게 그리는가? (How Android draws views?)


ConstraintLayout의 실행에 대해 더욱 잘 이해하기 위해, 한걸음 물러서서 안드로이드가 어떻게 뷰를 그리는지에 대해 알아볼 필요가 있습니다.


사용자가 안드로이드의 뷰에 초점을 맞추면, 안드로이드 프레임워크는 뷰에게 직접 뷰 스스로를 그릴 것을 지시합니다. 이 그리기 과정(drawing process)은 다음의 3가지 단계로 구성되어 있습니다.


1. 측정(Measure)


시스템은 각각의 뷰 그룹과 뷰 구성요소들의 크기와 위치를 결정하기 위해 뷰 트리의 하향식 탐색(top-down traversal)을 수행합니다.

뷰 그룹이 측정될 때에는, 뷰 그룹에 속한 뷰들(children)도 함께 측정됩니다.


2. 레이아웃(Layout)


측정 단계에서 결정된 뷰그룹 하위에 속한 오브젝트들의 사이즈를 이용하여 뷰그룹의 위치가 결정됨과 함께, 또 다른 뷰 트리의 하향식 탐색이 일어납니다.


3. 그리기(Draw)


시스템은 아직 또 다른(레이아웃 단계에서 일어난) 하향식 탐색을 수행하고 있습니다. 뷰 트리에 속한 오브젝트들을 위해,

GPU가 수행할 그리기 명령들(a list of drawing commands to the GPU)을 담은 리스트를 보낼 Canvas 오브젝트가 생성됩니다. 

그리기 명령들은 앞의 두 단계(측정과 레이아웃)에서 시스템이 결정한, 뷰 그룹과 뷰 오브젝트들의 사이즈와 위치가 포함되어 있습니다.  



  

그림 1. 뷰 트리에서 어떻게 하향식 탐색 측정이 이뤄지는지에 대한 예제



그리기 과정 내의 모든 단계에서는 뷰 트리에 대한 하향식 탐색을 필요로 합니다. 그러므로, 개발자가 각 뷰 계층내에 포함시킨 뷰가 많아질 수록, 디바이스가 뷰를 그리기 위해 더 많은 시간과 연산 능력을 필요로 하게 됩니다. 개발자의 안드로이드 앱 레이아웃들이 수평적인 계층을 유지함을 통해, 개발자는 개발자의 앱을 위해 더 빠르고 즉각적인 유저 인터페이스를 만들 수 있습니다.



전통적인 레이아웃 계층의 비용(The expense of a traditional layout hierarchy)


앞의 설명을 염두에 두고, LinearLayout과 RelativeLayout 오브젝트 같은 전통적인 레이아웃 계층을 만들어 봅시다.


 


그림 2. 예제 레이아웃


우리가 위의 이미지와 같은 레이아웃을 만든다고 가정해 봅시다. 만약 개발자가 전통적인 레이아웃으로 이걸 만든다면, XML 파일이 포함하는 요소 계층은 아래와 유사할 것입니다.(이 예제에서는, attributes를 생략합니다.)



<RelativeLayout>
  <ImageView />
  <ImageView />
  <RelativeLayout>
    <TextView />
    <LinearLayout>
      <TextView />
      <RelativeLayout>
        <EditText />
      </RelativeLayout>
    </LinearLayout>
    <LinearLayout>
      <TextView />
      <RelativeLayout>
        <EditText />
      </RelativeLayout>
    </LinearLayout>
    <TextView />
  </RelativeLayout>
  <LinearLayout >
    <Button />
    <Button />
  </LinearLayout>
</RelativeLayout>

 

보통 이 뷰 계층의 타입에는 개선의 여지가 있기는 하지만, 여전히 개발자는 중첩된 뷰가 있는 계층을 만들 필요가 있을 것입니다. 


이전에 논의했던 것 처럼, 중첩된 계층 구조들은 성능에 악영향을 끼칠 수 있습니다. 안드로이드 스튜디오의 Systrace 도구를 사용하여 중첩된 뷰들이 실제로 UI 성능에 어떻게 영향을 미치는지 보겠습니다. 우리는 각 뷰그룹(ConstraintLayout과 RelativeLayout)을 위한 측정과 레이아웃 단계를 프로그래밍적으로 호출했으며 측정과 레이아웃 단계의 호출이 실행중일 때 Systrace를 작동시켰습니다.

아래의 명령은 20초 사이에 일어나는 고비용의 측정/레이아웃 작업(expensive measure/layout passes)과 같은 주요 이벤트들(key events)을 포함한 개요 파일을 생성합니다.


python $ANDROID_HOME/platform-tools/systrace/systrace.py --time=20 -o ~/trace.html gfx view res

   

Systrace를 어떻게 사용하는지에 대한 자세한 설명은, Systrace를 통한 UI 성능 분석(Analyzing UI Performance with Systrace) 가이드를 보기 바랍니다.


Systrace는 이 레이아웃의 (수많은) 성능 문제에 대해 자동적으로 강조할 뿐만 아니라, 그 문제를 해결할 제안도 제공합니다. 개발자는 "Alerts" 탭을 누르면, 개발자는 이 뷰 계층에서 측정과 레이아웃 단계에서 80번의 고비용의 작업이 필요하다는 걸 알게 될 것입니다!


많은 고비용의 측정과 레이아웃단계는 이상적인 것과 거리가 멉니다; 많은 양의 그리기 작업(drawing activity)은 사용자가 알아차릴정도의 프레임 건너 뛰기를 발생시키기 때문입니다. 이런 이유 때문에, 개발자는 중첩된 계층과 그 하위구조를 두 번 측정하는 RelativeLayout이 낮은 성능을 가지고 있다고 결론낼 수 있습니다.  




그림 3. RelativeLayout을 사용하는 레이아웃 변형으로 인한 Systrace의 알림 보기



우리가 측정을 어떻게 수행하였는지에 대한 전체 코드는 우리의 Github repository에서 확인할 수 있습니다.



ConstraintLayout 오브젝트의 이점


만약 같은 레이아웃을 ConstraintLayout으로 만들었다면, XML 파일은 아래와 같은 요소 계층을 포함하고 있을 것입니다.(attributes는 다시한번 생략합니다.)


<android.support.constraint.ConstraintLayout>
  <ImageView />
  <ImageView />
  <TextView />
  <EditText />
  <TextView />
  <TextView />
  <EditText />
  <Button />
  <Button />
  <TextView />
</android.support.constraint.ConstraintLayout>


예제에서 보여주는 것처럼, 레이아웃은 이제 완전히 수평적인 계층구조를 갖게 되었습니다. 이것은 ConstraintLayout이 중첩된 뷰와 뷰그룹 구조 없이도 복잡한 레이아웃을 만들 수 있도록 해주었기 때문입니다.


예를 들어, 레이아웃의 가운데에 있는 TextView와 EditText를 보도록 합시다:



RelativeLayout을 사용했을 때, 개발자는 TextView와 EditText를 정렬할 새로운 ViewGroup을 만들어야 했습니다: 



 <LinearLayout    android:id="@+id/camera_area" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_below="@id/title" > <TextView android:text="@string/camera" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:id="@+id/cameraLabel" android:labelFor="@+id/cameraType" android:layout_marginStart="16dp" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <EditText android:id="@+id/cameraType" android:ems="10" android:inputType="textPersonName" android:text="@string/camera_value" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginTop="8dp" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" /> </RelativeLayout> </LinearLayout>

 

ConstraintLayout을 (RelativeLayout) 대신 사용하여, 개발자는 또다른 뷰 그룹을 만들지 않고도 TextView의 기준선에서 EditText의 기준선까지 제약 조건을 추가하는 것만으로 똑같은 효과를 얻을 수 있습니다.



그림 4. EditText와 TextView 사이의 제약 조건



<TextView       android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintLeft_creator="1" app:layout_constraintBaseline_creator="1" app:layout_constraintLeft_toLeftOf="@+id/activity_main_done" app:layout_constraintBaseline_toBaselineOf="@+id/cameraType" />


ConstraintLayout을 사용한 레이아웃에서 Systrace 도구를 작동시켰을 때, 개발자는 같은 20초 사이에 훨씬 적은 고비용 측정/레이아웃 작업이 일어난 것을 확인할 수 있습니다. 이 성능향상이 납득이 가는건, 개발자가 뷰 계층을 수평적으로 유지했기 때문입니다! 




그림 5. ConstraintLayout을 사용하는 레이아웃 변형으로 인한 Systrace의 알림 보기



또 주목할 점은, 우리가 ConstraintLayout 변형을 XML 직접 작성 대신 레이아웃 에디터(layout editor)만을 이용했다는 것입니다. 같은 시각 효과를 RelativeLayout을 사용하여 얻기 위해서는, 아마도 XML을 직접 손으로 수정해야 할 필요가 있었을 것입니다.



성능 차이의 측정(Measuring the performance difference)


우리는 안드로이드 7.0(API level 24)에서 소개한 OnFrameMetricsAvailableListener를 사용하여, ConstraintLayout과 RelativeLayout 두 가지의 레이아웃이 모든 측정과 레이아웃 패스를 수행하는데에 소요되는 시간을 분석했습니다. OnFrameMetricsAvailableListener 클래스는 개발자의 앱의 UI 렌더링에 대한 프레임별 시간 정보를 수집할 수 있습니다.


아래의 코드를 호출하여, 프레임 당 UI 작업을 기록할 수 있습니다.  


window.addOnFrameMetricsAvailableListener(
        frameMetricsAvailableListener, frameMetricsHandler);


시간 정보를 사용할 수 있게 된 후, 앱은 frameMetricsAvailableListener() 콜백을 동작시킵니다. 우리는 측정/레이아웃 성능에 관심이 있기에, 실제 프레임 지속 시간을 검색할 때 FrameMetrics.LAYOUT_MEASURE_DURATION을 호출했습니다.


Window.OnFrameMetricsAvailableListener {
        _, frameMetrics, _ ->
        val frameMetricsCopy = FrameMetrics(frameMetrics);
        // Layout measure duration in nanoseconds
        val layoutMeasureDurationNs = 
                frameMetricsCopy.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);


FrameMetrics가 받을 수 있는 다른 종류의 지속 정보(duration information)에 대해서는 배우기 위해서는 FrameMetrics API 레퍼런스를 보시기 바랍니다.



측정 결과 : ConstraintLayout이 빠르다.



그림 6. 측정 / 레이아웃 (단위 : ms, 100 프레임 평균)



이 결과가 보여주듯, ConstraintLayout이 전통적인 레이아웃들보다 더 나은 성능을 보입니다. 게다가 ConstraintLayout은 ConstraintLayout 오브젝트의 이점 섹션에서 다룬 것 처럼, 개발자가 복잡하면서도 고성능인 레이아웃들을 만들 수 있는 도와주는 다른 기능들을 갖고 있습니다.

자세한 사항은, ConstraintLayout으로 반응형 UI 만들기(Build a Responsive UI with ConstraintLayout) 가이드를 보시기 바랍니다. 우리는 개발자가 앱의 레이아웃을 디자인할 때 ConstraintLayout을 사용하기를 추천합니다. 이전에는 깊이 중첩된 레이아웃(deeply-nested layout)이 필요했던 거의 모든 상황에서, ConstraintLayout은 최적의 성능과 사용의 편의성을 가진 레이아웃이 될 것입니다.



추가 : 측정 환경(Appendix : Measure environment)  


위에서 수행했던 모든 측정들은 아래의 환경에서 진행되었습니다.


기기 : Nexus 5X

안드로이드 버전 : 8.0

ConstraintLayout 버전 : 1.0.2



이 다음은?(What's next)


개발자 가이드, API 레퍼런스 문서, 그리고 Medium 블로그 게시물을 확인하여 개발자를 위해 제공하는 ConstraintLayout에 대해 완전히 이해하도록 합니다. 그리고 다시 한번, 수 개월간 ConstraintLayout에 대한 우리의 알파버전에 피드백과 이슈사항을 등록해준 모든 분들께 감사 드립니다. 우리는 올해 초에 제작 가능한 1.0 버전을 릴리즈 할 수 있었던 것에 진심으로 감사히 생각합니다. 우리가 ConstraintLayout을 지속적으로 향상시키는 동안, Android issue tracker를 사용하여 지속적인 피드백을 보내주시기 바랍니다.     





+ Recent posts