Programming/Android

[번역] RecyclerView를 Wrapping했을 때 퍼포먼스 이슈 - feat.NestedScrollView

YK Choi 2022. 3. 4. 19:36

Bartek Lipinski의 Medium 게시물 

https://medium.com/android-news/android-dev-tip-4-91b7757b1f0a

 

Android Dev Tip #4

The performance of wrapping content in RecyclerView

medium.com

을 번역한 내용입니다. 오타 또는 오역이 있다면 댓글로 남겨주시면 수정하겠습니다.

 

[Tip]

When RecyclerView is wrapping its content, it’s not recycling anymore. Every record in the dataset has an item View kept in memory for as long as the RecyclerView is in the layout hierarchy.

[Tip 번역]

RecylerView의 내용물을 감쌌을때, RecyclerView는 내용물을 더이상 재활용하지 않습니다. Item View의 모든 데이터들은 RecyclerView가 layout 내부에 있는 한 메모리에 남아있을 것입니다.

 

- 해석 주의 : RecyclerView에서 wrap_content 를 하는것이 문제가 된다는 뜻이 아닙니다. 해당 게시물의 댓글에서도 다시 언급되는 내용으로, 여기서의 wrapping content는 RecyclerView가 Scroll 내부에 있다는것으로 해석해야합니다.

 따라서 RecyclerView가 `wrapping content`, `in layout hierarchy` 라는 것은 상위에 스크롤 내부에 있다는 것으로 해석하였습니다.

 


설명:

RecyclerView의 기본 개념은 간단합니다

1. 당신은 View들로 이루어진 스크롤 가능한 영역(RecyclerView)을 얻습니다.

 2. Item이 스크린 상의 보이지 않는 영역으로 넘어갔을때, RecyclerView는 그것을 구성요소에서 제외시킵니다.

3. 그렇게 제외된 Item은 스크롤로 인해 새롭게 보여지는 다른 Item 의 View에서 사용될 수 있습니다.

 

이 모든 것은 RecyclerView를 Scroll 내부에 넣었을 때 곤란해집니다. 그렇게 될 경우 RecyclerView는 item의 개수만큼 자식(ItemView) 개수를 갖게 됩니다. 그 결과, RecyclerView는 더이상 자체적으로 스크롤 가능한 존재가 아니게 되고 모든 Item들은 RecyclerView에서 벗어날 수 없게됩니다. RecyclerView는 모든 Item영역들을 갖고 있게 됩니다.

 

이러한 모든 것들은 하나의 간단한 사실로 도출합니다 :

그 어떤 Item도 스크롤에서 벗어날 수 없다면, 모든 Item들은 재활용될 수 없다.

 

RecyclerView가 Scroll 계층 내부에 있다면, 모든 item들의 View들은 메모리에 남아있게 됩니다.

 

그것이 당신에게 여전히 문제로 여겨지지 않는다면, 이런 가정을 해보겠습니다 :

만약 당신의 RecyclerView에서 DB Table의 모든 row를 보여주어야하는데, Table에 726개의 row가 있다면 당신은 726개의 아이템 View들을 메모리에 전부 올리게 되는 것입니다.

 

근본적으로, 당신은 Adapter를 사용한 수많은 View를 생성하게 됩니다. 하지만 이때의 성능은 ScrollView 내에 일일이 모든 View들을 추가하는 것과 동일합니다. RecyclerView를 쓰면서 생겼던 성능 이득이 더이상 존재하지 않는 것입니다.

 


예시:

아래의 Layout을 생각해보시죠

<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
           android:layout_width="match_parent"
           android:layout_height="100dp"
           android:scaleType="centerCrop"
           android:src="@drawable/test_image"/>
        
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#3F51B5"
            android:text="Example"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"/>
    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

 

그리고 RecyclerView에서 Item은 아래를 inflate합니다.

<TextView
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

 

만약 RecyclerView의 Adapter가 30개의 데이터를 들고 있다면, 해당 화면은 아래와 같을 겁니다:

 

 

처음에는 문제가 없어 보일 것입니다. 하지만 Layout 계층을 보면(Layout Inspector tool을 사용하여) 이러한 것을 볼 수 있습니다.

 

 

RecyclerView의 모든 Item View들은 항상 메모리에 올라가 있습니다. 현재 보이지 않는 Item들까지 포함합니다.

 

똑같은 상황에서 NestedScrollView를 사용하지 않았을때의 상황을 비교해 보시죠.(같은 데이터셋)

 

어느 시점에서든, 오직 RecyclerView의 스크롤 범위 내의 View 만이 메모리에 올라가 있습니다.

 

 


 

 

번외 - 블로그 내의 댓글) 

Question -> 그렇다면 RecyclerView 내에 ImageView와 TextView를 하나의 셀로 만드는 대신, 다른 대안은 없는 건가요?

Anwer -> Adapter 내에 Header를 넣는것이 당신이 할 수 있는 최선일 것입니다.

  참고 - RecyclerViewHeader

  Adapter 를 여러 ViewType으로 생성할 수 있도록 배우는 것을 강력하게 추천합니다. 오랫동안 당신에게 도움이 될것입니다.

 


 

번역 후 나의 생각

- 블로그 내에서 wrapping content라는 워딩이 계속 걸려서 테스트용 앱에서 wrap_content로 테스트를 해보았는데 Android Studio 내의 Layout Inspector 내에서 Recycling이 잘 되는 것을 확인하였다. 정말로 wrap_content를 말씀하신 게 아니라, RecyclerView 상위 Layout에 스크롤뷰가 있는 경우를 의미하신게 맞는 것 같다.

- 대안책으로 제시해 주신 여러 ViewHolder를 바인딩할 수 있게 구성하는 것이 어떻게 보면 상당히 귀찮은 작업이 될 수도 있다. 하지만 Item을 나눌때 sealed class를 잘 사용하면 코드가 조금이나마 깔끔해질 수 있었다.