<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Vive Hodie</title>
    <link>https://hodie.tistory.com/</link>
    <description>완벽이 아닌 향상을 추구합니다</description>
    <language>ko</language>
    <pubDate>Wed, 19 Aug 2026 17:42:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Hodie!</managingEditor>
    <image>
      <title>Vive Hodie</title>
      <url>https://tistory1.daumcdn.net/tistory/3076017/attach/265fe8287796473291503a7a09e3c579</url>
      <link>https://hodie.tistory.com</link>
    </image>
    <item>
      <title>[Android/Project] 기존 앱에 Jetpack Compose 도입기: LazyColumn 활용</title>
      <link>https://hodie.tistory.com/125</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존 앱에 Jetpack Compose 도입기: LazyColumn 활용&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지난 7월말 Google I/O Extended Seoul 컨퍼런스를 다녀오고, 여러 자료들을 보면서 더이상 Compose에 대한 공부를 늦추면 안되겠다는 생각을 많이하게 되었다.&lt;/li&gt;
&lt;li&gt;특히, 최근 업데이트 과정에서 Compose에 대한 지원이 아낌없이 이루어지고 있는 점, Figma와 같은 협업 툴에서도 관련 내용들이 속속들이 등장하고 있는 모습을 보면서, XML은 여전히 많이 사용되고있지만 점차 저물고 있는 해라는 생각을 많이 하게되었고 언제까지 XML만 사용할 수 있을까? 하는 의문이 많이 들었다.&lt;/li&gt;
&lt;li&gt;따라서, 현재 진행하고 있는 프로젝트에서 새롭게 화면을 만들어야 하는 부분을 Compose로 개발하기로 확정짓고 개발하게 되었다.&lt;/li&gt;
&lt;li&gt;Compose로 처음 개발하는 만큼, 안드로이드를 처음 배웠을 때가 오버랩되면서 만족스러운 코드가 나왔다고는 할 수 없지만, Compose를 처음 접하는 입장에서 개발한 과정을 공유하면 좋을 것 같다는 생각이 들어 작성하게 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개발 기능&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compose로 처음 개발하는 만큼, 다양한 기능이 존재하는 화면보다는 단순한 화면에서부터 적용시키는 것이 사이드 이펙트가 적을것이라고 판단하였고 사이드 이펙트가 적은 화면을 개발 대상 기준으로 하였다.&lt;/li&gt;
&lt;li&gt;마침, 개발하고 있는 앱이 좋아요 기능이 존재하는데 좋아요를 누른 사람의 리스트가 보여지는 화면을 만들어야 하는 상황이라 해당 화면을 Compose로 만들어보기로 했다. 좋아요 리스트를 보여주는 것이외에는 많은 동작이 없는 화면이라 적합하다고 판단하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, XML로 만들어진 레이아웃에도 ComposeView를 이용해 Compose로 개발할 수 있지만, 새 화면에서 Compose로 개발을 진행하면, 좀 더 Compose에 대해 잘 사용할 수 있을 것 같아 새로운 화면을 선택하였다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;기본 세팅&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compose를 통해 개발하기 위해서는 개발에 앞서 여러 세팅들을 요구 한다.&lt;/li&gt;
&lt;li&gt;대부분 &lt;a href=&quot;https://developer.android.com/jetpack/compose/setup&quot;&gt;&lt;b&gt;공식문서&lt;/b&gt;&lt;/a&gt;에 있는대로 차근차근 따라가면 된다.
&lt;div&gt;&lt;del&gt;물론, 버전업을 하면서 다른 의존성과의 호환성 문제가 있을 수 있다.&lt;/del&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;build.gradle (app 수준)&lt;/code&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Compose 버전 설정시 현재 Kotlin Version과 Compose Compiler Version의 호환성 체크
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/jetpack/androidx/releases/compose-kotlin#pre-release_kotlin_compatibility&quot;&gt;Pre-release Kotlin Compatibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;android {
    buildFeatures {
        compose = true
    }

    composeOptions {
        kotlinCompilerExtensionVersion = &quot;본인의 Compose Version&quot;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;이후 원하는 의존성 선택적으로 추가&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;dependencies {

 val composeBom = platform(&quot;androidx.compose:compose-bom:2023.08.00&quot;)
 implementation(composeBom)
 androidTestImplementation(composeBom)

     ...

}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 앱에 Compose를 도입하기 위한 구현 순서&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (25).png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYddb3/btsq2PFW4ms/tTU0NbgZEGjO96kUknLJn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYddb3/btsq2PFW4ms/tTU0NbgZEGjO96kUknLJn0/img.png&quot; data-alt=&quot;만들어야 하는 페이지 레이아웃 샘플&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYddb3/btsq2PFW4ms/tTU0NbgZEGjO96kUknLJn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYddb3%2Fbtsq2PFW4ms%2FtTU0NbgZEGjO96kUknLJn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;만들어야 하는 페이지 레이아웃 샘플&quot; loading=&quot;lazy&quot; width=&quot;244&quot; height=&quot;434&quot; data-filename=&quot;Untitled (25).png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;1280&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;만들어야 하는 페이지 레이아웃 샘플&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기존 앱은 &lt;code&gt;하나의 Activity&lt;/code&gt;에 &lt;code&gt;여러 개의 Fragment&lt;/code&gt;을 &lt;code&gt;Navigation Component&lt;/code&gt;를 이용해 각 화면을 구성하고 있던 상태였고, &lt;b&gt;이를 고려&lt;/b&gt;했어야 했다.&lt;/li&gt;
&lt;li&gt;구현해야 하는 주요 부분은 크게 &lt;b&gt;상단 액션바&lt;/b&gt;와, 그 아래 &lt;b&gt;유저에 대한 정보가 담긴 컴포넌트&lt;/b&gt;가 반복되는 레이아웃이라고 볼 수 있다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;액션바&lt;/b&gt;와&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유저 정보가 담긴 컴포넌트에 대한 레이아웃&lt;/b&gt;을 만든뒤&lt;/li&gt;
&lt;li&gt;이를 서버에서 받아온 정보를 활용해 유저에게 &lt;b&gt;리스트뷰 형식으로 보여주면&lt;/b&gt; 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Implementation(구현)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fragment&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 Fragment와 Navigation Component를 활용해, 앱을 구성하고 있던 상황이었기 때문에, 기존 Fragment를 활용하는 방식을 유지하되 Compose를 도입하는 방향으로 생각하게 되었고, 공식문서에서도 이러한 방법을 안내하고 있어 동일하게 진행하였다.&lt;/li&gt;
&lt;li&gt;Compose 공식문서 중 &lt;b&gt;&lt;a href=&quot;https://developer.android.com/jetpack/compose/migrate/strategy&quot;&gt;Migration Strategy&lt;/a&gt;의 Build new feature with Compose - New Screens&lt;/b&gt; 참고해 새로운 화면을 만들었다.&lt;/li&gt;
&lt;li&gt;&amp;rarr; 이를 활용하면, XML없이도 Compose를 이용해 기존 앱에서 레이아웃을 구현할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;class PostLikeUsersFragment : Fragment() {
        override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
            setContent {
                MaterialTheme {
                    Scaffold(topBar = { SetPostLikeUsersActionbar() }) { contentPadding -&amp;gt;
                        Box(modifier = Modifier.padding(contentPadding)) {
                            SetPostLikeUsers()
                        }
                    }
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;setViewCompositionStrategy&lt;/code&gt;는 &lt;code&gt;Composition&lt;/code&gt;을 &lt;b&gt;&lt;code&gt;Dispose&lt;/code&gt;해야하는 시기를 정의&lt;/b&gt;하는 역할을 한다.&lt;br /&gt;&lt;span&gt;&lt;br /&gt;기본적으로&lt;/span&gt; &lt;span&gt;설정되어&lt;/span&gt; &lt;span&gt;있는&lt;/span&gt; &lt;code&gt;DisposeOnDetachedFromWindowOrReleasedFromPool&lt;/code&gt;&lt;span&gt;는&lt;/span&gt; &lt;b&gt;&lt;code&gt;View&lt;/code&gt;&lt;span&gt;가&lt;/span&gt; &lt;code&gt;Window&lt;/code&gt;&lt;span&gt;으로부터&lt;/span&gt; &lt;code&gt;detach&lt;/code&gt; &lt;span&gt;될&lt;/span&gt; &lt;span&gt;때마다&lt;/span&gt; &lt;code&gt;Composition&lt;/code&gt;&lt;span&gt;을&lt;/span&gt; &lt;span&gt;삭제&lt;/span&gt;&lt;span&gt;한다&lt;/span&gt;.&lt;/b&gt; &lt;br /&gt;(&lt;code&gt;RecyclerView&lt;/code&gt;&lt;span&gt;와&lt;/span&gt; &lt;span&gt;같이&lt;/span&gt; &lt;code&gt;pooling container&lt;/code&gt;&lt;span&gt;의&lt;/span&gt; &lt;span&gt;일부가&lt;/span&gt; &lt;span&gt;아닌&lt;/span&gt; &lt;span&gt;경우에&lt;/span&gt;)&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;따라서,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;code&gt;ComposeView&lt;/code&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;code&gt;Fragment View&lt;/code&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;에 있을 떄 사용하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;code&gt;DisposeOnViewTreeLifecycleDestroyed&lt;/code&gt;로 변경해 설정한다.&lt;/li&gt;
&lt;li&gt;전적으로 &lt;code&gt;Compose&lt;/code&gt;에 기반하는 새로운 화면을 생성하기 위해서 &lt;b&gt;XML 파일을 사용하는 대신 &lt;code&gt;setContent()&lt;/code&gt; 메서드를 호출&lt;/b&gt;하고, &lt;b&gt;이 메서드 내에서 레이아웃을 정의&lt;/b&gt;한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서, 이 메서드 내에서 주요 UI를 구현하는 &lt;code&gt;Action bar 구현 메서드&lt;/code&gt;와 &lt;code&gt;유저 리스트를 구성하는 메서드&lt;/code&gt;를 호출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;가장 기본적인 Compsable UI를 작성하는 방식은 &lt;code&gt;Column&lt;/code&gt;, &lt;code&gt;Row&lt;/code&gt;를 사용하는데, &lt;code&gt;LinearLayout&lt;/code&gt;과 유사하다고 보면 될 것 같다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만 여기선 &lt;b&gt;유저 리스트 1개만 존재하므로 &lt;code&gt;Box&lt;/code&gt;로 구성&lt;/b&gt;하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Scaffold&lt;/code&gt;는 Material design의 기본 layout 구조를 만들기 위한 상위 레벨의 slots를 제공한다. &lt;code&gt;TopAppBar&lt;/code&gt;, &lt;code&gt;BottomAppBar&lt;/code&gt;, &lt;code&gt;FloatingActionButton&lt;/code&gt;, &lt;code&gt;Drawer&lt;/code&gt;의 &lt;b&gt;slot을 제공해 쉽게 구현&lt;/b&gt;할 수 있도록 해주며, &lt;b&gt;이 부분들을 채워 넣으면서 알맞은 위치에 components를 배치하고 동작&lt;/b&gt;하도록 할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 특이한 점은 &lt;code&gt;contentPadding&lt;/code&gt; 부분인데, &lt;code&gt;Scaffold&lt;/code&gt;의 &lt;code&gt;content에 해당하는 람다&lt;/code&gt;에서 &lt;code&gt;다른 Composable 객체&lt;/code&gt;를 추가할때 처럼 추가하면 &lt;b&gt;영역이 잘리기 때문&lt;/b&gt;에, &lt;code&gt;Padding&lt;/code&gt;값을 &lt;code&gt;TopBar&lt;/code&gt;와 &lt;code&gt;BottomBar&lt;/code&gt;가 차지하는 영역의 크기에 해당하는 &lt;code&gt;&lt;b&gt;contentPadding&lt;/b&gt;&lt;/code&gt;&lt;b&gt;값을 추가해서 정해줘야 하며&lt;/b&gt;, 정해주지 않으면 IDE에서도 경고한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MaterialTheme&lt;/code&gt;는 &lt;b&gt;구성 가능한 함수의 스타일을 지정하는 방법&lt;/b&gt;이다. &lt;code&gt;Theme&lt;/code&gt;를 지정하는 경우에 &lt;code&gt;setContent&lt;/code&gt;에서 &lt;code&gt;Theme&lt;/code&gt;로 감싸주는게 일반적인 형태라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Custom Action Bar&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (26).png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;144&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWRvCC/btsrf5t68a4/YlkkNrzKhmfmkeBZ7c0A01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWRvCC/btsrf5t68a4/YlkkNrzKhmfmkeBZ7c0A01/img.png&quot; data-alt=&quot;구현해야 하는 커스텀 액션바&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWRvCC/btsrf5t68a4/YlkkNrzKhmfmkeBZ7c0A01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWRvCC%2Fbtsrf5t68a4%2FYlkkNrzKhmfmkeBZ7c0A01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;345&quot; height=&quot;69&quot; data-filename=&quot;Untitled (26).png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;144&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구현해야 하는 커스텀 액션바&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Action bar Title 설정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱의 &lt;code&gt;Custom Actionbar&lt;/code&gt;에서 구현해야 하는 부분은 2가지 이다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;뒤로가기 버튼&lt;/li&gt;
&lt;li&gt;타이틀 텍스트&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;    @OptIn(ExperimentalMaterial3Api::class)
    @Composable
    private fun SetPostLikeUsersActionbar() {
        val interactionSource = remember { MutableInteractionSource() }
        CenterAlignedTopAppBar(
            colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
                containerColor = colorResource(id = R.color.white_FFFFFF)
            ),
            title = {
                Text(
                    text = stringResource(id = R.string.post_like_users_title),
                    style = TextStyle(
                        fontSize = 16.sp,
                        lineHeight = 24.sp,
                        fontWeight = FontWeight(600),
                        color = colorResource(id = R.color.gray_1_313131),
                        textAlign = TextAlign.Center
                    )
                )
            },
            navigationIcon = {
                IconButton(
                    onClick = { findNavController().navigateUp() },
                ) {
                    Icon(
                        painter = painterResource(id = R.drawable.ic_back_sign),
                        contentDescription = &quot;back sign&quot;,
                    )
                }
            }
        )
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Scaffold는 Material3에서 제공하는 요소이고, 모든 Material3 부분은 아직 Experimental에 해당하기 때문에 &lt;code&gt;@OptIn(ExperimentalMaterial3Api::class)&lt;/code&gt; Annotation을 추가시켜야 한다.&lt;/li&gt;
&lt;li&gt;여기서 Title은 상단바에서 한가운데 위치해야하기 때문에 &lt;code&gt;CenterAlignedTopAppBar&lt;/code&gt;를 활용해 구현해주었다.&lt;/li&gt;
&lt;li&gt;뒤로가기의 경우 &lt;code&gt;navigationIcon parameter&lt;/code&gt;에 람다로 넣어주면 된다. 이때 &lt;code&gt;IconButton&lt;/code&gt;을 활용해주면 된다.&lt;/li&gt;
&lt;li&gt;Compose에서도 XML에서와 마찬가지로 &lt;code&gt;리소스&lt;/code&gt;를 가져와 적용할 수 있다. &lt;code&gt;stringResource&lt;/code&gt;, &lt;code&gt;colorResource&lt;/code&gt;, &lt;code&gt;paintingResource&lt;/code&gt; 등을 이용해 리소스를 가져와 적용 시킬수 있고, &lt;code&gt;resource id&lt;/code&gt;를 작성해주면 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Context&lt;/code&gt;를 가져올 수도 있지만, 굳이 사용할 이유는 없어보인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;기존 &lt;code&gt;Android View 시스템의 ImageButton&lt;/code&gt;은 &lt;code&gt;Compose에서 IconButton&lt;/code&gt;과 &lt;b&gt;매칭&lt;/b&gt;되기 때문에, 뒤로가기 버튼의 경우 &lt;code&gt;Icon Button&lt;/code&gt;으로 작성해준다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Button&lt;/code&gt;과의 차이점은 &lt;code&gt;IconButton&lt;/code&gt;은 &lt;b&gt;내부에 보통 &lt;code&gt;Icon&lt;/code&gt;을 포함&lt;/b&gt;하고, &lt;code&gt;Button&lt;/code&gt;은 &lt;b&gt;내부에 보통 &lt;code&gt;Text&lt;/code&gt;를 포함&lt;/b&gt;한다는 점이다.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;IconButton&lt;/code&gt;의 &lt;code&gt;parameter&lt;/code&gt;중 클릭시 어떤 동작을 할지 설정하는 &lt;code&gt;&lt;b&gt;onClick&lt;/b&gt;&lt;/code&gt;과 &lt;code&gt;IconButton에 그려질 Icon&lt;/code&gt;을 포함하는 &lt;code&gt;&lt;b&gt;content&lt;/b&gt;&lt;/code&gt;는 반드시 전달해야 한다.&lt;br /&gt;&lt;br /&gt;또한, &lt;code&gt;content&lt;/code&gt;는 &lt;code&gt;androidx.compose.material.icons.Icons&lt;/code&gt;의 아이콘을 사용하는 아이콘이여야 한다. 만약 &lt;code&gt;사용자 지정 아이콘&lt;/code&gt;을 사용하는 경우 &lt;b&gt;내부 아이콘의 일반적인 크기는 24x24&lt;/b&gt;가 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참고로 &lt;code&gt;IconButton&lt;/code&gt;은 &lt;code&gt;&lt;b&gt;Box&lt;/b&gt;&lt;/code&gt;&lt;b&gt;를 내부에 포함하고 있다. 해당 박스 내부&lt;/b&gt;에 &lt;b&gt;우리가 전달한 &lt;code&gt;content(Icon)&lt;/code&gt;&lt;/b&gt;이 그려지게 된다.&lt;/li&gt;
&lt;li&gt;클릭시 발생하는 &lt;code&gt;이벤트&lt;/code&gt;는 &lt;code&gt;Box&lt;/code&gt;의 &lt;code&gt;Modifier&lt;/code&gt;의 &lt;code&gt;clickable 인자&lt;/code&gt;로 전달되어 &lt;code&gt;&lt;b&gt;이벤트&lt;/b&gt;&lt;/code&gt;&lt;b&gt;가 처리&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좋아요 누른 유저 정보 레이아웃&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (27).png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uLGJb/btsq5W5T3Sd/qKJvBlndRsB36XcTkUrws1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uLGJb/btsq5W5T3Sd/qKJvBlndRsB36XcTkUrws1/img.png&quot; data-alt=&quot;구현해야 하는 유저 정보가 나타나는 레이아웃 리스트 방식&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uLGJb/btsq5W5T3Sd/qKJvBlndRsB36XcTkUrws1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuLGJb%2Fbtsq5W5T3Sd%2FqKJvBlndRsB36XcTkUrws1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;구현해야 하는 유저 정보가 나타나는 레이아웃 리스트&quot; loading=&quot;lazy&quot; width=&quot;313&quot; height=&quot;102&quot; data-filename=&quot;Untitled (27).png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구현해야 하는 유저 정보가 나타나는 레이아웃 리스트 방식&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Paging3&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compose에서도 페이징을 처리하기 위해 Jetpack Paging Library를 사용하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Composable
    private fun SetPostLikeUsers() {
        val likeUsers = postViewModel.postLikeUsers.collectAsLazyPagingItems()
        when (likeUsers.loadState.refresh) {
            is LoadState.Loading -&amp;gt; {
                        //TODO implement loading state
                        }

            is LoadState.Error -&amp;gt; {
                        //TODO implement error state
                        }

            else -&amp;gt; {
                SetPostLikeUsersLayout(likeUsers = likeUsers)
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;postViewModel&lt;/code&gt;으로부터 &lt;code&gt;poseLikeUsers&lt;/code&gt;을 통해 &lt;code&gt;좋아요를 누른 유저리스트가 담긴 정보&lt;/code&gt;를 받아오기 위해 &lt;code&gt;viewModel&lt;/code&gt;로부터 &lt;code&gt;flow&lt;/code&gt;를 &lt;code&gt;collect&lt;/code&gt;를 위한 &lt;code&gt;collectsLazyPagingItems&lt;/code&gt;을 사용해&lt;code&gt;lazyPagingItems&lt;/code&gt;로 받아오게 된다.&lt;br /&gt;&lt;br /&gt;이를 이용하게 되면, &lt;code&gt;Compose&lt;/code&gt;는 유저가 로드한 리스트의 끝에 도달하면 &lt;b&gt;해당 객체를 어떻게 handle&lt;/b&gt;하고, &lt;b&gt;더 많은 아이템을 request하기 위한 방법을 알고 있다.&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;따라서, 해당 객체를 &lt;code&gt;composable&lt;/code&gt; 화면에 전달하면 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추가로, &lt;code&gt;LazyPagingItems&lt;/code&gt; 는 &lt;code&gt;LazyColumn&lt;/code&gt;, &lt;code&gt;LazyRow&lt;/code&gt;, &lt;code&gt;LazyHorizontalGrid&lt;/code&gt; or &lt;code&gt;LazyVerticalGrid&lt;/code&gt;과 같은 &lt;b&gt;lazy 레이아웃에 pass&lt;/b&gt;할 수 있다. 자세한 정보는 &lt;a href=&quot;https://developer.android.com/jetpack/compose/lists#lazy&quot;&gt;here&lt;/a&gt;를 참고하자&lt;/li&gt;
&lt;li&gt;&lt;code&gt;collectAsLazyPagingItmes()&lt;/code&gt;는 cold flow를 &lt;code&gt;LazyPagingItems&lt;/code&gt; 인스턴스로 변환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LazyPagingItems&lt;/code&gt;은 &lt;code&gt;LoadState&lt;/code&gt;에 대한 정보를 가지고 있기 때문에 &lt;code&gt;loading state&lt;/code&gt;를 &lt;b&gt;handling할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;위 코드에서는 &lt;code&gt;refresh event&lt;/code&gt;(처음 로드하거나, data를 invalidating하는)에만 동작하는 코드를 작성했지만, &lt;code&gt;append&lt;/code&gt;, &lt;code&gt;prepend&lt;/code&gt; 등에 대해서도 handling할수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;LazyColumn&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 &lt;code&gt;RecyclerView&lt;/code&gt;로 구성했던 &lt;code&gt;리스트뷰&lt;/code&gt;를 &lt;code&gt;LazyColumn&lt;/code&gt;으로 구현할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만 RecyclerView처럼 하위 항목을 재사용하지는 않는다. 대신 스크롤을 따라 새로운 Composable들을 emit하는데, Android View를 인스턴스화하는 것보다 Composable을 emit하는 것이 상대적으로 Cheap하기 때문에 재활용하지 않더라도 여전히 성능이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;        @Composable
    private fun SetPostLikeUsersLayout(likeUsers: LazyPagingItems&amp;lt;LikeUser&amp;gt;) {
        LazyColumn(
            modifier = Modifier
                .padding(vertical = 4.dp)
                .fillMaxSize()
                .background(color = colorResource(id = R.color.white_FFFFFF))
        ) {
            items(likeUsers.itemCount) { index -&amp;gt;
                val item = likeUsers[index]
                LikeUserLayout(likeUser = item!!)
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 하면, 유저 리스트를 보여줄 수 있다. &lt;code&gt;RecyclerView&lt;/code&gt;를 이용해 리스트 뷰를 구현했던 것과 비교하면 &lt;b&gt;엄청나게 간단해진 셈이다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 &lt;code&gt;RecyclerView&lt;/code&gt; 구현시 작성해야 했던 항목들
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerViewAdapter&lt;/code&gt; 생성&lt;/li&gt;
&lt;li&gt;유저 정보에 대한 &lt;code&gt;XML&lt;/code&gt; 및 &lt;code&gt;ViewHolder&lt;/code&gt; 생성&lt;/li&gt;
&lt;/ol&gt;
&amp;rarr; 기존 UI에서 구현했어야 했던 양에 비하면, &lt;b&gt;코드 양에서부터 엄청나게 간결&lt;/b&gt;해졌다. &lt;b&gt;많은 보일러플레이트 코드를 줄일 수 있다&lt;/b&gt;는 점에서 큰 발전이라고 생각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;items&lt;/code&gt;는 &lt;code&gt;Composable&lt;/code&gt;을 반복해서 나타내고자할 때 사용한다. 여기에서는 유저의 정보를 보여주는 부분에 해당한다.&lt;br /&gt;&lt;br /&gt;특히 &lt;code&gt;items&lt;/code&gt;를 여러 개 구성할 수 있어서 더이상 &lt;code&gt;ConcatAdapter&lt;/code&gt; 같은 것도 필요없다.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;items&lt;/code&gt;의 &lt;code&gt;parameter&lt;/code&gt;에는 코드에서처럼 &lt;code&gt;특정 item 리스트의 count&lt;/code&gt;를 넣을수도 있고, &lt;code&gt;특정 item&lt;/code&gt;을 &lt;b&gt;직접 넣을수도 있다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만, 불과 며칠전에 &lt;code&gt;paging-compose&lt;/code&gt;가 3.2.0으로 업데이트 되면서, &lt;code&gt;LazyListScope&lt;/code&gt;만 지원하는 &lt;code&gt;items(lazyPagingItems)&lt;/code&gt;나 &lt;code&gt;itemsIndexed(lazyPagingItem)&lt;/code&gt;는 Deprecated 되었다. 따라서 이전에 &lt;code&gt;LazyColumn&lt;/code&gt;과 &lt;code&gt;paging3&lt;/code&gt;을 함께 구현했던 여러 블로그에서의 예제 중 &lt;code&gt;items(LazyPagingItems)&lt;/code&gt;는 더이상 사용할 수 없다. &lt;code&gt;count&lt;/code&gt;만 넣어야할 듯 하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;User Layout&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (28).png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S1SP8/btsrhZUc8mI/HpZo9dC0pI6fTTd7g3WKA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S1SP8/btsrhZUc8mI/HpZo9dC0pI6fTTd7g3WKA0/img.png&quot; data-alt=&quot;구현해야 하는 유저 정보가 나타나는 레이아웃&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S1SP8/btsrhZUc8mI/HpZo9dC0pI6fTTd7g3WKA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS1SP8%2FbtsrhZUc8mI%2FHpZo9dC0pI6fTTd7g3WKA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;구현해야 하는 유저 정보가 나타나는 레이아웃&quot; loading=&quot;lazy&quot; width=&quot;325&quot; height=&quot;45&quot; data-filename=&quot;Untitled (28).png&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;구현해야 하는 유저 정보가 나타나는 레이아웃&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;@Composable
    private fun LikeUserLayout(likeUser: LikeUser) {
        Surface(
            color = colorResource(id = R.color.white_FFFFFF),
            modifier = Modifier.fillMaxWidth()
        ) {
            Row(
                verticalAlignment = Alignment.CenterVertically,
                modifier = Modifier
                    .fillMaxWidth()
                    .clickableSingle { navigateUserProfile(likeUser.memberId) }
                    .padding(horizontal = 18.dp, vertical = 8.dp)
            ) {
                LikeUserImageLayout(likeUser = likeUser)
                LikeUserNicknameLayout(userNickname = likeUser.nickname)
                Spacer(modifier = Modifier.weight(1f))
                if (likeUser.memberId != DayoApplication.preferences.getCurrentUser().memberId) {
                    LikeUserFollowLayout(likeUser = likeUser)
                }
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compose에는 margin이 없고, padding만이 존재한다. Modifier에서 padding이 적용된 순서에 따라 margin처럼 적용되기도 하고, padding처럼 적용되기도 한다. 따라서, modifier를 사용할때의 코드의 순서가 매우 중요하다.만약, padding이 clickable보다 앞에 존재하는 경우엔, 우리가 알던 margin처럼 적용되어 padding으로 추가된 공간을 클릭하더라도 아무런 동작도 하지않게 된다.&lt;br /&gt;&lt;br /&gt;이 경우, padding이 가장 마지막으로 적용되어있는데, clickable이 그 앞에 적용되어있다. 이렇게 적용시키게되면, 기존에 우리가 알던 padding처럼 적용되어 padding으로 추가된 공간을 포함에 전체 영역을 클릭할 경우 clickable에 작성한 동작을 하게된다.&lt;br /&gt;&lt;br /&gt;만약 유저가 접속한 본인이라면 팔로우 버튼이 보여져서는 안된다. 따라서, 만드려고 하는 유저 UI가 본인인지에 대한 State에 depend해서 버튼이 만들어지도록 설정했다.&lt;/li&gt;
&lt;li&gt;좌측에 유저의 이미지와 닉네임이 나타나고 우측에 버튼이 나타나는 형태이다. 이미지와 닉네임은 좌측으로 align시키고, 버튼은 우측으로 align시켜야 하기 때문에 닉네임과 버튼 사이에 Spacer를 통해 공간을 주고 weight를 적용시켜 닉네임, 이미지, 버튼의 영역이외에는 Spacer가 모두 채울 수 있도록 공백을 추가시켜줬다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;닉네임에 가중치를 줘도 되지만, 이 경우 닉네임의 영역이 버튼 직전까지 채워지기 때문에 Spacer를 추가시켜주는 것이 적절하다고 생각했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Glide Image&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (29).png&quot; data-origin-width=&quot;90&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXYge2/btsq1Kybs34/thug1IjJW7t9m0F3eq6Vc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXYge2/btsq1Kybs34/thug1IjJW7t9m0F3eq6Vc1/img.png&quot; data-alt=&quot;유저 이미지 예시&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXYge2/btsq1Kybs34/thug1IjJW7t9m0F3eq6Vc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXYge2%2Fbtsq1Kybs34%2Fthug1IjJW7t9m0F3eq6Vc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;유저 이미지 예시&quot; loading=&quot;lazy&quot; width=&quot;90&quot; height=&quot;90&quot; data-filename=&quot;Untitled (29).png&quot; data-origin-width=&quot;90&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;유저 이미지 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;    @Composable
    private fun LikeUserImageLayout(likeUser: LikeUser) {
        GlideImage(
            imageModel = { &quot;Profile Image Url&quot; },
            imageOptions = ImageOptions(
                contentDescription = &quot;image description&quot;,
                contentScale = ContentScale.Crop,
            ),
            modifier = Modifier
                .height(USER_THUMBNAIL_SIZE.dp)
                .aspectRatio(1f)
                .clip(CircleShape)
        )
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지를 표시하기 위해서는 ImageLoader가 필수적인데, 기존엔 Glide를 사용했지만 아직 Glide에서 공식적으로 Compose를 지원하지는 않는것 같다. 다행히, 이러한 상황에 대해 Compose로 Glide로 사용할 수 있게 해놓은 &lt;b&gt;&lt;a href=&quot;https://github.com/skydoves/Landscapist&quot;&gt;오픈소스&lt;/a&gt;&lt;/b&gt;가 있어서 손쉽게 사용할 수 있었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 라이브러리는 Glide이외에도 Coil 등 몇 개의 이미지 로더 라이브러리를 지원하는데, 기존에 Glide를 사용중이었기 때문에, 동일한 스펙으로 맞추는게 좋을 것 같아 GlideImage를 선택하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1:1 비율의 원 모양인 이미지가 필요했기 때문에, Image를 Crop하고, &lt;code&gt;asepectRatio&lt;/code&gt;를 통해 비율을 설정해 준뒤, clip을 통해 원 모양을 설정해주었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Follow Button&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (31).png&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m7kt9/btsq2QERROH/t8qOMU247gFevm47HCyg40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m7kt9/btsq2QERROH/t8qOMU247gFevm47HCyg40/img.png&quot; data-alt=&quot;해당 유저를 팔로우 하지 않음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m7kt9/btsq2QERROH/t8qOMU247gFevm47HCyg40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm7kt9%2Fbtsq2QERROH%2Ft8qOMU247gFevm47HCyg40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;해당 유저를 팔로우하지 않은 상태&quot; loading=&quot;lazy&quot; width=&quot;158&quot; height=&quot;58&quot; data-filename=&quot;Untitled (31).png&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해당 유저를 팔로우 하지 않음&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (30).png&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;96&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFsPmk/btsq5XKsCLq/8lmff2CNfbmfAT8ndo2tYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFsPmk/btsq5XKsCLq/8lmff2CNfbmfAT8ndo2tYK/img.png&quot; data-alt=&quot;해당 유저를 팔로우한 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFsPmk/btsq5XKsCLq/8lmff2CNfbmfAT8ndo2tYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFsPmk%2Fbtsq5XKsCLq%2F8lmff2CNfbmfAT8ndo2tYK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;해당 유저를 팔로우한 상태&quot; loading=&quot;lazy&quot; width=&quot;160&quot; height=&quot;59&quot; data-filename=&quot;Untitled (30).png&quot; data-origin-width=&quot;261&quot; data-origin-height=&quot;96&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;해당 유저를 팔로우한 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;    @Composable
    private fun LikeUserFollowLayout(likeUser: LikeUser) {
        val followInteractionSource = remember { MutableInteractionSource() }
        val followIsPressed by followInteractionSource.collectIsPressedAsState()

        var followState by rememberSaveable { mutableStateOf(likeUser.follow) }
        val followSuccess by .. 
        val unFollowSuccess by ..

        TextButton(
            onClick = {
                if (followState) {
                    DeleteFollow()
                    if (unFollowSuccess) followState = false
                } else {
                                        CreateFollow()
                    if (followSuccess) followState = true
                }
            },
            interactionSource = followInteractionSource,
            colors = ButtonDefaults.buttonColors(
                if (followState or (!followState and followIsPressed)) colorResource(id = R.color.white_FFFFFF)
                else colorResource(id = R.color.primary_green_23C882)
            ),
            contentPadding = PaddingValues(0.dp),
            modifier = Modifier
                .defaultMinSize(1.dp)
                .border(
                    width = 1.dp,
                    color =
                    if (followState or (!followState and followIsPressed)) colorResource(id = R.color.gray_3_9FA5AE)
                    else colorResource(id = R.color.primary_green_23C882),
                    shape = RoundedCornerShape(size = FOLLOW_BUTTON_RADIUS_SIZE.dp),
                )
                .background(
                    color =
                    if (followState or (!followState and followIsPressed)) colorResource(id = R.color.white_FFFFFF)
                    else colorResource(id = R.color.primary_green_23C882),
                    shape = RoundedCornerShape(size = FOLLOW_BUTTON_RADIUS_SIZE.dp)
                )
                .width(FOLLOW_BUTTON_WIDTH.dp)
                .height(FOLLOW_BUTTON_HEIGHT.dp)
        ) {
            Text(
                text = if (followState) stringResource(id = R.string.follow_already)
                else stringResource(id = R.string.follow_yet),
                style = TextStyle(
                    fontSize = 14.sp,
                    lineHeight = 21.sp,
                    fontWeight = FontWeight(600),
                    color = if (followState or (!followState and followIsPressed)) colorResource(id = R.color.gray_3_9FA5AE)
                    else colorResource(id = R.color.white_FFFFFF),
                    textAlign = TextAlign.Center,
                ),
                modifier = Modifier
            )
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 만든 화면에서 가장 복잡했던 코드였는데, State에 따라서 변화되어야 하는 것들이 많았기 때문에 좀 더 신경써서 구현했어야 했다. 다만, 잘 짠 코드라고 생각하진 않아서 아쉬웠지만.. 좀 더 살펴봐서 개선시켜나가고 싶다.&lt;/li&gt;
&lt;li&gt;Jetpack Compose에서 데이터의 상태관리는 Recomposition 될 때에 따라 잘 관리되어야 한다. 이때 remember를 사용하면 특정 상태 변경으로 인해 Recomposition이 되더라도 데이터를 보존시켜줄 수 있다.&lt;br /&gt;&lt;br /&gt;하지만, Configuration이 변경되는 등의 상황에서는 보존을 보장하지 못하므로, 이때는 자동으로 Bundle에 값을 저장할 수 있는 rememberSaveable을 사용해야 한다.&lt;br /&gt;&lt;br /&gt;이 rememberSaveable은 직접 Saver를 구현해 저장하는 조건등의 로직을 커스텀할 수 있도록 구현되어 있다.&lt;/li&gt;
&lt;li&gt;해당 함수는 LazyColumn 내부에서 호출되고 있다. LazyColumn 자체가 RecyclerView처럼 화면에 보여지는 Composable만을 표시하는 scrollable한 Column이다. 그렇기 때문에, 화면 로딩 시간을 최적화 시킬 수 있다는 장점을 가지고 있다.&lt;br /&gt;&lt;br /&gt;하지만, 이러한 이유인 탓인지, LazyColumn 내부에서 remember을 이용해 특정값을 유지시키려고 하더라도 스크롤을 내리는 등의 동작으로 composable이 보여지지 않으면 특정값 유지가 되지 않는 문제가 발생한다. 스크롤을 내리면서 composable이 destroy되고 그에따라 dispose됨에 따라 값이 유지되지 않고 리셋되는 것으로 보인다.&lt;/li&gt;
&lt;li&gt;따라서, 화면을 내리더라도 상태를 유지하기 위해서는, LazyColumn 바깥 영역에서 remember을 사용하거나, Bundle에 값이 저장되는 rememberSaveable을 사용해야 한다.&lt;br /&gt;&lt;br /&gt;위와 같은 화면에서는 LazyColumn 하나하나 상태를 기억해줬어야 했으며, Decomposition이 된 후에 다시 composition이 이루어지더라도 상태가 유지되었어야 했다.&lt;br /&gt;&lt;br /&gt;기본적으로 처음 화면이 보여질 때 최초의 팔로우 여부를 설정한 후, 유저가 팔로우를 하거나 팔로우를 취소하는 상태를 저장하고 스크롤하더라도 그 모습이 유지되어야하기 때문이다. 따라서 이경우에 followState에 대해 rememberSaveable을 사용하였다.&lt;/li&gt;
&lt;li&gt;다만, followInteractionSource의 경우 버튼이 press되고 있는지 여부에 대해 판단하기 위함이였기 때문에 Decomposition되고 다시 initial Composition되었을 때 초기화되더라도 상관없었기 때문에 remember로 작성해주었다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Android</category>
      <category>Android</category>
      <category>Compose</category>
      <category>ComposeView</category>
      <category>jetpack</category>
      <category>Project</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/125</guid>
      <comments>https://hodie.tistory.com/125#entry125comment</comments>
      <pubDate>Sat, 12 Aug 2023 15:50:54 +0900</pubDate>
    </item>
    <item>
      <title>[Review] Google I/O Extended Seoul 2023 참석 후기</title>
      <link>https://hodie.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;Google&amp;nbsp;I/O&amp;nbsp;Extended&amp;nbsp;Seoul&amp;nbsp;2023&amp;nbsp;참석&amp;nbsp;후기&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 오랜만에 안드로이드 내용이 담긴 오프라인 행사에 참석하였다.&lt;/p&gt;
&lt;p&gt;&lt;iframe mapdata=&quot;addr=%EC%84%9C%EC%9A%B8%20%EA%B0%95%EB%82%A8%EA%B5%AC%20%EC%82%BC%EC%84%B1%EB%8F%99%20159%20%EB%AC%B4%EC%97%AD%EC%84%BC%ED%84%B0%EC%BD%94%EC%97%91%EC%8A%A4%204%EC%B8%B5&amp;amp;addtype=1&amp;amp;confirmid=17573702&amp;amp;docid=&amp;amp;idx=1&amp;amp;ifrH=362px&amp;amp;ifrW=490px&amp;amp;mapHeight=362&amp;amp;mapInfo=%7B%22version%22%3A2%2C%22mapWidth%22%3A490%2C%22mapHeight%22%3A362%2C%22mapCenterX%22%3A513000%2C%22mapCenterY%22%3A1114773%2C%22mapLevel%22%3A4%2C%22coordinate%22%3A%22wcongnamul%22%2C%22markInfo%22%3A%5B%7B%22markerType%22%3A%22standPlace%22%2C%22coordinate%22%3A%22wcongnamul%22%2C%22x%22%3A513003%2C%22y%22%3A1114778%2C%22clickable%22%3Atrue%2C%22draggable%22%3Atrue%2C%22icon%22%3A%7B%22width%22%3A35%2C%22height%22%3A56%2C%22offsetX%22%3A17%2C%22offsetY%22%3A56%2C%22src%22%3A%22%2F%2Ft1.daumcdn.net%2Flocalimg%2Flocalimages%2F07%2F2012%2Fattach%2Fpc_img%2Fico_marker2_150331.png%22%7D%2C%22content%22%3A%22%EC%BD%94%EC%97%91%EC%8A%A4%22%2C%22confirmid%22%3A17573702%7D%5D%2C%22graphicInfo%22%3A%5B%5D%2C%22roadviewInfo%22%3A%5B%5D%7D&amp;amp;mapWidth=490&amp;amp;mapX=513000&amp;amp;mapY=1114773&amp;amp;map_hybrid=false&amp;amp;map_level=4&amp;amp;map_type=TYPE_MAP&amp;amp;rcode=1168058000&amp;amp;tel=02-6000-0114&amp;amp;title=%EC%BD%94%EC%97%91%EC%8A%A4&quot; src=&quot;/proxy/plusmapViewer.php?id=maps_1690789765552&quot; id=&quot;maps_1690789765552&quot; width=&quot;540px&quot; height=&quot;350px&quot; frameborder=&quot;0&quot; scrolling=&quot;no&quot; data-ke-type=&quot;map&quot; data-maps-data=&quot;addr=%EC%84%9C%EC%9A%B8%20%EA%B0%95%EB%82%A8%EA%B5%AC%20%EC%82%BC%EC%84%B1%EB%8F%99%20159%20%EB%AC%B4%EC%97%AD%EC%84%BC%ED%84%B0%EC%BD%94%EC%97%91%EC%8A%A4%204%EC%B8%B5&amp;amp;addtype=1&amp;amp;confirmid=17573702&amp;amp;docid=&amp;amp;idx=1&amp;amp;ifrH=362px&amp;amp;ifrW=490px&amp;amp;mapHeight=362&amp;amp;mapInfo=%7B%22version%22%3A2%2C%22mapWidth%22%3A490%2C%22mapHeight%22%3A362%2C%22mapCenterX%22%3A513000%2C%22mapCenterY%22%3A1114773%2C%22mapLevel%22%3A4%2C%22coordinate%22%3A%22wcongnamul%22%2C%22markInfo%22%3A%5B%7B%22markerType%22%3A%22standPlace%22%2C%22coordinate%22%3A%22wcongnamul%22%2C%22x%22%3A513003%2C%22y%22%3A1114778%2C%22clickable%22%3Atrue%2C%22draggable%22%3Atrue%2C%22icon%22%3A%7B%22width%22%3A35%2C%22height%22%3A56%2C%22offsetX%22%3A17%2C%22offsetY%22%3A56%2C%22src%22%3A%22%2F%2Ft1.daumcdn.net%2Flocalimg%2Flocalimages%2F07%2F2012%2Fattach%2Fpc_img%2Fico_marker2_150331.png%22%7D%2C%22content%22%3A%22%EC%BD%94%EC%97%91%EC%8A%A4%22%2C%22confirmid%22%3A17573702%7D%5D%2C%22graphicInfo%22%3A%5B%5D%2C%22roadviewInfo%22%3A%5B%5D%7D&amp;amp;mapWidth=490&amp;amp;mapX=513000&amp;amp;mapY=1114773&amp;amp;map_hybrid=false&amp;amp;map_level=4&amp;amp;map_type=TYPE_MAP&amp;amp;rcode=1168058000&amp;amp;tel=02-6000-0114&amp;amp;title=%EC%BD%94%EC%97%91%EC%8A%A4&quot; data-maps-mapx=&quot;513000&quot; data-maps-mapy=&quot;1114773&quot; data-maps-thumbnail=&quot;https://ssl.daumcdn.net/map3/staticmap/image?center=513000%2C1114773&amp;amp;lv=4&amp;amp;size=540x350&amp;amp;srs=WCONGNAMUL&amp;amp;markers=symbol%3Asc_marker%7Clocation%3A513003%2C1114778&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 참석한 &lt;b&gt;Google I/O Extended Seoul 2023&lt;/b&gt;는 7월 29일 코엑스에서 13시부터 17시까지 진행되었는데, 시간대 별로 1개 내지 2개의 세션이 진행되었고 나는 그 중에 주로 &lt;b&gt;Track 1 세션&lt;/b&gt;을 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 기억이 나지 않기전에, 각 발표자분들이 발표한 내용들 중 &lt;b&gt;새롭게 알게되거나 깊이 공감해서 기억에 남았던 부분들을 중심&lt;/b&gt;으로 작성해 놓으려고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;20230731_202656.jpg&quot; data-origin-width=&quot;2465&quot; data-origin-height=&quot;1387&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oAafA/btspE4DdcEs/JG5PMiLnraNGi5leE2MLo1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oAafA/btspE4DdcEs/JG5PMiLnraNGi5leE2MLo1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oAafA/btspE4DdcEs/JG5PMiLnraNGi5leE2MLo1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoAafA%2FbtspE4DdcEs%2FJG5PMiLnraNGi5leE2MLo1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2465&quot; height=&quot;1387&quot; data-filename=&quot;20230731_202656.jpg&quot; data-origin-width=&quot;2465&quot; data-origin-height=&quot;1387&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참 석 세션 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 행사에서 참석해서 들었던 세션은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. What&amp;rsquo;s New in Android Development Tools - 노현석 (카카오뱅크)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Dagget Hilt로 의존성 주입하기 - 안성용 (네이버웹툰)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. Flutter에 Clean Architecture을 얹어보자 - 양수장 (GDG Songdo / Flutter Songdo)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 일 잘하는 개발자는 회사에서 어떻게 일할까? - 박상권 (헤이딜러)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참석 세션&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. What's New in Android Development Tools&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세션 소개&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Android 앱 개발자를 위한 최신 Android Studio의 새로운 기능을 소개합니다. Android Studio Flamingo/Giraffe/Hedgehog 버전을 다룹니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기억에 남았던 부분&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 세션은 13시 20분 부터 시작했었는데... 다소 늦게 참석한 관계로 중간부분부터 들었다.&lt;/li&gt;
&lt;li&gt;그럼에도 불구하고, Android Studio의 업데이트 된 기능에 대해 잘 모르고 있던 부분이 많았다고 느껴졌고, 생각보다 유용하게 사용할 수 있는 기능들을 잘 소개해주셔서 기억에 많이 남는 세션이었다.&lt;/li&gt;
&lt;li&gt;특히, 프로젝트를 진행하다보면 개발환경을 한번 세팅하고 나면 최대한 개발 외적인 사이드 이펙트를 줄이고자 사용하고 있는 개발 환경을 그대로 유지하는 경우가 많고 그 과정에서 Android Studio의 업데이트를 뒤로 미루는 경우도 많은데 &lt;b&gt;이번 세션을 통해서 Android Studio 버전을 한 번 업데이트 해봐도 괜찮을 것 같다고 생각했다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Network Inspector interception&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통신을 하는 앱이라면 성능 체크시에 Network Inspector를 사용해본 경험이 있을 것이다. 그때, 보통은 Response 내용과 주고받는 트래픽 등에 대해서 Real-time으로 볼 수 있었을 것이다.&lt;/li&gt;
&lt;li&gt;여기까지는 Network logging interceptor를 보통 사용하므로 별로 흥미롭지 않을수도 있지만, &lt;b&gt;Android Studio Flamingo 버전부터 incoming connection에 대해 custom-rule을 적용할 수 있고 이를 통해 앱에서 실제로 보여지는 응답값을 보여줄 수 있다는 것이 기억에 남는 점&lt;/b&gt;이었다.&lt;/li&gt;
&lt;li&gt;기억에 남았던 이유는, 기존에 네트워크 통신시에 Response 값을 변경하기 위해서는 서버의 도움을 받거나, 직접 테스트 관련 내용들을 만들어서 다시 빌드했어야 했는데, 이를 거치지 않고&lt;b&gt; Rule을 적용하면 실시간으로 Network Response/header 등을 변경할 수 있다는 것이&lt;/b&gt;었다.&lt;/li&gt;
&lt;li&gt;이를 통해 Empty List가 오는 상황과 null이 오는 상황등 처럼 오류를 발생시킬 수 있는 엣지케이스들을 직접 확인하고자 할 떄 유용하게 사용할 수 있을 것으로 생각된다.&lt;/li&gt;
&lt;li&gt;이에 대해 소개한 내용을 따로 검색한 결과, 나중에 사용할 때 다시 알아보도록 해야겠다.&lt;br /&gt;&lt;a href=&quot;https://thatrohit.medium.com/applying-rules-for-network-inspector-in-android-studio-flamingo-144fc8a5a929&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Applying Rules for Network Inspector in Android Studio Flamingo [Link]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&amp;nbsp;Gradle Version Catalogs&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 Dependency를 관리하는 방법에는 여러가지 방식이 있었다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이때 Version Catalog를 통해 관리하게 되면 버전을 깔끔하게 관리할 수 있고, bundle형태로 묶어서 관리할 수도 있다!&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Compose Animation Preview&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에도 Compose를 이용한 Animation에 대한 Preview가 가능했는데, 이번에 이것이 좀 더 개선이 되었다고 한다&lt;/li&gt;
&lt;li&gt;상태를 설정해서 애니메이션 디버깅도 가능하고, 여러 부분에서 개선이 이루어지고 있다고 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Android Studio Bot&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Android Studio내에서 사용할 수 있는 프로그래밍 보조 &lt;b&gt;AI Studio Bot이 발표&lt;/b&gt;되었다고 한다.&lt;/li&gt;
&lt;li&gt;Coding Assistant인 만큼, IDE내에서 도구창으로 통합되어 사용할 수 있는 것을 볼 수 있었는데, 한국어와 영어로 물어봤을 때 발표당시에 보인 자료로는 영어가 좀 더 정확하게 대답해 주는 것 같았다. 특히, 어떤 코드에 대해서 주석을 다는 것을 요구하는 모습을 보여주셨는데, 코딩을 할 때 보조 도구로 잘 사용할 수 있지 않을까? 하는 예시였던 것 같다 (다른 사람의 코드를 읽는 것은 힘든 일이니까...)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;다만, 아직까지는 실험버전이라 부정확한 모습을 많이 보인다고 한다.&lt;/li&gt;
&lt;li&gt;거기다, 미국 국내에서만 이용가능하도록 제한이 걸려있기 때문에, 사용하려면 VPN을 거쳐서 사용해볼 수 있을 것 같다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;그럼에도 IDE에 이러한 것들을 통합시키려는 것만으로도 의미가 있다고 생각했다!&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Dagger Hilt로 의존성 주입하기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세션 소개&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Dagger Hilt를 이용하여 의존성을 주입하는 방법을 알아보고, 실제 경험을 기반으로 권장할 만한 UseCase를 공유합니다.&lt;/blockquote&gt;
&lt;figure id=&quot;og_1690801711503&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Dagger Hilt로 의존성 주입하기&quot; data-og-description=&quot;2023년 7월 29일 (토) I/O Extended 2023 Seoul 행사에서의 발표자료입니다. https://festa.io/events/3683&quot; data-og-host=&quot;speakerdeck.com&quot; data-og-source-url=&quot;https://speakerdeck.com/fornewid/dagger-hiltro-yijonseong-juibhagi&quot; data-og-url=&quot;https://speakerdeck.com/fornewid/dagger-hiltro-yijonseong-juibhagi&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gx1xa/hyTvcyl3mT/hO45DMBmWTOTiPVW9Lymdk/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://speakerdeck.com/fornewid/dagger-hiltro-yijonseong-juibhagi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://speakerdeck.com/fornewid/dagger-hiltro-yijonseong-juibhagi&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gx1xa/hyTvcyl3mT/hO45DMBmWTOTiPVW9Lymdk/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Dagger Hilt로 의존성 주입하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2023년 7월 29일 (토) I/O Extended 2023 Seoul 행사에서의 발표자료입니다. https://festa.io/events/3683&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;speakerdeck.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기억에 남았던 부분&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 Hilt를 많이 사용했었기 때문에 어느정도 익숙한 상태에서 들을 수 있었다!&lt;/li&gt;
&lt;li&gt;발표는 특정 상황에서 Hilt를 활용하는 방법으로 케이스 중심으로 진행되었다.&lt;b&gt; Hilt을 어떻게 하면 활용하는게 잘 활용하는 것인지 다시 생각해보게 되는 계기가 되었다.&lt;/b&gt; 여전히 Hilt는 어려운것 같다&lt;/li&gt;
&lt;li&gt;다행히 발표시간이 짧았던 탓인지 코드부분이 빠르게 넘어가서 아쉬웠는데, 발표자료를 올려주신 곳이 있어서 참고할 수 있을 것 같다!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Flutter에 Clean Architecture을 얹어보자&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세션 소개&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;플러터로 서비스를 런칭하면서 적용했던 아키텍처 구조와 사용한 라이브러리에 대해 발표를 진행하며, 실제 동작가능한 예제와 간단한 유닛테스트를 함께 공유합니다. 해당 예제를 통해 Flutter에서 Clean Architecture의 구현 방법을 실제로 살펴보고, 간단한 유닛 테스트로 앱을 더욱 견고하게 만드는 방법을 다룹니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기억에 남았던 부분&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존에 Flutter 개발을.. 해본적이 없었기 때문에, 걱정은 조금 했지만 다행히 발표자분도 그러한 부분을 어느정도 이해하신건지 Android 네이티브만 개발한 입장에서도 대략적으로 이해갈 수 있게 설명해주셨다고 느꼈다.&lt;/li&gt;
&lt;li&gt;특히 마지막에 &lt;b&gt;Folder Structure&lt;/b&gt;에 대해서 소개해주셨는데, 이부분이 가장 기억에 남았고 Clean Architecture에 대해서 짚어주셔서 이에 대해 다시 한번 되새기는 계기가 되어서 좋았다..&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 일 잘하는 개발자는 회사에서 어떻게 일할까?&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;세션 소개&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&amp;lsquo;일을 잘하는 개발자&amp;rsquo;란 어떤 사람들을 말하는걸까요? 단순히 기술적인 코딩을 잘하는 사람이 회사에서 &amp;lsquo;일을 잘한다' 라고 평가 받을까요? &amp;lsquo;일을 잘하는 개발자'들이 회사에서 어떤 방식으로 일하는지 공유합니다.&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기억에 남았던 부분&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;많은 공감을 하게되는 세션&lt;/b&gt;이였고, 실제로 주변의 다른 사람들도 발표를 들으시면서 많이 공감하셨던것 같다.&lt;/li&gt;
&lt;li&gt;&quot;일 잘하는 개발자는 어떻게 일할까?&quot;에 대해 발표자분이 개인적으로 생각하신 부분에 대해 맨 처음에 목차로 13개를 소개해주셨는데, 하나하나 다 맞는 말이라 생각했다. &lt;s&gt;(실천하기가...어려울 때가 있을 뿐...)&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;특히 1번째로 &lt;b&gt;&quot;암묵지 없애기&quot;&lt;/b&gt;를 말씀하셨었는데, 너무 공감됐던 부분이었다. 동시에 없어질래야 없어지기 어려운 부분이라 어떻게 해결해야 하는지 다시 한 번 되새기는 계기가 되었다. 같은 프로젝트를 진행해도 동상이몽을 꾸는 경우가 너무 많은데 이 부분에 대해서 &lt;b&gt;어떻게 해결해 나가고 있는 지에 대해 사례로 보여주셔서 좀 더 참고하는 계기가 되었고 너무 좋았다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;그외의 &lt;b&gt;&quot;최적의 피쳐 개발방향 찾기&quot;&lt;/b&gt;중 &lt;b&gt;완성도를 5%를 떨어트리더라도 개발시간을 80%이상 단축할 수 있다면 제안하는 것이 중요하다&lt;/b&gt;는 부분에서 기존의 기획을 무조건적으로 지키려고 했던 지난 날들이 스쳐지나갔다... 자식 같은 코드라고 집착하지 않는게 중요한데.. 개발하다보면 자꾸 그 부분을 놓쳐서 아쉬웠던 만큼 다시 한 번 생각하는 계기가 되었다..&lt;/li&gt;
&lt;li&gt;이외에도 &lt;b&gt;회고의 중요성&lt;/b&gt; 등을 이야기 해주셨는데, &lt;b&gt;하나하나 마다 과거의 내 모습이 스쳐지나가면서 공감되면서도 다시 한 번 반성하게 되는 계기가 되었던 세션&lt;/b&gt;이였다..&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 후기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 발표 내용중에 알고있어서 &lt;b&gt;공감되거나, 다시 한 번 생각하는 부분&lt;/b&gt;들도 있고, 미처 인지하지 못해서 &lt;b&gt;놓쳤던 부분&lt;/b&gt;들도 있었는데 &lt;b&gt;그러한 것들을 모두 느낄 수 있는 행사&lt;/b&gt;였어서 참석 여부를 고민했던 행사였는데 가길 잘했다고 생각했다!&lt;/li&gt;
&lt;li&gt;특히, 이번에 발표된 내용들을 보면 내용에는 잘 안드러났지만, &lt;b&gt;Jetpack Compose&lt;/b&gt;과 엮어서 설명하는 부분이 많았는데 확실히 Compose를 처음 알게되었을 때보다 &lt;b&gt;Compose로 대세가 옮겨가고 있는건가? 싶은 생각은 많이 들었던 행사&lt;/b&gt;였다. XML을 레이아웃을 만드는데 주로 사용하고 있지만, &lt;b&gt;XML은 언제까지 갈 수 있을까?&lt;/b&gt; &lt;s&gt;(Legacy가 있는한..?)&lt;/s&gt; &lt;b&gt;Compose에 대해 좀 더 배우고 진행하는 프로젝트에 한 번 사용해봐야겠다&lt;/b&gt;는 생각이 많이 들었다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>ETC/회고록</category>
      <category>2023</category>
      <category>Google I/O Extended Seoul</category>
      <category>review</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/124</guid>
      <comments>https://hodie.tistory.com/124#entry124comment</comments>
      <pubDate>Mon, 31 Jul 2023 20:31:00 +0900</pubDate>
    </item>
    <item>
      <title>[Android/Refactoring] Memory Leak - 3. View's Reference는 Memory Leak의 대상일까?</title>
      <link>https://hodie.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;View's Reference는 Memory Leak의 대상일까?&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이어지는 글&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hodie.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.07.21 - Memory Leak - 1. 안드로이드 앱에서의 메모리 누수 찾기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hodie.tistory.com/119&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.07.25 - Memory Leak - 2. 자주 발생하는 안드로이드 메모리 누수 방지하기&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지난번까지 메모리 누수를 자주 일으키는 요소들에 대해 방지하는 방법에 대해 알아보았다. 그러던 중, View를 참조하는 변수들이 메모리 누수를 일으키는 원인으로 Leakcanary에서 자주 식별되어, View의 요소를 참조하는 것이 메모리 누수를 일으키는 원인이 될 수 있는 지에 대해 알아보고자 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Fragment&lt;/code&gt;에서 &lt;code&gt;myTextView = view.findViewById(R.id.myTextViewId)&lt;/code&gt; 혹은 &lt;code&gt;myTextView = binding.myTextViewId&lt;/code&gt; 처럼 initialize해준 뒤, &lt;code&gt;View&lt;/code&gt;가 &lt;code&gt;Destroy&lt;/code&gt; 되는 경우 해당 &lt;code&gt;variable&lt;/code&gt;에 대해 &lt;code&gt;null&lt;/code&gt; 처리를 해주지 않으면 &lt;code&gt;Leak&lt;/code&gt;이 실제로 발생하는 지에 대한 여부를 알아보고자 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;View&amp;rsquo;s Reference에 대해 Null 처리하는 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AOSP에서의 코드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제로 AOSP(Android Open Source Project)에서의 &lt;a href=&quot;https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/fragment/fragment/src/main/java/androidx/fragment/app/ListFragment.java#158&quot;&gt;ListFragment[Link]&lt;/a&gt; and &lt;a href=&quot;https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/preference/preference/src/main/java/androidx/preference/PreferenceFragment.java#310&quot;&gt;PreferenceFragment[Link]&lt;/a&gt; 의 예시코드를 보면 ListView인 mList에 대해&amp;nbsp;&lt;code&gt;mList = null&lt;/code&gt;를 Destory 하는 경우에 호출해 null 처리 해주는 것을 볼 수 있다.&lt;/li&gt;
&lt;li&gt;그렇다면 실제로 View를 참조하는 것이 메모리 누수를 일으킬 수 있을 확률이 있어 그러는 것일까?&lt;/li&gt;
&lt;li&gt;이와 같은 의문을 나만 가진게 아니였기에, StackOverflow를 통해 이러한 질문을 가진 사람을 어렵지 않게 찾아볼 수 있었고, 이를 참고했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Memory Leak 발생의 가정&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Fragment A&lt;/code&gt;가 &lt;code&gt;Instance field&lt;/code&gt;로, 해당 &lt;code&gt;View&lt;/code&gt;의 &lt;code&gt;childView&lt;/code&gt;에 대한 &lt;code&gt;reference&lt;/code&gt;가 있다고 가정한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fragment A&lt;/code&gt;에서 &lt;code&gt;Fragment B&lt;/code&gt;로의 &lt;code&gt;navigation&lt;/code&gt;은 &lt;code&gt;특정 FragmentTransaction&lt;/code&gt;을 사용한 &lt;code&gt;FragmentManager&lt;/code&gt;를 통해 이루어지게 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Transaction의 Type&lt;/code&gt;에 따라, &lt;code&gt;Manager&lt;/code&gt;는 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;View만 kill&lt;/b&gt;&lt;/span&gt;하고, &lt;code&gt;Fragment A&lt;/code&gt;의 &lt;code&gt;instance&lt;/code&gt;는 유지할 수 있다 (&lt;code&gt;lifecycle&lt;/code&gt; 부분에서 &amp;ldquo;&lt;code&gt;fragment&lt;/code&gt;는 &lt;b&gt;Backstack에서 layout으로 돌아간다&lt;/b&gt;&amp;rdquo;를 참조)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fragment B&lt;/code&gt;에서 &lt;code&gt;Fragment A&lt;/code&gt;로 &lt;b&gt;다시 이동&lt;/b&gt;하는 경우, &lt;code&gt;Fragment A&lt;/code&gt;의 &lt;code&gt;instance&lt;/code&gt;의 &lt;code&gt;previous instance&lt;/code&gt;가 &lt;b&gt;Front로 이동&lt;/b&gt;하지만, &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;새로운 View가 Created&lt;/b&gt;&lt;/span&gt;된다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이때, 문제는 만약 &lt;code&gt;lateinit property&lt;/code&gt;에서 &lt;code&gt;view&lt;/code&gt;를 &lt;code&gt;instance&lt;/code&gt;로 유지하고, &lt;code&gt;reference&lt;/code&gt;를 삭제하지 않으면, &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;view가 완전히 destroyed되지 않아 Memory Leak이 발생&lt;/b&gt;&lt;/span&gt;할수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 위와 같은 단계를 생각해보면, &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;Fragment간 이동 중에 Memory Leak이 발생할 것이라고 예상할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;가정에 대한 증명&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고실험&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;먼저 단계별로, 메모리가 어떻게 할당및 해제되는 지에 대해 단계적으로 살펴보자&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;Fragment A&lt;/code&gt;를 &lt;b&gt;Open&lt;/b&gt;할 때
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 &lt;code&gt;Content&lt;/code&gt;/&lt;code&gt;root&lt;/code&gt; &lt;code&gt;View&lt;/code&gt;와 &lt;code&gt;Textview&lt;/code&gt;가 메모리에 &lt;b&gt;할당&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fragment B&lt;/code&gt;로 &lt;b&gt;Naivgate&lt;/b&gt;할 때
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Fragment A&lt;/code&gt;의 &lt;code&gt;onDestroyView()&lt;/code&gt;가 &lt;b&gt;호출&lt;/b&gt;되지만, &lt;code&gt;Fragment A&lt;/code&gt;의 &lt;code&gt;View&lt;/code&gt;는 &lt;b&gt;destroyed되지 않는다&lt;/b&gt;.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;왜냐하면 &lt;code&gt;TextView&lt;/code&gt;가 &lt;code&gt;Fragment A&lt;/code&gt;에 대한 &lt;code&gt;Strong reference&lt;/code&gt;를 &lt;b&gt;유지&lt;/b&gt;하고 있고, &lt;code&gt;Fragment A&lt;/code&gt;는 &lt;code&gt;TextView&lt;/code&gt;에 대한 &lt;code&gt;Strong Reference&lt;/code&gt;를 &lt;b&gt;유지&lt;/b&gt;하고 있기 때문이다&lt;/li&gt;
&lt;li&gt;(the &lt;code&gt;TextView&lt;/code&gt; holds a strong reference to it and &lt;code&gt;FragmentA&lt;/code&gt; holds a strong reference to &lt;code&gt;TextView&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fragment B&lt;/code&gt;에서 &lt;code&gt;Fragment A&lt;/code&gt;로 &lt;b&gt;Navigate Back&lt;/b&gt; 하는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;View&lt;/code&gt;와 &lt;code&gt;TextView&lt;/code&gt;의 &lt;b&gt;이전 할당(previous allocation)이 cleared&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;또한, &lt;b&gt;동시에&lt;/b&gt; &lt;code&gt;onCreateView()&lt;/code&gt;가 호출될 때 &lt;b&gt;새로운 할당&lt;/b&gt;(new allocation)을 받게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fragment A&lt;/code&gt;에서 &lt;code&gt;Back&lt;/code&gt;을 클릭하는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;새로운 할당이 clear&lt;/b&gt; 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고실험의 결론&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 단계를 살펴 봤을 때 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;2단계&lt;/b&gt;&lt;/span&gt;에서 &lt;code&gt;View&lt;/code&gt;의 &lt;code&gt;retain memory&lt;/code&gt;가 원래대로 확보되지 않아 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;Memory Leak이 발생&lt;/b&gt;&lt;/span&gt;하는 것을 볼 수 있다.&lt;/li&gt;
&lt;li&gt;반면 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;3단계&lt;/b&gt;&lt;/span&gt;부턴 유저가 &lt;code&gt;Fragment&lt;/code&gt;로 돌아오자마자 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;메모리가 복구&lt;/b&gt;&lt;/span&gt;되는 것을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;따라서, &lt;code&gt;FragmentManager&lt;/code&gt;가 &lt;code&gt;Fragment&lt;/code&gt;를 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;다시 가져올 때 까지 Memory Leak이 지속됨&lt;/b&gt;&lt;/span&gt;을 알 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 수행을 통한 증명: Statical Analysis (Heap Memory 이용)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제로 &lt;code&gt;Button&lt;/code&gt;이 있는 &lt;code&gt;Activity&lt;/code&gt;와 &lt;code&gt;Fragments&lt;/code&gt;에 대한 &lt;code&gt;container&lt;/code&gt;인 &lt;code&gt;FrameLayer&lt;/code&gt;가 있다고 하고 이는 다음과 같이 생성한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;code&gt;Button&lt;/code&gt;을 누르면 &lt;code&gt;Container&lt;/code&gt;가 &lt;code&gt;Fragment A&lt;/code&gt;로 바뀐다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fragment A&lt;/code&gt;에는 &lt;code&gt;Container&lt;/code&gt;를 &lt;code&gt;Fragment B&lt;/code&gt;로 replace하는 &lt;code&gt;Button&lt;/code&gt;이 있다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fragment B&lt;/code&gt;는 &lt;code&gt;Instance Field&lt;/code&gt;로 &lt;b&gt;Fragment에 저장&lt;/b&gt;하는 &lt;code&gt;TextView&lt;/code&gt;가 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (9).png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;773&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dx5SoA/btsozTwhyRs/tRGyRX51KBZaYQHwzp4ZlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dx5SoA/btsozTwhyRs/tRGyRX51KBZaYQHwzp4ZlK/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://stackoverflow.com/a/58248851&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dx5SoA/btsozTwhyRs/tRGyRX51KBZaYQHwzp4ZlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdx5SoA%2FbtsozTwhyRs%2FtRGyRX51KBZaYQHwzp4ZlK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Event에 따른 heap dump의 모습과 변화하는 Retained Size&quot; loading=&quot;lazy&quot; width=&quot;1746&quot; height=&quot;773&quot; data-filename=&quot;Untitled (9).png&quot; data-origin-width=&quot;1746&quot; data-origin-height=&quot;773&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://stackoverflow.com/a/58248851&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 Report는 가정을 한 Application을 실행해 Operation을 실행했을 때 관찰되는 Heap Dump와 이외의 정보들이다.&lt;/li&gt;
&lt;li&gt;위의 리포트는 아래의 Operation을 실행하였을 때 나온 결과이다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Opened the app:&lt;/b&gt; Activity가 보인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed the Button in the Activity:&lt;/b&gt; &lt;code&gt;FragmentA&lt;/code&gt; visible&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed the Button in FragmentA:&lt;/b&gt; &lt;code&gt;FragmentB&lt;/code&gt; visible 되고, &lt;code&gt;FragmentA&lt;/code&gt; &lt;code&gt;onDestroyView()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed the Button in the Activity:&lt;/b&gt; &lt;code&gt;FragmentA&lt;/code&gt;의 2번째 instance가 Visible되고, &lt;code&gt;FragmentB&lt;/code&gt; &lt;code&gt;onDstroyView()&lt;/code&gt;&lt;br /&gt;(위에서 &lt;b&gt;4단계로 수행했던 예시의 2번째 단계와 동일&lt;/b&gt;하다. &lt;code&gt;Fragment B&lt;/code&gt;가 &lt;code&gt;A 역할&lt;/code&gt;을 하고, &lt;code&gt;Fragment A&lt;/code&gt;의 &lt;code&gt;2번째 instance&lt;/code&gt;가 &lt;code&gt;B 역할&lt;/code&gt;을 한다는 점을 제외하곤)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed the Button in the 2nd instance FragmentA:&lt;/b&gt; &lt;code&gt;FragmentB&lt;/code&gt;의 2번째 instance가 visible and &lt;code&gt;FragmentA&lt;/code&gt;의 2번째 instance가 &lt;code&gt;onDestroyView()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed Back Button:&lt;/b&gt; &lt;code&gt;FragmentA&lt;/code&gt;의 2번째 instance가 visible and &lt;code&gt;FragmentB&lt;/code&gt;의 2번째 instance가 &lt;code&gt;onDetach()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed Back Button:&lt;/b&gt; &lt;code&gt;FragmentB&lt;/code&gt;의 1번째 instance가 visible and &lt;code&gt;FragmentA&lt;/code&gt;의 2번째 instance가 &lt;code&gt;onDetach()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed Back Button:&lt;/b&gt; &lt;code&gt;FragmentA&lt;/code&gt;의 1번째 instance가 visible and &lt;code&gt;FragmentB&lt;/code&gt;의 1번째 instance가 &lt;code&gt;onDetach()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed Back Button:&lt;/b&gt; &lt;code&gt;FragmentA&lt;/code&gt;의 1번째 instance가 &lt;code&gt;onDetach()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pressed Back Button:&lt;/b&gt; 앱 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Report 분석&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Report를 봤을 때, &lt;code&gt;1단계&lt;/code&gt;에서 &lt;b&gt;앱이 닫힐 때 까지&lt;/b&gt; 각각의 &lt;code&gt;view&lt;/code&gt;와 &lt;code&gt;모든 view&lt;/code&gt;는 앱이 닫힐 때까지 살아있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;2단계&lt;/code&gt;에서는 &lt;code&gt;Fragment A&lt;/code&gt;의 &lt;code&gt;View&lt;/code&gt; (e.g. &lt;code&gt;FrameLayout&lt;/code&gt;과 &lt;code&gt;child&lt;/code&gt;, &lt;code&gt;Button&lt;/code&gt; 등)이 할당되어 있으며, 예상한대로 &lt;code&gt;3단계&lt;/code&gt;에서 &lt;b&gt;모두 clear&lt;/b&gt; 되었다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3단계&lt;/code&gt;에서는, &lt;code&gt;Fragment B&lt;/code&gt;의 &lt;code&gt;View&lt;/code&gt; (e.g. &lt;code&gt;FrameLayout&lt;/code&gt;과 그 &lt;code&gt;child&lt;/code&gt;, &lt;code&gt;TextView&lt;/code&gt; 등)이 할당되어 있지만, &lt;code&gt;4단계&lt;/code&gt;에서는 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;삭제하지 않아 &lt;u&gt;메모리 누수가 발생&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;했다.&lt;/li&gt;
&lt;li&gt;그러나, &lt;code&gt;7단계&lt;/code&gt;에서 &lt;code&gt;View&lt;/code&gt;가 &lt;b&gt;다시 created&lt;/b&gt; 되고 &lt;b&gt;새롭게 create된 view에 할당될 때, clear&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;반면에 &lt;code&gt;5단계&lt;/code&gt;에서 생성된 &lt;code&gt;View&lt;/code&gt;는 &lt;code&gt;6단계&lt;/code&gt;에서 삭제돼, &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;메모리 누수가 발생하지 않았다.&lt;/b&gt;&lt;/span&gt; 이는, &lt;code&gt;fragment&lt;/code&gt;가 &lt;code&gt;detach&lt;/code&gt;되어, &lt;b&gt;fragment가 cleard up하는 것을 prevent하지 않았기 때문&lt;/b&gt;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실제 수행의 결과를 통한 결론&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한 과정을 통해 유저가 &lt;code&gt;fragment&lt;/code&gt;로 돌아갈 때 까지, &lt;code&gt;fragment&lt;/code&gt;에 &lt;code&gt;view&lt;/code&gt;를 저장함으로써 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;leak이 발생한다는 것을 &lt;code&gt;observe&lt;/code&gt;할 수 있었다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Fragment&lt;/code&gt;가 다시 돌아가는 경우 (e.g. &lt;code&gt;onCreatedView()&lt;/code&gt;가 호출되는 등)에 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;leak은 다시 복구&lt;/b&gt;&lt;/span&gt;(recover)된다.&lt;/li&gt;
&lt;li&gt;반면에 &lt;code&gt;fragment&lt;/code&gt;가 &lt;code&gt;top&lt;/code&gt;에 있고 &lt;b&gt;뒤로가기만 가능한 경우&lt;/b&gt;에는 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;어떠한 leak도 일어나지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위와 같이 단계적인 증명과 이를 바탕으로 실제로 앱을 만들어 실행해보았을 때 &lt;code&gt;View&amp;rsquo;s Reference&lt;/code&gt;들은 &lt;code&gt;다른 Fragment&lt;/code&gt;로 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;이동했을 때 Leak을 발생&lt;/b&gt;&lt;/span&gt;시키지만, &lt;code&gt;해당 Fragment&lt;/code&gt;로 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;돌아갔을 때 다시 Recover&lt;/b&gt;&lt;/span&gt;되므로 현재까지 이에 대해 알지 못했더라도 큰 문제가 되지 않았던 것으로 판단된다.&lt;/li&gt;
&lt;li&gt;따라서, &lt;code&gt;View&amp;rsquo;s Reference&lt;/code&gt;의 경우 아래와 같이 유의해서 사용하면 될 듯 하다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;Fragment&lt;/code&gt;에서 &lt;code&gt;Forward transaction&lt;/code&gt;이 없는 경우, &lt;code&gt;View&lt;/code&gt;는 &lt;code&gt;onDetach()&lt;/code&gt;에서 &lt;b&gt;clear&lt;/b&gt;되므로, &lt;code&gt;Strong reference&lt;/code&gt;로 &lt;code&gt;View&lt;/code&gt;를 저장하는 것에 아무런 문제가 없다.&lt;/li&gt;
&lt;li&gt;만약, &lt;code&gt;forward Transaction&lt;/code&gt;이 존재하는 경우, view의 &lt;code&gt;weak Reference&lt;/code&gt;를 저장함으로써, &lt;code&gt;onDestroyView()&lt;/code&gt;에서 clear되도록 할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;혹은 개인적으로 &lt;code&gt;Destroy&lt;/code&gt;하는 경우에 &lt;code&gt;해당 reference&lt;/code&gt;를 &lt;code&gt;null&lt;/code&gt;처리 함으로써 &lt;code&gt;leak&lt;/code&gt;이 발생하지 않도록 할 수 있을 것이라 생각한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/58080268/keeping-a-reference-to-a-view-in-a-fragment-causes-memory-leaks&quot;&gt;Keeping a reference to a View in a Fragment causes memory leaks?&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690091918400&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Keeping a reference to a View in a Fragment causes memory leaks?&quot; data-og-description=&quot;Somebody told me the following, but I am a bit perplexed. Please, would you be able to confirm or dispute it? (the Fragment is not retained via setRetainInstance() At the moment it is a common&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/58080268/keeping-a-reference-to-a-view-in-a-fragment-causes-memory-leaks&quot; data-og-url=&quot;https://stackoverflow.com/questions/58080268/keeping-a-reference-to-a-view-in-a-fragment-causes-memory-leaks&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/qhb39/hyTpf3q0Br/pzW626nIZ29sLwnWqauwok/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316,https://scrap.kakaocdn.net/dn/GDpZ0/hyTo5NgOdw/yFi63j5odT1H9qk8iKggzk/img.jpg?width=1746&amp;amp;height=773&amp;amp;face=0_0_1746_773&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/58080268/keeping-a-reference-to-a-view-in-a-fragment-causes-memory-leaks&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/58080268/keeping-a-reference-to-a-view-in-a-fragment-causes-memory-leaks&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/qhb39/hyTpf3q0Br/pzW626nIZ29sLwnWqauwok/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316,https://scrap.kakaocdn.net/dn/GDpZ0/hyTo5NgOdw/yFi63j5odT1H9qk8iKggzk/img.jpg?width=1746&amp;amp;height=773&amp;amp;face=0_0_1746_773');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Keeping a reference to a View in a Fragment causes memory leaks?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Somebody told me the following, but I am a bit perplexed. Please, would you be able to confirm or dispute it? (the Fragment is not retained via setRetainInstance() At the moment it is a common&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>IT/Android</category>
      <category>Android</category>
      <category>memory leak</category>
      <category>View's Reference</category>
      <category>메모리 누수</category>
      <category>최적화</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/122</guid>
      <comments>https://hodie.tistory.com/122#entry122comment</comments>
      <pubDate>Fri, 28 Jul 2023 07:59:39 +0900</pubDate>
    </item>
    <item>
      <title>[Android/Refactoring] Memory Leak - 2. 자주 발생하는 안드로이드 메모리 누수 방지하기</title>
      <link>https://hodie.tistory.com/119</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Android/Refactoring] Memory Leak - 2. 자주 발생하는 안드로이드 메모리 누수 방지하기&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이어지는 글&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hodie.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.07.21 - [IT/Android] - [Android/Refactoring] Memory Leak - 1. 안드로이드 앱에서의 메모리 누수 찾기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1689930575386&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Android/Refactoring] Memory Leak - 1. 안드로이드 앱에서의 메모리 누수 찾기&quot; data-og-description=&quot;배경 개발을 해놓고 나니 메모리가 이미지를 로딩하는 등 앱을 지속적으로 실행시키면서 메모리가 슬금슬금 증가하는 모습을 안드로이드 프로파일러를 통해서 발견했다. 앱을 사용하면서 지속&quot; data-og-host=&quot;hodie.tistory.com&quot; data-og-source-url=&quot;https://hodie.tistory.com/118&quot; data-og-url=&quot;https://hodie.tistory.com/118&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dmKPgK/hyTpf2m3H8/OEAawwEqSZZa9uV51cxRy1/img.png?width=800&amp;amp;height=193&amp;amp;face=0_0_800_193,https://scrap.kakaocdn.net/dn/s0D8M/hyTo9nycU0/kgNqKJELVD5n9o6GjQb1Hk/img.png?width=800&amp;amp;height=193&amp;amp;face=0_0_800_193,https://scrap.kakaocdn.net/dn/02Wnw/hyTnNzDPsT/PmdpW2GY7jRx0RfBai4REK/img.png?width=871&amp;amp;height=576&amp;amp;face=0_0_871_576&quot;&gt;&lt;a href=&quot;https://hodie.tistory.com/118&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hodie.tistory.com/118&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dmKPgK/hyTpf2m3H8/OEAawwEqSZZa9uV51cxRy1/img.png?width=800&amp;amp;height=193&amp;amp;face=0_0_800_193,https://scrap.kakaocdn.net/dn/s0D8M/hyTo9nycU0/kgNqKJELVD5n9o6GjQb1Hk/img.png?width=800&amp;amp;height=193&amp;amp;face=0_0_800_193,https://scrap.kakaocdn.net/dn/02Wnw/hyTnNzDPsT/PmdpW2GY7jRx0RfBai4REK/img.png?width=871&amp;amp;height=576&amp;amp;face=0_0_871_576');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Android/Refactoring] Memory Leak - 1. 안드로이드 앱에서의 메모리 누수 찾기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;배경 개발을 해놓고 나니 메모리가 이미지를 로딩하는 등 앱을 지속적으로 실행시키면서 메모리가 슬금슬금 증가하는 모습을 안드로이드 프로파일러를 통해서 발견했다. 앱을 사용하면서 지속&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hodie.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Static References&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Activities&lt;/code&gt;, &lt;code&gt;Fragments&lt;/code&gt;, &lt;code&gt;views&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt; 에 대해 &lt;code&gt;static references&lt;/code&gt;를 과도하게 사용하지 않아야 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Static 객체&lt;/code&gt;는 &lt;code&gt;Application&lt;/code&gt;이 실행되는 동안 살아있지만, &lt;code&gt;View&lt;/code&gt;(또는 &lt;code&gt;Context&lt;/code&gt;)에 대한 &lt;code&gt;Static Reference&lt;/code&gt;는 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;제 때 지워지지 않을 확률이 높기 때문&lt;/b&gt;&lt;/span&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Application&lt;/code&gt;이 &lt;code&gt;Class&lt;/code&gt;를 &lt;code&gt;JVM&lt;/code&gt;에 로드하면 해당 &lt;code&gt;static member&lt;/code&gt;들이 &lt;b&gt;메모리 할당되고 증가된 lifespan&lt;/b&gt;에 의해 &lt;code&gt;Class&lt;/code&gt;가 &lt;code&gt;garbage collection&lt;/code&gt;의 대상이 될 때 까지 &lt;b&gt;메모리에 남아있게 된다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://blog.logrocket.com/preventing-detecting-memory-leaks-android-apps/#detecting-memory-leaks-using-android-profiler&quot;&gt;예시&lt;/a&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Static variable&lt;/code&gt;인 &lt;code&gt;TextView&lt;/code&gt;를 선언하고, 다음과 같이 &lt;code&gt;TextView&lt;/code&gt;의 &lt;b&gt;값을 업데이트하는 클래스&lt;/b&gt;가 있고 이를 &lt;code&gt;onCreate() 메소드&lt;/code&gt;에서 &lt;b&gt;실행&lt;/b&gt;한다고 하자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static TextView textView;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private void changeText() {
    textView = (TextView) findViewById(R.id.testview);
    textView.setText(&quot;Update Hello World greetings!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 &lt;code&gt;Static View&lt;/code&gt;는 &lt;code&gt;changeText()&lt;/code&gt; 클래스를 실행하는 동안 &lt;code&gt;Activity&lt;/code&gt;의 일부분이다. 따라서, &lt;code&gt;특정 Activity&lt;/code&gt;에 대한 &lt;code&gt;static reference&lt;/code&gt;가 &lt;b&gt;hold&lt;/b&gt;된다. 해당 &lt;code&gt;Static View&lt;/code&gt;는 &lt;code&gt;Activity&lt;/code&gt;의 &lt;code&gt;Lifecycle&lt;/code&gt; &lt;b&gt;이후에도 실행&lt;/b&gt;되게 된다.&lt;/li&gt;
&lt;li&gt;따라서, 이경우 여전히 &lt;code&gt;Activity&lt;/code&gt;에 대한 참조를 &lt;code&gt;view&lt;/code&gt;가 가지고 있기 때문에 &lt;code&gt;Activity&lt;/code&gt;가 &lt;code&gt;garbage collected&lt;/code&gt;되지 않는다. &lt;b&gt;이는 곧&lt;/b&gt; &lt;code&gt;메모리 누수&lt;/code&gt;&lt;b&gt;가 발생함을 의미&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Static&lt;/code&gt;은 주어진 클래스에서 동일한 &lt;code&gt;variable&lt;/code&gt;를 &lt;b&gt;모든 객체에서 사용하기 위해 사용&lt;/b&gt;된다. 따라서, &lt;code&gt;View&lt;/code&gt;가 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;statically 유지 되어야만 하는 경우&lt;/b&gt;&lt;/span&gt;에는 onDestroy()에서 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;해당 reference를 null 처리&lt;/b&gt;&lt;/span&gt;하면 메모리 누수를 막을 수 있다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이 경우, &lt;code&gt;activity&lt;/code&gt;가 &lt;code&gt;destroy&lt;/code&gt;되면, &lt;code&gt;static reference&lt;/code&gt;도 &lt;code&gt;destroyed&lt;/code&gt;되기 때문에, &lt;code&gt;activity&lt;/code&gt;가 garbage collected될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;aspectj&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Override
protected void onDestroy() {
  super.onDestroy();
  textView = null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위처럼 &lt;code&gt;Destroy&lt;/code&gt;할 때 &lt;code&gt;null 처리&lt;/code&gt;하여 메모리 누수를 막을 수도 있지만 가장 좋은 것은, &lt;b&gt;가능한 static keyword를 사용하지 않고 선언하는 것&lt;/b&gt;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://blog.logrocket.com/preventing-detecting-memory-leaks-android-apps/#detecting-memory-leaks-using-android-profiler&quot;&gt;예시 2 (Static Context)&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;private static Context mContext;
...
.. onCreate() {
    mContext = this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위처럼 &lt;code&gt;onCreate()&lt;/code&gt; 메소드 안에서 &lt;code&gt;Activity Context&lt;/code&gt;를 &lt;code&gt;Static reference&lt;/code&gt;하려는 경우 &lt;code&gt;acitivty&lt;/code&gt;에 대한 &lt;code&gt;leak&lt;/code&gt;을 발생시킬 수 있으며, 안드로이드 스튜디오도 이를 &lt;b&gt;경고&lt;/b&gt;할 것이다.&lt;/li&gt;
&lt;li&gt;그러나, &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;code&gt;static field&lt;/code&gt;에 &lt;b&gt;두어야만 한다면&lt;/b&gt;&lt;/span&gt;, &lt;code&gt;virtual&lt;/code&gt;/&lt;code&gt;weak reference&lt;/code&gt;를 사용해 &lt;code&gt;hold&lt;/code&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;actionscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static WeakReference&amp;lt;Context&amp;gt; mContext;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 &lt;code&gt;onCreate()&lt;/code&gt; 메소드 내부에서&lt;br /&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;mContext = new WeakReference&amp;lt;&amp;gt; (this);&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;를 사용할 수 있고, &lt;code&gt;onDestroy()&lt;/code&gt; 메소드 내부에서 &lt;code&gt;null 처리&lt;/code&gt;해 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code class=&quot;language-java&quot;&gt;  mContext = new WeakReference&amp;lt;&amp;gt; (this);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Binding leaks&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewBinding&lt;/code&gt;을 사용하는 경우에 &lt;code&gt;onDestroyView()&lt;/code&gt; lifecycle 메소드에서 &lt;code&gt;binding = null&lt;/code&gt; 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Context&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Context&lt;/code&gt;는 &lt;code&gt;Application&lt;/code&gt;에서 다른 components간에 communicate가 가능하도록 allow해준다.&lt;/li&gt;
&lt;li&gt;이를 통해, 새로운 객체를 만들거나, 리소스(layout, image, string 등)에 접근할 수 있도록 해주며, activity, database와 기기의 내부 저장소를 launch할 수 있도록 해준다.&lt;/li&gt;
&lt;li&gt;따라서 &lt;code&gt;Context&lt;/code&gt;에 Access할 수 있는 방법에도 여러 방법이 있는데, &lt;code&gt;this&lt;/code&gt; 와 &lt;code&gt;getApplicationContext&lt;/code&gt;가 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Context&lt;/code&gt;는 다른 &lt;code&gt;component&lt;/code&gt;에 대한 참조를 유지한다. 따라서, &lt;code&gt;context&lt;/code&gt;를 사용하는 방법은 &lt;code&gt;application&lt;/code&gt;에서 중요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://blog.logrocket.com/preventing-detecting-memory-leaks-android-apps/#detecting-memory-leaks-using-android-profiler&quot;&gt;예시&lt;/a&gt;&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class SingletonClass {

    private static SingletonClass singletonClassInstance;

    private Context context;

    private SingletonClass(Context context){
        this.context = context;
    }

    public static void singletonClassInstance(Context context){

        if (singletonClassInstance == null){
            singletonClassInstance = new SingletonClass(context);

        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 예시에서, &lt;code&gt;SingletonClass.singletonClassInstance(this)&lt;/code&gt;를 사용해 &lt;code&gt;MainActivity&lt;/code&gt;의 &lt;code&gt;SingletonClass&lt;/code&gt; 클래스에 접근하게 된다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이때, SingletonClass 데이터를 가져오기 위해, parameter this를 사용해 해당 클래스의 context를 얻게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 경우, context는 Java 클래스이다. 이는, Application components나 다른 OS feature에 대한 정보를 얻기 위한 방법을 제공한다.&lt;/li&gt;
&lt;li&gt;그러나, 이처럼 &lt;code&gt;this&lt;/code&gt; context를 사용해 MainActivity에서 SingletonClass를 exceuting하면 activity가 누수(leak)될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context의 사용법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context는 전체 Application의 lifecycle에 묶여있다. 따라서, Context를 잘못 사용하는 경우 메모리 누수로 이어질 수 있는 것이다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;따라서, 다른 Context를 사용하는 경우 위치와 사용시기를 확인해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예를 들어, &lt;code&gt;getApplicationContext&lt;/code&gt;는 객체가 Activity Lifecycle을 넘어서까지 살아있어야 하는 경우 사용할 수 있다. 그러나, UI와 관련된 component들에선 사용할 수 없다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;code&gt;Singleton&lt;/code&gt;이 있는 경우, 항상 ApplicationContext를 사용해야 한다는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;또한 this는 객체가 activity lifecycle의 과거에 살아 있지 않은 경우에만 사용할 수 있다. 다만, UI component들을 참조하는데는 사용 할 수 있다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;UI component들은 장시간 실행되는 작업이 아니고, activity lifecycle을 초과해 살아있을 수 없다.&lt;/li&gt;
&lt;li&gt;this Context는 XML 레이아웃이나, dialogue, getting resources, starting activity를 하는 여러 operation에 사용될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시 2 (ApplicationContext - Singleton)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SingletonClass를 사용하는 경우, &lt;b&gt;Context-implementing 객체는 1개만&lt;/b&gt; 있을 수 있으므로, &lt;code&gt;getApplicationContext&lt;/code&gt;를 사용하는 것이 적절하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getApplicationContext&lt;/code&gt;는 &lt;code&gt;singleton Context&lt;/code&gt;이다. 이는, 얼마나 많이 context에 접근하는 지 중요하게 생각하지 않으며 항상 같은 instance를 얻게 된다.&lt;/li&gt;
&lt;li&gt;따라서 해당 instance는 새로운 context를 만들지 않는다. 따라서 SingletonClass를 사용하는 경우 아래와 같이 사용하면 memory leak을 방지할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;SingletonClass.singletonClassInstance(getApplicationContext());&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Fragment Lifecycle vs ViewLifecycleOwner Lifecycle&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Fragment에서 LiveData를 observing하는 경우, &lt;code&gt;this&lt;/code&gt; 대신 &lt;code&gt;viewLifecycleOwner&lt;/code&gt; 전달&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;viewLifecycleOwner은 UI에 있는 한(&lt;code&gt;onCreateVew()&lt;/code&gt; ~ &lt;code&gt;onDestroyView()&lt;/code&gt;), fragment에 묶여있는데(tied to fragment UI) 반해&lt;/li&gt;
&lt;li&gt;반면에 &lt;code&gt;this&lt;/code&gt;는 fragment의 전체 lifecycle (&lt;code&gt;onCreate()&lt;/code&gt; ~ &lt;code&gt;onDestroy()&lt;/code&gt;)에 묶여있기 때문에 아래의 그림처럼 viewLifecycleOwner보다 this를 사용하는 경우 더 오랜 시간 observe하게 된다.&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOrIpX/btsowsTjA4o/7y2goHM62Z2VHHRIZhq4wk/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/bOrIpX/btsowsTjA4o/7y2goHM62Z2VHHRIZhq4wk/img.png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;602&quot; data-filename=&quot;Untitled (6).png&quot; /&gt;출처: https://write.agrevolution.in/memory-leaks-in-android-apps-45a27c6ac35d&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이것은 곧 &lt;code&gt;this&lt;/code&gt;를 &lt;code&gt;lifecycle owner&lt;/code&gt;로 전달(pass)하는 경우 메모리 누수를 일으키는 원인이 될 수 있음을 의미한다.&lt;/li&gt;
&lt;li&gt;예를 들어, &lt;code&gt;view&lt;/code&gt;가 &lt;code&gt;destroyed&lt;/code&gt;되었으나, (&lt;code&gt;onDestroyView()&lt;/code&gt; is called), &lt;code&gt;fragment&lt;/code&gt;는 &lt;code&gt;destroy&lt;/code&gt;되지 않은 경우 (&lt;code&gt;onDestroy()&lt;/code&gt; is not called), &lt;code&gt;LiveData 객체&lt;/code&gt;의 모든 변경사항이 여전히 observed되기 때문에 Crush가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FragmentStateAdapter Constructor에서 lifecycle 객체 대신 viewLifecycleOwner.lifecycle 전달&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위와 비슷한 이유로, &lt;code&gt;FragmentStateAdapter&lt;/code&gt; &lt;code&gt;Constructor&lt;/code&gt;에서 &lt;code&gt;Adapter&lt;/code&gt;의 범위를 &lt;code&gt;Fragment&lt;/code&gt;가 아닌, &lt;code&gt;Fragment의 View&lt;/code&gt;의 lifecycle로 Scope를 지정해야 하기 때문에 &lt;code&gt;viewLifecycleOwner의 Lifecycle&lt;/code&gt;을 전달해야 한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;code&gt;FragmentPagerStateAdapter(fragment)&lt;/code&gt; 혹은 &lt;code&gt;FragmentPagerStateAdapter(fragmentActivity)&lt;/code&gt;에서 &lt;code&gt;FragmentPagerStateAdapter(fragmentManger, lifecycle)&lt;/code&gt;로 변경한다&lt;/li&gt;
&lt;li&gt;1번째와 2번째는 &lt;code&gt;fragmentActivity.getLifecycle()&lt;/code&gt;과 &lt;code&gt;fragment.getLifecycle()&lt;/code&gt;를 이용해 &lt;code&gt;LifecycleEvent&lt;/code&gt;를 관찰한다. 따라서, previous memory leak이 일어나게 된다.&lt;/li&gt;
&lt;li&gt;3번째를 사용해 &lt;code&gt;fragment의 viewLifecycleOwner&lt;/code&gt;를 2번째 인자로 넣어줘 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;위의 식으로 인해, &lt;code&gt;View&lt;/code&gt;가 destryoed되고 나서, 다른 fragment로 이동한 후, &lt;code&gt;onDetach&lt;/code&gt;를 calling할 때 까지 response를 받는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;  viewModel.orders.collectLatest {
  adapter?.submitData(it)
  }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;이때 위와 같은 식이 있다면, fragment의 view가 destroy됐음에도 불구하고, recyclerview에 액세스하려고할 것이며 이는, adapter에 대한 참조를 가지고 있으면서 memory leak을 일으키는 원인이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Threaded Code&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Threaded code&lt;/code&gt;는 메모리 누수를 일으킬 가능성이 매우 높다. Thread들은 실행 logic을 여러개의 동시 tasks로 decompose한다.&lt;/li&gt;
&lt;li&gt;안드로이드는 스레드를 사용해, 동시에 실행되는 여러 task들을 처리한다. 스레드에는 고유한 실행 환경이 없기 때문에, 부모 task로부터 실행 환경을 상속한다. 따라서 스레드는 단일 프로세스 범위 내에서 서로 데이터를 쉽게 통신하고 교환할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://blog.logrocket.com/preventing-detecting-memory-leaks-android-apps/#detecting-memory-leaks-using-android-profiler&quot;&gt;예시&lt;/a&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;Thread Task를 초기화하고, threaded task를 set up한 뒤, onCreate() 메소드에서 task를 실행한다&lt;/ul&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private final ThreadedTask thread = new ThreadedTask();&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  private class ThreadedTask extends Thread {
      @Override
      public void run() {
          // Run the ThreadedTask for some time
          SystemClock.sleep(1000 * 20);
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;  thread.start();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Thread Task가 시작되면 실행이 완료될 때까지 시간이 걸리게 된다.&lt;/li&gt;
&lt;li&gt;만약, task 실행이 끝나기 이전에 activity를 닫게되면, 실행되고 있는 &lt;code&gt;ThreadedTask&lt;/code&gt;는 Activity가 garbage collected되는 것을 막을 것이다. Background에서 발생하는 view, activity또는 context에 대한 참조를 가지고 있는 것은 메모리 누수를 발생할 확률이 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이러한 누수를 해결하기 위해 &lt;code&gt;static class&lt;/code&gt;를 사용할 수 있다. &lt;code&gt;static class&lt;/code&gt;는 static class를 둘러싼 &lt;code&gt;activity class&lt;/code&gt;에 대한 참조를 가지고 있지 않기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // make ThreadedTask static to remove reference to the containing activity
  private static class ThreadedTask extends Thread {
      @Override
      public void run() {
          // check if the thread is interrupted
          while (!isInterrupted()) {
              // Run the ThreadedTask for some time
              SystemClock.sleep(1000 * 20);
          }
      }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;또는 Activity가 destroy되는 &lt;code&gt;onDestroy()&lt;/code&gt;에서 해당 스레드를 중단시킬수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  // If the activity is destroyed, isInterrupted() will return true, and the thread will be stopped:

  @Override
  protected void onDestroy() {
      super.onDestroy();
      //kill the thread in activity onDestroy
      thread.interrupt();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Handler Threads&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Handler&lt;/code&gt;는 &lt;code&gt;Java background Thread&lt;/code&gt;이다. 이것은 background에서 계속 실행되며, &lt;code&gt;Appplication&lt;/code&gt;이 스레드 실행을 종료할 때 까지 순차적으로 다른 task들을 실행한다.&lt;/li&gt;
&lt;li&gt;Handler는 주로 Application UI와 통신하고, 실행 스레드를 기반으로 다른 component들을 업데이트 한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;가장 대표적인 handler application 예시가 progress bar&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Handler&lt;/code&gt;는 메시지큐를 만드는데 &lt;code&gt;loopers&lt;/code&gt;를 사용하기에, 이를 이용해 메시지를 예약하고, 다른 반복 tasks를 기반으로 UI를 업데이트 할 수 있다.&lt;/li&gt;
&lt;li&gt;다만, Handler는 스레드이며, 여러번 반복해 실행되기 때문에, 어떻게 사용하느냐에 따라 메모리 누수가 발생할 가능성이 존재한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;a href=&quot;https://blog.logrocket.com/preventing-detecting-memory-leaks-android-apps/#detecting-memory-leaks-using-android-profiler&quot;&gt;예시&lt;/a&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;handler task를 선언하고, &lt;code&gt;onCreate()&lt;/code&gt; 메소드에서 task를 실행한다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;handler.postDelayed(new Runnable() {
   @Override
   public void run() {
      textView.setText(&quot;Handler execution done&quot;);
   }
   // delay its execution.
 }, 1000 * 10);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private final Handler handler = new Handler(Looper.getMainLooper());&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해당 handler가 실행되었을 때, 해당 &lt;code&gt;handler&lt;/code&gt;는 &lt;code&gt;activity&lt;/code&gt;에 callback을 register한다. 이것은 &lt;code&gt;activity&lt;/code&gt;가 &lt;code&gt;garbage collecting&lt;/code&gt;되는 것을 막고, 이는 곧 메모리 누수를 일으키게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
protected void onDestroy() {
   super.onDestroy();
   //remove the handler references and callbacks.
   handler.removeCallbacksAndMessages(null);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서, 메모리 누수를 해결하기 위해, 모든 callback들을 삭제해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드는 단일 프로세스 범위 내에서 서로 데이터를 통신하고 교환하기 때문에, &lt;code&gt;onDestroy()&lt;/code&gt; 메소드가 호출될 때 관련 callback을 제거해야 한다.&lt;/li&gt;
&lt;li&gt;이렇게 하면 &lt;code&gt;Handler references&lt;/code&gt;가 제거되고, 메모리 누수가 해결된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Application에서 스레드가 누수 될수 있는 가능성은 많다. 따라서 Threaded 실행이 잘 되도록 하려면, 스레드가 생성된 시점부터 제거된 시점까지 Thread Lifecycle이 완전히 실행될 수 있도록 해야 한다.&lt;/li&gt;
&lt;li&gt;또한, inner class에서 outer(parent) class에 대한 모든 implicit references를 관찰해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Viewpager&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewPager&lt;/code&gt;를 사용하는 경우 관련된 누수를 방지하기 위해 &lt;code&gt;onDestroyView()&lt;/code&gt; 메소드 에서 다음과 같이 처리해주어야 한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;viewPager.adapter = null&lt;/li&gt;
&lt;li&gt;tabLayout이 존재하는 경우 tabs에 적용한 listener들 remove 처리&lt;/li&gt;
&lt;li&gt;TabLayoutMediator Detach&lt;/li&gt;
&lt;/ol&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override fun onDestroyView() {
	super.onDestroyView()
	binding.apply {
		viewPager.adapter = null
		tabLayout.removeOnTabSelectedListener(tabSelectedListener)
	}
    _binding = null
    tabLayoutMediator?.detach()
    tabLayoutMediator = null
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RecyclerView&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;를 사용하는 경우 &lt;code&gt;onDestroyView()&lt;/code&gt; Lifecycle 메소드에서 RecyclerView 객체에 대한 참조를 holding하는 것을 방지하기 위해 &lt;code&gt;RecyclerView.adapter = null&lt;/code&gt; 처리를 해줘야 한다.&lt;/li&gt;
&lt;li&gt;RecyclerView가 onCreateView() 에서 만들어 진뒤 해당 RecyclerView는, RecyclerViewAdapter에 대한 reference를 hold하고 있게 된다. 만약 fragment가 adapter의 reference를 hold하게 된다면, 이것은 Adapter를 통해 직접 및 간접적으로 RecyclerView를 hold하게 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RecyclerView is created inside &lt;code&gt;onCreateView()&lt;/code&gt;, and it holds a reference to RecyclerViewAdapter. If the fragment also holds the reference of the adapter, it holds RecyclerView both directly and indirectly through adapter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;onDestyoView()가 호출됐을 때, fragment는 RecyclerView에 대한 직접적인 reference는 해제할 수 있지만, 간접적인(indircet) reference는 해제할 수 없기 때문에, RecyclerView가 leak되게 된다.&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBpVAW/btsovf1XItC/DGy2N9bfyn1kKim2ZSRc0k/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/bBpVAW/btsovf1XItC/DGy2N9bfyn1kKim2ZSRc0k/img.png&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;281&quot; data-filename=&quot;Untitled (7).png&quot; /&gt;&lt;/li&gt;
&lt;li&gt;만약 Fragment가 Adapter의 reference를 hold해야 하는 경우, reference를 null로 set해줘야 하고 이것이 onDestroyView에서 null로 set해주는 것이다.&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFnpM8/btsox9SKcDA/RDZVGfRk6jCHgM1zdoaRD0/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/bFnpM8/btsox9SKcDA/RDZVGfRk6jCHgM1zdoaRD0/img.png&quot; data-origin-width=&quot;249&quot; data-origin-height=&quot;281&quot; data-filename=&quot;Untitled (8).png&quot; /&gt;출처: https://weidianhuang.medium.com/how-to-prevent-common-memory-leaks-inside-android-fragment-c243ed7074d6
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;null로 Set 해줌에 따라, reference를 break 해줄수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이를 통해서, RecyclerView &amp;rarr; Adapter로의 reference를 break해주는 것을 이해할 수 있는 있지만, Adapter &amp;rarr; RecyclerView의 reference를 break하는 것에 대해선 부족한 설명일 수 있다. 이는 Android &lt;a href=&quot;http://androidxref.com/9.0.0_r3/xref/frameworks/support/v7/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java&quot;&gt;RecyclerView source code&lt;/a&gt;를 통해 확인할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;setAdapter()&lt;/code&gt;코드를 확인해보면 다음과 같다&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void setAdapter(@Nullable Adapter adapter) {
	// bail out if layout is frozen
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    processDataSetCompletelyChanged(false);
    requestLayout();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 &lt;code&gt;setAdapterInternal&lt;/code&gt;를 호출하는 것을 알 수 있다.
&lt;pre id=&quot;code_1689929412986&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private void setAdapterInternal(@Nullable Adapter adapter, 
	boolean compatibleWithPrevious,
	boolean removeAndRecycleViews
) {
	if (mAdapter != null) {
    mAdapter.unregisterAdapterDataObserver(mObserver);
    mAdapter.onDetachedFromRecyclerView(this);
    }
    ......
    final Adapter oldAdapter = mAdapter;
    mAdapter = adapter;
    ......
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;해당 메소드에서 mAdapter는 previous reference이고, new reference가 업데이트 되기 이전에, 이것은 unregisterAdapterDataObserver를 호출해 adapter 내부의 observable한 항목을 제거하도록 한다.&lt;br /&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;&amp;rarr; 이를 통해 Adapter에서 Adapter &amp;rarr; recyclerView의 reference가 break됨을 알 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Destroy하는 경우에 null 처리하는 것이 싫다면, RecyclerViewAdapter의 reference를 Fragment에서 유지하지 않고, 항상 recyclerView.adapter를 이용해 RecyclerViewAdapter에 acces 하는 것도 하나의 방법이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SwipeRefreshLayout&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SwipeRefreshLayout의 경우 라이브러리 자체적으로 Leak을 가지고 있는 것이 Google Issue Tracker에서도 이미 보고되었다. 문제는 여전히 고쳐지지 않고 있다는 보고가 지속적으로 올라오고 있기 때문에 이에 대한 대비를 해둘 필요가 있다고 판단했다.&lt;/li&gt;
&lt;li&gt;이를 위해 onResume ~ onPause 동안만 사용할 수 있도록 코드 내부에서 자체적으로 enable를 true/false 처리 해줄필요가 있다.&lt;br /&gt;In&amp;nbsp;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;code&gt;OnResume&lt;/code&gt;&lt;/span&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;  swipeRefresh.isEnabled = false
&lt;/code&gt;&lt;/pre&gt;
In &lt;code&gt;onPause&lt;/code&gt;
&lt;pre id=&quot;code_1689930080950&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;swipeRefresh.isEnabled = true&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기타&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 listeners, broadcast receiver 등에 대해 적절한 lifecycle 메소드에서 리소스 누수를 방지하기 위해 unregister 처리를 해줘야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어, &lt;code&gt;onViewCreated()&lt;/code&gt;에서 receiver를 설정한 경우, &lt;code&gt;onViewDestroyed()&lt;/code&gt;에서 unregister처리를 해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Android</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/119</guid>
      <comments>https://hodie.tistory.com/119#entry119comment</comments>
      <pubDate>Tue, 25 Jul 2023 07:08:32 +0900</pubDate>
    </item>
    <item>
      <title>[Android/Refactoring] Memory Leak - 1. 안드로이드 앱에서의 메모리 누수 찾기</title>
      <link>https://hodie.tistory.com/118</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발을 해놓고 나니 메모리가 이미지를 로딩하는 등 앱을 지속적으로 실행시키면서 메모리가 슬금슬금 증가하는 모습을 &lt;code&gt;안드로이드 프로파일러&lt;/code&gt;를 통해서 발견했다.&lt;/li&gt;
&lt;li&gt;앱을 사용하면서 지속적으로 새로운 것들을 사용하기 때문에 메모리가 증가하는 것은 어떻게 보면 당연하지만, 문제는 사용하지 않는 것들에 대해선 적절하게 &lt;code&gt;GC&lt;/code&gt;가 수거해주어야 하고, 그에 따라 메모리가 줄어들고 난 뒤에 다시 늘어나는 경향을 보여야 하는데 &lt;b&gt;메모리 사용량이 지속적으로 늘어나기만 하는 모습&lt;/b&gt;을 보였다.&lt;/li&gt;
&lt;li&gt;이는 어디에선가 사용하지 않는 객체를 해제하지 않아 메모리를 지속적으로 차지하고 있는, &lt;b&gt;&lt;code&gt;메모리 누수&lt;/code&gt;&lt;/b&gt;가 일어나고 있는 것으로 판단하였고 이를 개선하기 위해 메모리 누수에 대해 알아보고 개선하고자 하였다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이러한 판단의 배경에는 저사양 기기로 테스트하는 경우에 &lt;code&gt;OOM 에러&lt;/code&gt;로 앱이 종료되는 현상이 있었고 해당 현상은 &lt;b&gt;객체의 참조로 인해 화면 종료시에도 GC가 제대로 되지 않으면서 계속적으로 Activity 객체들이 쌓이고 있다고 판단했기 때문&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Activity&lt;/code&gt;의 &lt;code&gt;멤버 변수&lt;/code&gt;가 &lt;code&gt;다른 Class&lt;/code&gt;에 참조되어 메모리 해제가 되지 않으면, 그 &lt;code&gt;Activity&lt;/code&gt; 까지도 &lt;b&gt;메모리가 완전히 해제가 되지 않아서&lt;/b&gt;, 사용하면 할수록 메모리 사용량의 증가가 비대해지는 영향을 끼친다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Memory Leak이란?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (1).png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHjZtE/btsonH3Vsea/1cAM1pMKql6Kle5gvpRvf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHjZtE/btsonH3Vsea/1cAM1pMKql6Kle5gvpRvf0/img.png&quot; data-alt=&quot;출처: https://write.agrevolution.in/memory-leaks-in-android-apps-45a27c6ac35d&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHjZtE/btsonH3Vsea/1cAM1pMKql6Kle5gvpRvf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHjZtE%2FbtsonH3Vsea%2F1cAM1pMKql6Kle5gvpRvf0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;객체들이 다른 객체를 참조하는 것을 나타내는 이미지&quot; loading=&quot;lazy&quot; width=&quot;1400&quot; height=&quot;339&quot; data-filename=&quot;Untitled (1).png&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://write.agrevolution.in/memory-leaks-in-android-apps-45a27c6ac35d&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 누수&lt;/b&gt;는, &lt;code&gt;Garbage Collector&lt;/code&gt;가 &lt;b&gt;객체에 메모리를 할당&lt;/b&gt;하지만(&lt;code&gt;allocate&lt;/code&gt;), 다시 &lt;b&gt;해제(&lt;/b&gt;&lt;code&gt;deallocate&lt;/code&gt;&lt;b&gt;)하지 않는 경우에 발생&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;즉, &lt;code&gt;Garbage Collector&lt;/code&gt; 객체 할당해제에 실패하여 &lt;code&gt;Application&lt;/code&gt;에서 해당 객체를 더 이상 필요로 하지 않는 경우에도 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;객체를 계속해서 사용함에 따라 비효율적인 메모리 사용을 초래&lt;/b&gt;&lt;/span&gt;하는 것을 &lt;span style=&quot;background-color: #ee2323; color: #ffffff;&quot;&gt;&lt;u&gt;&lt;b&gt;&amp;nbsp;메모리 누수 &lt;/b&gt;&lt;/u&gt;&lt;/span&gt;라고 한다.&lt;/li&gt;
&lt;li&gt;이는 &lt;code&gt;Garbage Collector&lt;/code&gt;가 &lt;b&gt;해당 객체는 여전히 필요한 객체라고 생각하기 때문&lt;/b&gt;인데, 이는 다른 객체로 부터 &lt;b&gt;참조되고 있다고 판단하기 때문에 필요한 객체라고 판단하는 것&lt;/b&gt;이다. 그러나, 해당 참조는 Cleared되었어야 한다는 점에서 &lt;b&gt;정상적인 판단이라 보기 어렵다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;만약, 지속적으로 이러한 일이 일어난다면 &lt;code&gt;Heap Memory&lt;/code&gt;를 &lt;b&gt;지속적으로 사용&lt;/b&gt;할 것이고 기기는 한정된 메모리를 가지고 있고, &lt;b&gt;앱에 할당할 수 있는 메모리가 제한&lt;/b&gt;되어 있기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;더이상 메모리를 할당하지 못해, 결국엔 앱이 Crash&lt;/b&gt;&lt;/span&gt; 날 것이다.&lt;/li&gt;
&lt;li&gt;즉, 메모리 누수는 &lt;code&gt;garbage collecting&lt;/code&gt; 될 대상에 &lt;b&gt;참조가 포함된 객체가 있을 때 발생&lt;/b&gt;한다. 해당 &lt;code&gt;객체의 Instance&lt;/code&gt;가 &lt;b&gt;점점 더 많이 생성&lt;/b&gt;됨에 따라, &lt;code&gt;이전 Instance&lt;/code&gt;는 &lt;b&gt;여전히 응용 프로그램의 메모리에 남아있게 된다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;메모리에 &lt;b&gt;오래 머물게&lt;/b&gt; 되면, 결국 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Application에 할당된 메모리가 모두 소모&lt;/b&gt;&lt;/span&gt;되고 유저에 &lt;b&gt;Application의 poor memory performance를 알리고 앱이 crash&lt;/b&gt;가 나게 되는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Memory Leak을 탐지하는 방법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안드로이드 스튜디오에선 기본적으로 &lt;code&gt;Profiler&lt;/code&gt;를 통해 &lt;b&gt;리소스의 사용량과 누수를 확인&lt;/b&gt;할 수 있고, &lt;b&gt;Memory탭 에선 Leaks이 발생했음을 표시해주는 기능&lt;/b&gt;이 있기 떄문에 이를 통해 &lt;b&gt;메모리 누수를 확인&lt;/b&gt;할 수 있다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;시스템의 메모리에 앱이 어떤 영향을 주고 있는 지에 더 자세히 알아볼 수 있다는 장점이 있지만&amp;hellip; &lt;b&gt;추적하기 쉽지않다는 것이 가장 큰 단점&lt;/b&gt;이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이외에 안드로이드에서 좀 더 &lt;b&gt;편리하게 메모리 누수를 탐지&lt;/b&gt;하기 위한 방법으로 &lt;code&gt;LeakCanary&lt;/code&gt;를 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Memory Heap 파일&lt;/code&gt;을 &lt;code&gt;Dump&lt;/code&gt;뜬 뒤 &lt;code&gt;MAT&lt;/code&gt; 를 이용해 하나하나 인스턴스 개수를 찾아가며 하는 방법이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Android Studio Profiler&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (2).png&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dRiKIn/btsol8BAggS/g4pzbVpAAGKZWflRgEvTp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dRiKIn/btsol8BAggS/g4pzbVpAAGKZWflRgEvTp0/img.png&quot; data-alt=&quot;Instance View에서 할당된 object의 detail 확인 가능 / 출처:&amp;amp;amp;nbsp; https://developer.android.com/studio/profile/memory-profiler&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dRiKIn/btsol8BAggS/g4pzbVpAAGKZWflRgEvTp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdRiKIn%2Fbtsol8BAggS%2Fg4pzbVpAAGKZWflRgEvTp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Android Profiler 실행 에시&quot; loading=&quot;lazy&quot; width=&quot;871&quot; height=&quot;576&quot; data-filename=&quot;Untitled (2).png&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Instance View에서 할당된 object의 detail 확인 가능 / 출처:&amp;amp;nbsp; https://developer.android.com/studio/profile/memory-profiler&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;탐지 방법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;앱 빌드후 앱이 실행되고 있는 상태에서, &lt;code&gt;Android Studio IDE&lt;/code&gt; 하단에 보이는 &lt;code&gt;Profiler 탭&lt;/code&gt;을 열어 &lt;code&gt;Profiler&lt;/code&gt;를 연다&lt;/li&gt;
&lt;li&gt;해당 탭에서 &lt;b&gt;+&lt;/b&gt; 버튼을 눌러 &lt;b&gt;새로운 세션을 추가&lt;/b&gt;한다. 이 때, 앱이 실행되고 있는 &lt;b&gt;기기를 선택&lt;/b&gt;한 뒤, 생성한 &lt;b&gt;Application Package를 선택&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;해당 탭을 열어 실행되고 있는 &lt;b&gt;앱을 디버깅하기 시작&lt;/b&gt;하면, &lt;b&gt;Memory 부분에서 현재 메모리가 어느정도 사용되고 있는지 실시간으로&lt;/b&gt; 보이게 된다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;code&gt;Heap&lt;/code&gt;의 종류
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;default heap&lt;/code&gt; When no heap is specified by the system.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;image heap&lt;/code&gt; The system boot image, containing classes that are preloaded during boot time. Allocations here are guaranteed to never move or go away.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zygote heap&lt;/code&gt; The copy-on-write heap where an app process is forked from in the Android system.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app heap&lt;/code&gt; The primary heap on which your app allocates memory.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JNI heap&lt;/code&gt; The heap that shows where Java Native Interface (JNI) references are allocated and released.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Memory 영역을 선택&lt;/b&gt;하게 되면, &lt;b&gt;메모리에 대한 자세한 내용을 확인&lt;/b&gt;할 수 있는데, 메모리 누수를 찾기위해서 휴지통 모양의 아이콘을 여러 번 클릭해 &lt;code&gt;Force Garbage Collection&lt;/code&gt;을 수행한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이 동작후 &lt;code&gt;GC&lt;/code&gt;로도 &lt;b&gt;해제되지 않은 메모리를 검출&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (3).png&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brysjk/btsoo6IGNUy/N8uzzABK9tCgeGAN6CSE11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brysjk/btsoo6IGNUy/N8uzzABK9tCgeGAN6CSE11/img.png&quot; data-alt=&quot;출처:https://developer.android.com/studio/profile/memory-profiler&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brysjk/btsoo6IGNUy/N8uzzABK9tCgeGAN6CSE11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrysjk%2Fbtsoo6IGNUy%2FN8uzzABK9tCgeGAN6CSE11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Android Profiler에서의 Memory 부분 모습&quot; loading=&quot;lazy&quot; width=&quot;625&quot; height=&quot;317&quot; data-filename=&quot;Untitled (3).png&quot; data-origin-width=&quot;1023&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:https://developer.android.com/studio/profile/memory-profiler&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;이후, &lt;b&gt;capture heap dump를 선택후 Record를 클릭&lt;/b&gt;하면,&amp;nbsp;Heap Dump와 함께&amp;nbsp;Leaks&amp;nbsp;항목을 통해&amp;nbsp;&lt;b&gt;Memory Leak이 일어나고 있는 개수를 확인&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;여기서&amp;nbsp;Leaks를 클릭하면,&amp;nbsp;&lt;b&gt;메모리가 해제되지 않은 클래스를 보여준다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Leaks의 개수는 그 개수만큼&amp;nbsp;&lt;b&gt;Memory Leak이 발생한 의미가 아닌&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;반복 테스트한 횟수 만큼의 reference 숫자 만큼 보여지는 것&lt;/b&gt;&lt;/span&gt;이고, 만약&amp;nbsp;context가 해제되지 않고, 살아있다면 해당&amp;nbsp;reference까지 모두 잡히기 때문에&amp;nbsp;SupportRequestManagerFragment,&amp;nbsp;ReportFragment까지 검출되게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;중요한 것은&amp;nbsp;Application의&amp;nbsp;Class&amp;nbsp;중&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1번째 항목부터 분석&lt;/b&gt;&lt;/span&gt;하는 것이다.&lt;/li&gt;
&lt;li&gt;1번째 항목을 선택하고, 나오는&amp;nbsp;Instance List에서도&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1번째 항목을 선택&lt;/b&gt;&lt;/span&gt;하면,&amp;nbsp;Instance Detail&amp;nbsp;항목을 볼 수 있다. 여기서&amp;nbsp;References 탭을 클릭해,&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1번째 항목을 선택&lt;/b&gt;&lt;/span&gt;하고&amp;nbsp;child로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;타고 내려가다 보면, 의심스러운 변수명을 발견&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;이제&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;여기서부터 발견해 나가는 것이 개발자의 역량&lt;/b&gt;&lt;/span&gt;이다. 1번째 reference child가 아니라면 다른&amp;nbsp;reference들을 찾아나가는 과정을 거치면서&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;어떤 원인으로 메모리 누수가 발생해냈는 지를 유추&lt;/b&gt;&lt;/span&gt;해 나가야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LeakCanary&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;탐지방법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부적으로 &lt;code&gt;LeakCanary&lt;/code&gt;가 하는 것은, 단순히 &lt;b&gt;destroy된 Activity를 모니터링&lt;/b&gt; 하며, &lt;b&gt;5초동안 기다린 뒤&lt;/b&gt;에, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Garbage Collection을 강제하는 것&lt;/b&gt;&lt;/span&gt;이다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;code&gt;LeakCanary&lt;/code&gt;는 &lt;code&gt;destroyed object&lt;/code&gt;들의 &lt;code&gt;weak reference&lt;/code&gt;들을 hold하기 위해 &lt;code&gt;objectWatcher&lt;/code&gt;을 사용한다. &lt;code&gt;AppWatcher&lt;/code&gt;는 더이상 필요하지 않은 &lt;code&gt;object&lt;/code&gt;들을 &lt;code&gt;watch&lt;/code&gt;한다. 만약, 해당 &lt;code&gt;weak reference&lt;/code&gt;들이 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;5초 내에 clear되지 않는 다면, watched instance들은 retain되는 것으로 간주하고, &lt;u&gt;leaking instance의 가능성이 있음을 표시&lt;/u&gt;&lt;/b&gt;&lt;/span&gt;한다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Application&lt;/code&gt;이 실행되고 표시되는 동안 &lt;code&gt;ObjectWatcher&lt;/code&gt;가 보유한 &lt;code&gt;Instance&lt;/code&gt; retained object의 개수가 5개에 도달하거나 개발자의 조작을 통해, &lt;code&gt;LeakCanary&lt;/code&gt;는 &lt;code&gt;Java Heap&lt;/code&gt;을 파일 시스템에 저장된 &lt;code&gt;.hprof&lt;/code&gt; 파일에 &lt;code&gt;Dump&lt;/code&gt;한다. 그런 다음 Heap을 분석 보관된 &lt;code&gt;Instance&lt;/code&gt;가 &lt;code&gt;Garbage collection&lt;/code&gt;되지 않도록 하는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;references chain을 확인&lt;/b&gt;&lt;/span&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위의 과정을 거치면서 만약 &lt;code&gt;Activity&lt;/code&gt;가 여전히 존재하는 경우, &lt;code&gt;LeakCanary&lt;/code&gt;는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Potential Leak으로 간주&lt;/b&gt;&lt;/span&gt;한다.&lt;/li&gt;
&lt;li&gt;이는 기본적으로 &lt;code&gt;프로파일러&lt;/code&gt;를 사용해 수동으로 수행하던 &lt;code&gt;Force Garbage-Collector&lt;/code&gt;와 &lt;code&gt;Dump Heap&lt;/code&gt;을 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;모니터링하고 강제로 실행했던&lt;/b&gt; &lt;code&gt;Manual Process&lt;/code&gt;&lt;b&gt;의 자동화&lt;/b&gt;&lt;/span&gt;라고 볼 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;LeakCanary&lt;/code&gt;는 &lt;code&gt;Activity&lt;/code&gt;, &lt;code&gt;Fragment&lt;/code&gt;, &lt;code&gt;ViewModel&lt;/code&gt;, &lt;code&gt;Service&lt;/code&gt;, &lt;code&gt;Fragment&amp;rsquo;s View&lt;/code&gt;를 모니터링한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Leak Sources&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;Leaks&lt;/code&gt;는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;2개의 카테고리&lt;/b&gt;&lt;/span&gt;에서 일어난다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;App Level&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Library Level&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;App Level&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;App level&lt;/code&gt; Leak은 leak를 식별하고, &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;자체적으로 각 leak을 해결&lt;/span&gt;할 수 있다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Library Level&lt;/code&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Library level&lt;/code&gt; Leak은 할 수 있는 것이 많지 않다.&lt;/li&gt;
&lt;li&gt;따라서, &lt;code&gt;LeakCanary&lt;/code&gt;에선 &lt;b&gt;library leak을 자체적으로 표시해서 알려준다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서, &lt;code&gt;workable leaks&lt;/code&gt;에 초점을 맞추거나, &lt;code&gt;significant한 leak을 일으키는 library&lt;/code&gt;에 대해 assist 할 수 있다는 점이 &lt;code&gt;LeakCanary&lt;/code&gt;의 장점이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LeakCanary 사용법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;단순히 &lt;code&gt;build.gradle&lt;/code&gt; 파일에 아래를 추가해줌에 따라 사용 가능하다.&lt;i&gt;&lt;i&gt;&lt;code&gt;LeakCanary&lt;/code&gt;는 Debug에서만 사용되기 떄문에, debugImplementation으로 implementation 한다.&lt;br /&gt;&lt;/i&gt;&lt;/i&gt;
&lt;pre id=&quot;code_1689842387121&quot; class=&quot;nginx&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;debugImplementation 'com.squareup.leakcanary:leakcanary-android:$version'&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;이후 Application을 실행하던 도중에, &lt;code&gt;Suspected leak&lt;/code&gt;이 존재하면, 알림을 받을 수 있다.&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br6bnB/btsonJOitLy/m7LKapZVVKaltVg263FSK0/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/br6bnB/btsonJOitLy/m7LKapZVVKaltVg263FSK0/img.png&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;600&quot; data-filename=&quot;Untitled (4).png&quot; /&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;출처: &lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://write.agrevolution.in/memory-leaks-in-android-apps-45a27c6ac35d&quot;&gt;https://write.agrevolution.in/memory-leaks-in-android-apps-45a27c6ac35d&lt;/a&gt;&lt;/i&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;해당 알림을 클릭하거나, &lt;code&gt;leak object&lt;/code&gt;가 5개가 되었을 때 해당 &lt;code&gt;leak&lt;/code&gt;과 관련해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;analyze를 진행&lt;/b&gt;&lt;/span&gt;하게 된다.&lt;/li&gt;
&lt;li&gt;이후 analyze가 완료되고 나서, 다시 알림을 클릭하면 아래 이미지의 좌측과 같이 &lt;code&gt;leak list&lt;/code&gt;가 나오게 되고, &lt;b&gt;각각의 leak을 클릭&lt;/b&gt;하면 우측과 같이 &lt;b&gt;어떠한 leak이 몇 번 발생헀는지&lt;/b&gt; 알려준다. &lt;b&gt;동시에 leak trace를 보여주게 된다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled (5).png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;2532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctubg5/btsonTwFOlx/GO0kp0MAOjwhazN5VKGf3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctubg5/btsonTwFOlx/GO0kp0MAOjwhazN5VKGf3K/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://square.github.io/leakcanary/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctubg5/btsonTwFOlx/GO0kp0MAOjwhazN5VKGf3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fctubg5%2FbtsonTwFOlx%2FGO0kp0MAOjwhazN5VKGf3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;LeakCanary의 Leak List와 Leak Trace 예시&quot; loading=&quot;lazy&quot; width=&quot;545&quot; height=&quot;519&quot; data-filename=&quot;Untitled (5).png&quot; data-origin-width=&quot;2660&quot; data-origin-height=&quot;2532&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://square.github.io/leakcanary/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최하단으로 내리면, &lt;code&gt;&amp;lsquo;Leaking: YES&amp;rsquo;&lt;/code&gt; 와 동시에 &lt;code&gt;ObjectWatcher was watching this because com.example.myApplication.ListFragment received Fragment#onDestroyView() callback (references to its views should be cleared to prevent leaks&lt;/code&gt;) 처럼 표시된 부분이 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;leak trace&lt;/code&gt;에서는 &lt;code&gt;leak이 발생할 수 없는 곳&lt;/code&gt;은 빨간색의 밑줄이 그어지지 않고, &lt;code&gt;발생 가능한 지점&lt;/code&gt;부터 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;leak이 최초로 발생되었을 것으로 간주해 빨간색의 밑줄&lt;/b&gt;&lt;/span&gt;을 그어준다. 따라서, 해당 부분 부터 어디서 누수가 발생했을 지를 아래에서 부터 찾아가면 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Android Studio Profiler&lt;/code&gt;보다는 조금 더 친절하지만, 여전히 메모리 누수의 원인을 찾아내는 것은 개발자의 몫이다. &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;따라서, 메모리 누수가 발생하는 흔한 원인을 먼저 제거하는 것이 중요&lt;/b&gt;&lt;/span&gt;하다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Android</category>
      <category>Android Profiler</category>
      <category>LeakCanary</category>
      <category>memory leak</category>
      <category>memory profiler</category>
      <category>메모리누수</category>
      <category>안드로이드</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/118</guid>
      <comments>https://hodie.tistory.com/118#entry118comment</comments>
      <pubDate>Fri, 21 Jul 2023 07:47:35 +0900</pubDate>
    </item>
    <item>
      <title>[모바일] 무한 스크롤과 디자인 윤리</title>
      <link>https://hodie.tistory.com/117</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한스크롤 구현과 관련된 내용을 찾아보다, 무한스크롤이 메이저 UX에서 버려지고 있는 추세라는 점을 알게되었다. 여전히 많은 앱들에서는 체류시간 증가를 위해 무한 스크롤을 사용하고 있지만,&amp;nbsp;사용자에게 UI를&amp;nbsp;사용하는 액션 자체에 중독시킨다는 디자인 윤리의 문제 , 푸터로 접근이 불가능한 형태로 구현되는 경우가 많다는 점이 그 이유였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 페이지네이션과 콘텐츠 로드가 디자인 윤리라는 관점에서 접근한다는 것은 단순히 모바일 개발만 하던 나에게 새로운 관점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실, 서비스를 개발하는 입장에선 단순히 어떻게 하면 사용자를 붙잡을 수 있을지, 불편함을 느끼지 않을지 정도까지가 가지고 있는 생각이었는데, 이를 기획하거나 디자인하는 입장에서는 윤리와 같은 부분까지 고려한다는 점에서 모바일 환경의 서비스를 개발하는 입장에서도 알아두면 좋을 것 같아 한번 알아보았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무한 스크롤이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;무한 스크롤&lt;/b&gt;이란 &lt;span style=&quot;text-align: justify;&quot;&gt;매끄러운 사용자 경험의 대표적 예시로, 페이지를 사용자가 제어할 수 있는 부분이 없고 더 많은 콘텐츠를 보고 싶을 때 손가락으로 화면을 밀어올리기만 하면 새로운 내용이 하염없이 이어지는 방식이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;대부분의 SNS, 페이스북, 틱톡, 인스타그램 등뿐만 아니라, 쇼핑몰 페이지 등에서도 적극적으로 도입된 디자인으로 이전 페이지와 다음 페이지간의 경계나 정지 신호 없이 무한히 콘텐츠가 이어진다는 면에서 무한 스크롤이라고 불린다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;여기서 정지신호란 예를들어, 게임의 경우&amp;nbsp;&lt;/span&gt;접속 시간을 공지해주는 기능이 있다. &amp;lsquo;접속 후 1시간이 지났습니다.&amp;rsquo; &amp;lsquo;2시간이 지났습니다.&amp;rsquo; 하는 것도 정지 신호라고 할 수 있다. 유튜브 영상으로 이야기 한다면 한 편이 끝나는 지점이 정지신호라&amp;nbsp;볼 수 있는 것이다.&lt;/p&gt;
&lt;h2 style=&quot;text-align: justify;&quot; data-ke-size=&quot;size26&quot;&gt;디자인 윤리관점에서의 무한 스크롤의 문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;무한 스크롤 기능을 처음 만들어낸 디자이너는 &lt;span style=&quot;text-align: justify;&quot;&gt;에이자 래스킨이다. &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;넷플릭스 디자인 다큐 'Abstract' 이안 스폴터편에는 무한 스크롤을 처음 디자인한 '에이자 래스킨'의 인터뷰가 짧게 등장하는데, 해당 인터뷰에서 그는 '스크롤하고 있다는 건 이미 화면을 더 보고 싶다는 증거인데, 굳이 더보기 버튼을 하단에 배치해야 하는가?'라고 생각을 바탕으로 화면에서 더보기 버튼을 없앴다고 말한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;에이자 래스킨의 가설은 적중했고, 무한 스크롤은 현대 UI를 대표하는 것 중 하나가 되었다. 그러나, 다큐 말미 그는 자신이 만든 UI에 강한 윤리적 경각심을 내비치고 있는 것을 볼 수 있는데, &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;자신이 매끄러운 사용자 경험을 위해 개발한 &lt;/span&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;무한 스크롤이 정지신호가 없어 이용자의 시간을 너무 많이 빼앗고 중독으로 빠뜨리고 있는 현실을 보게 된것이다. &lt;span style=&quot;text-align: justify;&quot;&gt;래스킨은 자신의 개발에 대해 반성하고 트리스탄 해리스와 함께 &amp;lsquo;인도적 기술 센터&amp;rsquo;를 설립해 인터넷에 정지신호를 복원하자는 주장을 하고 있다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;결론&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;무한 스크롤이 디자인 윤리와 관련되어 있다는 점은 사실 알아볼수록 이미 그 위험성을 알고 있었지만, 외면하고 있었던 부분이라고 생각한다. 스마트폰 중독과 관련된 부분이 해당 UI와 관련되어 있다는 점은 부정할 수 없는 사실이라고 생각한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;하지만, 그렇다면 서비스를 제공하는 입장에서 최대한 사용자를 붙잡아야 하는데 정지신호라는 사용자 이탈점이 생기는 부분과 윤리적 관점에서 서비스를 제공해야 한다는 부분은 상충되는 부분이다. 그렇다면 앞으로 서비스를 개발해 나갈때 어떤 방향으로 보완해 개발해 나갈 수 있을까? &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;예를 들어 인스타그램의 경우엔 이전과 달리 피드에선 &lt;span style=&quot;text-align: justify;&quot;&gt;새로운 게시글을 다 본 후엔&amp;nbsp;&lt;/span&gt;이미 본 게시글들을 다시 보기위해선 이전 게시글들을 보기 위한 버튼을 한번 더 눌러야 하고, 사용시간에 대한 알람을 설정할 수 있다. 그러나, 여전히 돋보기라고 불리는 발견 탭은 무한스크롤임이 변함이 없고 릴스의 경우에도 무한정으로 내려갈 수 있다. 그정도가 서비스를 제공하는 입장에서의 최대한의 타협점일까?&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고자료&lt;/h2&gt;
&lt;p style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.hani.co.kr/arti/economy/it/1057466.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&amp;lsquo;무한 스크롤&amp;rsquo; 개발 디자이너도 나섰다&amp;hellip;&amp;ldquo;이젠 정지신호 복원할 때&amp;rdquo;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kwangjae.com/1144&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;무한 스크롤은 디자인 윤리에 어긋날까?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.facebook.com/groups/designforthesuperrealworld/posts/2809629269270577/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;인간을 위한 디자인 / 무한 스크롤과 에이자 래스킨&lt;/a&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;text-align: justify;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>ETC</category>
      <category>디자인윤리</category>
      <category>모바일서비스개발</category>
      <category>무한스크롤</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/117</guid>
      <comments>https://hodie.tistory.com/117#entry117comment</comments>
      <pubDate>Fri, 14 Jul 2023 11:31:55 +0900</pubDate>
    </item>
    <item>
      <title>[Android] RecyclerView Deep Dive - 2. RecyclerView에서의 활용과 최적화</title>
      <link>https://hodie.tistory.com/116</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;이어지는 글&lt;/h2&gt;
&lt;figure id=&quot;og_1688522406579&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Android] RecyclerView Deep Dive - 1. RecyclerView 정의와 동작원리 및 생명주기&quot; data-og-description=&quot;개요 이전에 RecyclerView에 대한 글을 정리한 적이 있었다. 당시에는, RecyclerView의 등장 의의와 RecyclerView를 사용하는 경우에 구현해야 하는 구현부에 대한 내용을 중심으로 작성했었다. 2022.12.22 - [I&quot; data-og-host=&quot;hodie.tistory.com&quot; data-og-source-url=&quot;https://hodie.tistory.com/113&quot; data-og-url=&quot;https://hodie.tistory.com/113&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bS6LDv/hyTcAHOvCb/9cE1dc36EpmgDHK7rU1AF0/img.gif?width=320&amp;amp;height=548&amp;amp;face=0_0_320_548,https://scrap.kakaocdn.net/dn/6OtmP/hyTeaU1Saa/42ssTgGOqIei29ut7sbI2k/img.png?width=720&amp;amp;height=518&amp;amp;face=0_0_720_518&quot;&gt;&lt;a href=&quot;https://hodie.tistory.com/113&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hodie.tistory.com/113&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bS6LDv/hyTcAHOvCb/9cE1dc36EpmgDHK7rU1AF0/img.gif?width=320&amp;amp;height=548&amp;amp;face=0_0_320_548,https://scrap.kakaocdn.net/dn/6OtmP/hyTeaU1Saa/42ssTgGOqIei29ut7sbI2k/img.png?width=720&amp;amp;height=518&amp;amp;face=0_0_720_518');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Android] RecyclerView Deep Dive - 1. RecyclerView 정의와 동작원리 및 생명주기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요 이전에 RecyclerView에 대한 글을 정리한 적이 있었다. 당시에는, RecyclerView의 등장 의의와 RecyclerView를 사용하는 경우에 구현해야 하는 구현부에 대한 내용을 중심으로 작성했었다. 2022.12.22 - [I&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hodie.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RecyclerView.ViewHolder에서의 ViewTreeLifecycleOwner 사용&lt;i&gt;&lt;/i&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://pluu.github.io/blog/android/2021/09/20/lifecycleowner/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://pluu.github.io/blog/android/2021/09/20/lifecycleowner/&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 ViewTreeLifecycleOwner 사용시&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;480&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmxltl/btsmsSTOMVp/oYYaEEyJKzcVZn0NTlpE41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmxltl/btsmsSTOMVp/oYYaEEyJKzcVZn0NTlpE41/img.png&quot; data-alt=&quot;출처:https://pluu.github.io/blog/android/2021/09/20/lifecycleowner/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmxltl/btsmsSTOMVp/oYYaEEyJKzcVZn0NTlpE41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdmxltl%2FbtsmsSTOMVp%2FoYYaEEyJKzcVZn0NTlpE41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;480&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;480&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:https://pluu.github.io/blog/android/2021/09/20/lifecycleowner/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 &lt;code&gt;onBindViewHolder&lt;/code&gt;에서 위와 같이 &lt;code&gt;null&lt;/code&gt;이 반환되는 것을 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewTreeLifecycleOwner의 간단한 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Android&lt;/code&gt;에는 생명주기 개념을 가지는 Component로 &lt;code&gt;Activity&lt;/code&gt;/&lt;code&gt;Fragment&lt;/code&gt;가 있다.&lt;br /&gt;그리고, 생명주기를 다룬다는 정의를 &lt;a href=&quot;https://developer.android.com/reference/androidx/lifecycle/LifecycleOwner&quot;&gt;LifecycleOwner&lt;/a&gt; 인터페이스를 통해서 선언하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface *LifecycleOwner* {
    @NonNullLifecycle getLifecycle();
}

// 출처: https://github.com/androidx/androidx/blob/androidx-main/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/LifecycleOwner.java&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;AndroidX&lt;/code&gt;에는 &lt;code&gt;ComponentActivity&lt;/code&gt;와 &lt;code&gt;Fragment&lt;/code&gt;에서 &lt;code&gt;LifecycleOwner interface&lt;/code&gt;를 구현하고 있다.&lt;br /&gt;그래서, 해당 &lt;code&gt;Component&lt;/code&gt;에서는 직접 &lt;code&gt;getLifecycle()&lt;/code&gt; 함수를 통해서 직접 생명주기를 다룰 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;코드 분리&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리팩토링 등으로 &lt;code&gt;Activity&lt;/code&gt;/&lt;code&gt;Fragment&lt;/code&gt;에 코드는 없지만, &lt;u&gt;&lt;code&gt;&lt;b&gt;Activity&lt;/b&gt;&lt;/code&gt;&lt;b&gt;/&lt;code&gt;Fragment&lt;/code&gt;&lt;/b&gt;&lt;b&gt;의 LifecycleOwner 인터페이스에 접근해야 할 경우가 있다.&lt;/b&gt;&lt;/u&gt;&lt;span style=&quot;background-color: #faecde;&quot;&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&amp;rarr; 이때 유용한 API가 바로 &lt;code style=&quot;letter-spacing: 0px;&quot;&gt;ViewTreeLifecycleOwner&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그리고, &lt;code&gt;LifecycleOwner&lt;/code&gt;은 &lt;a href=&quot;https://developer.android.com/reference/androidx/lifecycle/ViewTreeLifecycleOwner#get(android.view.View)&quot;&gt;ViewTreeLifecycleOwner.get&lt;/a&gt; 혹은 &lt;a href=&quot;https://developer.android.com/reference/kotlin/androidx/lifecycle/package-summary#findviewtreeviewmodelstoreowner&quot;&gt;View.findViewTreeLifecycleOwner&lt;/a&gt; KTX를 통해서 가져올 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;public fun View.findViewTreeLifecycleOwner(): LifecycleOwner? = ViewTreeLifecycleOwner.get(this)

// 출처:&amp;nbsp;https://github.com/androidx/androidx/blob/androidx-main/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/View.kt&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RecyclerView.ViewHolder에서의 LifecycleOwner를 사용할 위치&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Lifecycle&lt;/code&gt;에 맞춘 핸들링은 &lt;code&gt;Activity&lt;/code&gt;/&lt;code&gt;Fragment&lt;/code&gt;뿐만 아니라 경우에 따라서 &lt;code&gt;View&lt;/code&gt;가 존재하는 어느 곳이더라도 필요할 수도 있다.&lt;/li&gt;
&lt;li&gt;또한, &lt;code&gt;View.findViewTreeLifecycleOwner&lt;/code&gt; KTX를 사용하면 쉽게 &lt;code&gt;LifecycleOwner&lt;/code&gt;를 가져올 수 있다는 것을 이미 알고 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;에서도 &lt;code&gt;ViewHolder&lt;/code&gt;의 &lt;code&gt;itemView&lt;/code&gt;를 사용하면 당연히 &lt;code&gt;LifecycleOwner&lt;/code&gt;가 반환될 것이라고 기대하지만, 위에서 봤다시피 결과는 &lt;code&gt;null&lt;/code&gt;을 반환한다.&lt;/li&gt;
&lt;li&gt;그 이유가 무엇일까?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. ViewTreeLifecycleOwner&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이를 위해 먼저 살펴볼 것은, &lt;code&gt;ViewTreeLifecycleOwner.get&lt;/code&gt; 이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;findViewTreeLifecycleOwner&lt;/code&gt; KTX 또한 &lt;code&gt;ViewTreeLifecycleOwner.get&lt;/code&gt;을 호출하고 있으므로 &lt;code&gt;LifecycleOwner&lt;/code&gt;를 가져오는 핵심 코드라고 볼 수 있다.&lt;br /&gt;
&lt;pre id=&quot;code_1688519058206&quot; class=&quot;pgsql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ViewTreeLifecycleOwner {
   @Nullable
   public static LifecycleOwner get(@NonNull View view) {
      LifecycleOwner found = (LifecycleOwner) view.getTag(R.id.view_tree_lifecycle_owner);
      if (found != null) return found;
      ViewParent parent = view.getParent();
      while (found == null &amp;amp;&amp;amp; parent instanceof View) { // View#getParent가 유효한지 체크
         final View parentView = (View) parent;
         found = (LifecycleOwner) parentView.getTag(R.id.view_tree_lifecycle_owner);
         parent = parentView.getParent();
      }
      return found;
   }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;&amp;rarr; 기본 동작은 &lt;code&gt;View.getParent&lt;/code&gt;를 &lt;b&gt;반복적으로 호출&lt;/b&gt;하면서 &lt;code&gt;LifecycleOwner&lt;/code&gt; 가 존재하는지 체크하는 형태이다&lt;/li&gt;
&lt;li&gt;이어서 &lt;code&gt;onBindViewHolder&lt;/code&gt;에서 &lt;code&gt;View.getTag&lt;/code&gt;와 &lt;code&gt;View.getParent&lt;/code&gt;를 확인해 보면&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brYbg9/btsmsATswpS/onQtydJG39uh7b5QcTLPyK/img.png&quot; width=&quot;746&quot; height=&quot;299&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/brYbg9/btsmsATswpS/onQtydJG39uh7b5QcTLPyK/img.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;513&quot; /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;당연히 View.getTag를 통한 LifecycleOwner는 null이다.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wg3Bc/btsmupwOKqx/OlqJ7UAMXkj2svIV8vEiL1/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/Wg3Bc/btsmupwOKqx/OlqJ7UAMXkj2svIV8vEiL1/img.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;513&quot; /&gt;&lt;br /&gt;&amp;rarr;&amp;nbsp;그러나, &lt;code&gt;ViewHolder&lt;/code&gt;에서 &lt;code&gt;View&lt;/code&gt;에 해당하는 &lt;code&gt;itemView&lt;/code&gt;의 parent는 &lt;code&gt;null&lt;/code&gt;이라고 출력된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ViewTreeLifecycleOwner#get&lt;/code&gt; 로직에서 내부적으로 &lt;code&gt;View.getParent&lt;/code&gt;를 호출하면서 &lt;code&gt;LifecycleOwner&lt;/code&gt;가 존재하는 객체를 찾지만, 여기에서는 &lt;code&gt;parent&lt;/code&gt;가 &lt;code&gt;null&lt;/code&gt;이므로 최종적으로 &lt;code&gt;null&lt;/code&gt;로 반환되었던 것이다.&lt;br /&gt;&amp;rarr; &lt;b&gt;&lt;code&gt;null&lt;/code&gt;이 반환되는 원인은 &lt;code&gt;ViewHolder&lt;/code&gt;가 가리키는 View의 &lt;code&gt;부모 View가 null&lt;/code&gt;이기 때문이었다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. RecyclerView 생명주기&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 살펴봤듯이, &lt;code&gt;RecyclerView&lt;/code&gt; 사용 시에 &lt;code&gt;onCreateViewHolder&lt;/code&gt;를 통해서 &lt;code&gt;View&lt;/code&gt;가 생성되고, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;를 통해서 값을 반영하는 코드&lt;/b&gt;&lt;/span&gt;라는 것을 알고 있다.&lt;/li&gt;
&lt;li&gt;그런데, &lt;code&gt;onBindViewHolder&lt;/code&gt; 단계에서 &lt;code&gt;ViewHolder&lt;/code&gt;의 &lt;code&gt;View&lt;/code&gt;는 &lt;code&gt;RecyclerView&lt;/code&gt;에 Add되었을까? &lt;code&gt;ViewHolder&lt;/code&gt;의 &lt;code&gt;itemView&lt;/code&gt;는 &lt;code&gt;Attach&lt;/code&gt;되었을까?&lt;br /&gt;&amp;rarr; 정답은 No이다. 이전 &lt;a href=&quot;https://hodie.tistory.com/113#ViewHolder%20Lifecycle%20-%201.%20Birth-1&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RecyclerView Deep Dive - 1. RecyclerView 정의와 동작원리 및 생명주기 글에서의 RecyclerViewㅢ Birth의 요약&lt;/a&gt;을&amp;nbsp;보면, 최초 &lt;code&gt;ViewHolder&lt;/code&gt;를 생성하는 경우, Bind한 뒤에, 마지막 단계에 &lt;code&gt;addView()&lt;/code&gt;를 하고, &lt;code&gt;onViewAttachedToWindow()&lt;/code&gt;를 호출하는 것을 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 즉, &lt;code&gt;ViewHolder&lt;/code&gt;는 &lt;code&gt;onBindViewHolder&lt;/code&gt; 시점이 되더라도 &lt;code&gt;RecyclerView&lt;/code&gt;에 add 되지 않은 상태이기에, . &lt;code&gt;onBindViewHolder&lt;/code&gt;에서 &lt;code&gt;parent&lt;/code&gt;가 &lt;code&gt;null&lt;/code&gt;로 반환됐던 것이며, &lt;code&gt;findViewTreeLifecycleOwner&lt;/code&gt; 결과가 &lt;code&gt;null&lt;/code&gt;로 나온 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. onViewAttachedToWindow&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그렇다면, &lt;code&gt;onViewAttachedToWindow&lt;/code&gt; 이후엔 View가 add되었다 볼 수 있다. 그렇다면 결과가 달라질 수 있을까?&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P7ilu/btsmrStDZA2/O6lBcqIQXbGqQngEXZXtx1/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/P7ilu/btsmrStDZA2/O6lBcqIQXbGqQngEXZXtx1/img.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;497&quot; /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJyNhR/btsmqKv63sM/VRwIEwjzhNyUi0noihKUK0/img.png&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/bJyNhR/btsmqKv63sM/VRwIEwjzhNyUi0noihKUK0/img.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;497&quot; /&gt;출처:https://pluu.github.io/blog/android/2021/09/20/lifecycleowner/
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;div&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;ViewHolder&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;의 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;itemView&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;의 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;parent&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;가 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;RecyclerView&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;로 지정된다&lt;/span&gt;&lt;/div&gt;
&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(RecyclerView의 child로 add 됨)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;parent&lt;/code&gt;가 존재하므로 &lt;code&gt;findViewTreeLifecycleOwner&lt;/code&gt; API 결과로 유효한 &lt;code&gt;LifecycleOwner&lt;/code&gt;가 반환된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;&amp;rarr; 따라서 LifecycleOwner를 사용하기 위해선 View가 Attach된 이후에 사용해야 한다&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RecyclerView에서 LifecycleOwner의 사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView.ViewHolder&lt;/code&gt;를 통한 &lt;code&gt;LifecycleOwner&lt;/code&gt; 탐색 시 2가지 사실을 알 수 있었다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewTreeLifecycleOwner.get&lt;/code&gt;에서 &lt;code&gt;parent&lt;/code&gt;를 반복적으로 호출하면서 &lt;code&gt;LifecycleOwner&lt;/code&gt;를 소유하는 객체를 찾는다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt; 단계에서는 &lt;code&gt;RecyclerView&lt;/code&gt;의 child로 추가되지 않는다&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&lt;b&gt;LifecycleOwner&lt;/b&gt;&lt;/code&gt;&lt;b&gt;를 얻는 시점을 &lt;code&gt;onViewAttachedToWindow&lt;/code&gt;에서 다룰 수도 있지만,&lt;/b&gt;&lt;br /&gt;매번 &lt;code&gt;Adapter&lt;/code&gt;에서 &lt;code&gt;ViewHolder&lt;/code&gt;에 &lt;code&gt;&lt;b&gt;LifecycleOwner&lt;/b&gt;&lt;/code&gt;&lt;b&gt;를 주입하는 코드는 놓치기 쉬운 코드며 모든 &lt;code&gt;ViewHolder&lt;/code&gt;에서는 필요한 경우는 드물다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;대신 &lt;code&gt;ViewHolder&lt;/code&gt;의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;초기화 단계(init)&lt;/b&gt;&lt;/span&gt;에서 &lt;code&gt;doOnAttach&lt;/code&gt; KTX 함수를 사용하면 &lt;code&gt;ViewHolder&lt;/code&gt;에서 코드를 작성할 수 있다.&lt;br /&gt;&amp;rarr; 결과로 간단하게 &lt;code&gt;View&lt;/code&gt;가 &lt;code&gt;Attach&lt;/code&gt;된 이후에 &lt;code&gt;ViewTreeLifecycleOwner.get&lt;/code&gt; API를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1688520132617&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewHolder(
   private val binding: ItemRecyclerViewSampleBinding
) : RecyclerView.ViewHolder(binding.root) {
   private var lifecycleOwner: LifecycleOwner? = null
  
   init {
      itemView.doOnAttach {
         lifecycleOwner = itemView.findViewTreeLifecycleOwner()
      }
      itemView.doOnDetach {
         lifecycleOwner = null
      }
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그 결과&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGZE4/btsmrBZUYPW/OGoaZ2xXNo1oPUOiAtnlA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGZE4/btsmrBZUYPW/OGoaZ2xXNo1oPUOiAtnlA0/img.png&quot; data-alt=&quot;출처: https://pluu.github.io/blog/android/2021/09/20/lifecycleowner/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGZE4/btsmrBZUYPW/OGoaZ2xXNo1oPUOiAtnlA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnGZE4%2FbtsmrBZUYPW%2FOGoaZ2xXNo1oPUOiAtnlA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;287&quot; height=&quot;590&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;2220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처: https://pluu.github.io/blog/android/2021/09/20/lifecycleowner/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Tips &amp;amp; Tricks&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;No Update == No OnBind&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아이템을 업데이트하지 않는다면 &amp;rarr; 그 아이템에 대한 &lt;code&gt;onBind&lt;/code&gt;는 호출되지 않는다.&lt;br /&gt;&lt;u&gt;&lt;b&gt;즉, 아이템이 새로운 아이템으로 업데이트 되지 않는다면 &lt;code&gt;onBind&lt;/code&gt;는 재호출되지 않는다.&lt;/b&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;❌ Don&amp;rsquo;t this (잘못된 코드)&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mAdapter.notifyItemMoved(1, 5);
// onBind 호출 X
// onBind가 일어나지 않으므로 invalidate도 없다.

public void onBindViewHolder(final ViewHolder holder, final int position) {
    holder.itemView.setOnClickListener(
        new View.OnClickLister() {
            @Override
            public void onClick(View v) {
                removeAtPosition(**position**);
            }
        }
    }
}    
// 아이템이 이동되었을 때 position을 가지고 뭔가 추가하거나 제거하려는 동작을 구현하였으나
// 아래 코드 상황에서 position이 올바르게 동작하지 않는 버그가 발생한다.

onBindViewHolder(holder, 5);
notifyItemMoved(5, 15);
holder.itemView.callOnClick();
// 버그 발생!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ Do this&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public void onBindViewHolder(final ViewHolder holder, final int position) {
    holder.itemView.setOnClickListener(
        new View.OnClickLister() {
            @Override
            public void onClick(View v) {
                removeAtPosition(**holder.getAdapterPosition**);
            }
        }
    }
}
// ViewHolder는 현재 어댑터의 position을 알고 있기에 getAdapterPosition을 
// 사용하면 아이템이 이동되는 이벤트에도 정확한 position을 구할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Payloads&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;아이템의 변경&lt;/b&gt;에 &lt;code&gt;Payload&lt;/code&gt;를 &lt;b&gt;사용&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어, &lt;b&gt;유저의 좋아요 이벤트&lt;/b&gt; 같은 아이템의 변경에 &lt;b&gt;페이로드를 사용&lt;/b&gt;해볼 수 있는데 이는 &lt;code&gt;onBind&lt;/code&gt;를 더 효율적으로 만들며, &lt;b&gt;더 나은 애니메이션을 제공할 수 있다.&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1688520257052&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mAdapter.notifyItemChanged(position, LIKE_UPDATE);
// &quot;좋아요&quot; 이벤트가 발생하여 아이템을 업데이트&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;onBind&lt;/code&gt;에서 &lt;code&gt;payloads&lt;/code&gt;가 비었는지를 체크해볼 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;payloads&lt;/b&gt;&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;가 존재한다면&lt;/b&gt;&lt;/span&gt;&amp;nbsp;전체 아이템을 전부 다시 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;code&gt;Bind&lt;/code&gt;할 필요가 없어진다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;code&gt;payloads&lt;/code&gt;를 통해 오직 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;변경점에 대한 부분만 다시 &lt;code&gt;Bind&lt;/code&gt;할 수 있게 되는 것&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1688520330927&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void onBindViewHolder(MyViewHolder holder, int position, List payloads) {
	if (payloads.isEmpty()) {
		// 전체 Re-Bind
		onBindViewHolder(holder, position);
	} else if (payloads.contains(LIKE_UPDATE)) {
		// 부분적인 Re-Bind
		Item item = mItems.get(position);
		holder.likeButton.setEnabled(!item.isLiked());	
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;OnCreate&lt;/code&gt; Means &amp;ldquo;&lt;code&gt;create&lt;/code&gt;&amp;rdquo;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자주하는 실수는 &lt;b&gt;헤더 뷰 같은 것을 구현할 때 발생&lt;/b&gt;한다.&lt;br /&gt;아래 코드에서 기존의 헤더 &lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ViewHolder&lt;/b&gt;&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;가 존재한다면 재사용해주도록 Return&lt;/b&gt;&lt;/span&gt;하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;❌ Don&amp;rsquo;t this&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == HeaderViewHolder.TYPE) {
        if (mHeaderViewHolder == null) {
            mHeaderViewHolder = new HeaderViewHolder(parent);
        }
        return mHeaderViewHolder;
    }
    return new MyViewHolder(parent);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 위 코드는 버그를 발생시킬 수 있는데, 이에 대해 &lt;code&gt;onCreateViewHolder&lt;/code&gt;가 어떤 의미인지 생각해볼 필요가 있다. &lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;말 그대로 &amp;lsquo;생성&amp;rsquo;을 의미&lt;/b&gt;&lt;/span&gt;하는 것이기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;위 코드는 바람직하지 않다&lt;/b&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;✅ Do this&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == HeaderViewHolder.TYPE) {
        return new HeaderViewHolder(parent);
    }
    return new MyViewHolder(parent);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Positions : &lt;code&gt;Adapter&lt;/code&gt; vs &lt;code&gt;Layout&lt;/code&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;adapterPosition&lt;/code&gt;과 &lt;code&gt;layoutPosition&lt;/code&gt;은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;아이템 &lt;u&gt;리스트의 변경&lt;/u&gt;이 생겼을 때 차이&lt;/b&gt;&lt;/span&gt;가 발생하게 된다.&lt;br /&gt;왜냐하면 &lt;code&gt;다음 레이아웃 과정&lt;/code&gt;에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이 position을 계산하는 과정이 비동기적이기 때문&lt;/b&gt;&lt;/span&gt;이다.&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ochj2/btsmunsjNRc/HvUIz2eQahhZzK3pMGtatk/img.png&quot; width=&quot;300&quot; height=&quot;273&quot; data-origin-width=&quot;1074&quot; data-origin-height=&quot;978&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/li&gt;
&lt;li&gt;비로소 &lt;code&gt;레이아웃 과정&lt;/code&gt;을 마치고 &lt;code&gt;View&lt;/code&gt;가 &lt;code&gt;refresh&lt;/code&gt; 된 이후에야 &lt;code&gt;adapterPosition&lt;/code&gt;과 &lt;code&gt;layoutPosition&lt;/code&gt;이 일치하게 되는 것이다.&lt;br /&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/euc25i/btsmrEPzwGI/lx3UCc4ckUqrEYkYJnUxkK/img.png&quot; width=&quot;456&quot; height=&quot;277&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;778&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;rarr; 그러므로 &lt;code&gt;데이터에 접근할 때&lt;/code&gt;는 &lt;code&gt;adapterPosition&lt;/code&gt;을 사용하는게 좋을 것이고&lt;br /&gt;유저가 &lt;code&gt;무언가를 클릭 했을 때 위, 아래에 있는 아이템에 접근해야할 경우&lt;/code&gt;에는 &lt;code&gt;layoutPosition&lt;/code&gt;을 사용하는게 좋다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RecyclerView의 최적화&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. RecyclerView/Adapter의 함수를 효율적으로 응용&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;setHasStableIds&lt;/code&gt;와 &lt;code&gt;getItemId&lt;/code&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;HasStableIds&lt;/code&gt; 사용을 통해, 데이터를 바인딩하는 경우, &lt;code&gt;onBindViewHolder()&lt;/code&gt;를 최적화되게 호출 할 수있다.&lt;/li&gt;
&lt;li&gt;참고로, &lt;code&gt;DiffUtil&lt;/code&gt;을 사용하는 경우, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;u&gt;&lt;b&gt;setHasStableId(true) , getItemId()는 사용하지 않아도 된다.&lt;/b&gt;&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;차이 알고리즘에 의해 필요한 부분만 업데이트가 이미 되기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;code&gt;HasStableIds&lt;/code&gt; 사용으로 최적화 호출이 가능한 조건&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;둘 중 하나의 조건에만 해당한다면 성능을 크게 향상시킬 수 있다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;똑같은 데이터가 반복적&lt;/b&gt;으로 나타나는 리스트&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;notifyDataSetChanged&lt;/code&gt;를 자주 호출한다&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최적화 호출 원리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;setHasStableIds(true)&lt;/code&gt; 를 사용하면 각각 아이템 &lt;code&gt;position&lt;/code&gt;에 지정된 &lt;code&gt;id&lt;/code&gt;를 기준으로 상황에 따라 &lt;code&gt;onBindViewHolder()&lt;/code&gt; &lt;b&gt;호출을 제외시킨다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;u&gt;&lt;b&gt;값이 변경된 &lt;code&gt;id&lt;/code&gt;&lt;/b&gt;&lt;/u&gt;만 &lt;code&gt;onBindViewHolder&lt;/code&gt;를 호출하거나 호출된 아이템의 &lt;code&gt;id&lt;/code&gt;가 이전 &lt;code&gt;position&lt;/code&gt; 아이템에 &lt;b&gt;이미 존재할 시&lt;/b&gt; &lt;code&gt;onBindViewHolder&lt;/code&gt; 함수를 호출 하지 않고 &lt;b&gt;이전에 같은 &lt;code&gt;id&lt;/code&gt;를 가진 &lt;code&gt;View&lt;/code&gt;&lt;/b&gt;를 대신 보여준다.&lt;/li&gt;
&lt;li&gt;이러한 원리로 같은 데이터임을 알고, 데이터 바인드를 할 필요가 없기 때문에 &lt;b&gt;호출되지 않으며 그만큼 성능이 향상&lt;/b&gt;될 수 있는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 방법&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;HasStableIds&lt;/code&gt;는 &lt;code&gt;Adapter.setHasStableIds(boolean)&lt;/code&gt;을 통해 설정할 수 있고, 사용하는 경우 &lt;code&gt;Adapter&lt;/code&gt;의 &lt;code&gt;getItemId(int)&lt;/code&gt;를 반드시 구현해야 작동한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getItemId(int)&lt;/code&gt;를 통해 해당 아이템은 고정된 상태로 설정된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 예시 (&lt;a href=&quot;https://blog.kmshack.kr/Stable-Id를-이용한-RecyclerView-성능-향상법/&quot;&gt;https://blog.kmshack.kr/Stable-Id를-이용한-RecyclerView-성능-향상법/&lt;/a&gt;)&lt;/h4&gt;
&lt;table style=&quot;height: 259px;&quot; width=&quot;355&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;position&lt;/th&gt;
&lt;th&gt;return&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위와 같이 값이 반환되게 구현된 경우,&amp;nbsp;onBindViewHolder(view, int)는&amp;nbsp;position&amp;nbsp;0, 1, 2, 4, 5만 호출된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;3번은 0번째 포지션에서 같은 고정된 ID를 반환했기 때문에&amp;nbsp;같은 데이터로 인식&lt;/b&gt;하여&amp;nbsp;onBindViewHolder(view, int)가 호출되지 않는다&lt;br /&gt;&amp;rarr;&amp;nbsp;같은&amp;nbsp;데이터임을&amp;nbsp;알고&amp;nbsp;데이터&amp;nbsp;바인드를&amp;nbsp;할&amp;nbsp;필요가&amp;nbsp;없기&amp;nbsp;때문에&amp;nbsp;호출되지&amp;nbsp;않으며&amp;nbsp;그만큼&amp;nbsp;성능은&amp;nbsp;향상된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;height: 258px;&quot; width=&quot;362&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;position&lt;/th&gt;
&lt;th style=&quot;height: 19px;&quot;&gt;return&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;400&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;position 2번에 데이터를 추가하고&amp;nbsp;notifyDataSetChanged()를 호출하였다. 이때, onBindViewHolder(view, int) 는 현재 보이고 있는 포지션이 모두 호출되지만&amp;nbsp; StableId를 사용하게 된다면 이미 호출된 고정된 ID를 제외한 위치가 호출된다.&lt;/li&gt;
&lt;li&gt;notifyDataSetChanged()를 하였음에도 &lt;b&gt;변경되는 ID만을 골라&lt;/b&gt; 해당 포지션만 onBindViewHolder(view, int)를 호출하게 됨으로 그만큼 &lt;b&gt;성능은 향상&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;setHasFixedSize&lt;/code&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;는 데이터가 삽입, 삭제가 될 때 각각 &lt;b&gt;아이템의 레이아웃을 다시 계산해야할지 정한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hasFixedSize&lt;/code&gt;가 &lt;code&gt;true&lt;/code&gt; 일 경우 &lt;b&gt;고정값으로 인식&lt;/b&gt;하고,&lt;br /&gt;&lt;code&gt;false&lt;/code&gt;일 경우 &lt;code&gt;requestLayout()&lt;/code&gt; 호출이 되어 &lt;b&gt;아이템의 레이아웃 계산이 다시&lt;/b&gt; 이루어진다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;View&lt;/code&gt;와 관련된 작업은 모두 &lt;code&gt;UI Thread&lt;/code&gt;가 위임하기 때문에, 버벅거림이 없는 &lt;code&gt;60프레임&lt;/code&gt;을 달성 하려면 최대한 &lt;code&gt;UI Thread&lt;/code&gt;를 효율적으로 활용 해야한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;따라서 아이템 UI의 사이즈가 고정이라면 &lt;code&gt;true&lt;/code&gt;로 하는게 좋다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중첩 RecyclerView간의 Pool 공유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중첩되어 구현된 &lt;code&gt;RecyclerView&lt;/code&gt;에서 &lt;code&gt;RecyclerView&lt;/code&gt;간의 &lt;code&gt;pool&lt;/code&gt;를 공유해 성능을 향상 시킬 수 있다.&lt;br /&gt;&amp;rarr; 바깥쪽과 안쪽 pool를 공유하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;override fun onCreateViewHolder(...): RecyclerView.ViewHolder {
    val innerLm = LinearLayoutManager(...) innerRv.apply{
        layoutManager = innerLm recyclerViewPool = sharedPool
    } return OuterAdapter.ViewHolder(innerRv)
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. &lt;code&gt;View&lt;/code&gt; 및 &lt;code&gt;onBindViewHolder&lt;/code&gt; 최적화&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;에서 복잡한 로직 피하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;는 가능하면 순수 &lt;b&gt;data set만 하는 것이 가장 이상적&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;data&lt;/code&gt;를 외부에서 미리 가공해서 &lt;code&gt;onBindViewHolder&lt;/code&gt;에서 &lt;code&gt;set&lt;/code&gt; 하는 것이 가장 안정적이다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구체적 예시&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;에서 가능하면 &lt;code&gt;for&lt;/code&gt;, &lt;code&gt;while&lt;/code&gt; 과 같은 &lt;b&gt;반복문은 피한다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;에서 &lt;b&gt;특별한 경우를 제외하고 콜백,리스너 정의를 하지 않아야 한다.&lt;/b&gt;&lt;br /&gt;(ex: onClickListener)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1688520912778&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ovrride fun onBindViewHolder(
    holder: RecyclerView.ViewHolder,
    position: Int
) { 
// 앱 성능 저하의 원인 
    for (...) {...}
    
// 불 필요한 코드
    view.setOnClickListenr { ... }
    
//아래 처럼 set만 하는 코드만 존재하는 것이 가장 이상적
    imageView.set...(data[position].url)
    textView.text = data[position].text
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;0&quot;&gt;ViewHolder&lt;/span&gt; 및 &lt;span style=&quot;color: #eb5757;&quot; data-token-index=&quot;2&quot;&gt;onCreateViewHolder&lt;/span&gt;에서 가능한 미리 값 처리&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시 코드&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예시로 &lt;code&gt;width&lt;/code&gt;를 디바이스 기기의 3분의 1 크기로 고정값으로 사용해야하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1688520948069&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class ViewHolder(v: View) :
    RecyclerView.ViewHolder(v) {
    init {
        // 가능하면 미리 정할 수 있는 값은 ViewHolder나 onCreateView에서 처리하는 것이 좋다.
        v.layoutParms = LayoutParms(....)
        v.itemView.setOnClickListener { ... }
    }

    override fun onBindViewHolder(...) {
        //별로 좋지않는 코드, 성능 저하의 원인 중 일부분이 될 수 있음 
        holder.layoutParms = LayoutParms(....)
        holder.itemView.setOnClickListener { ... }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 위 처럼 가능하면 &lt;code&gt;ViewHolder&lt;/code&gt;에서 미리 처리할 수 있는 것은 &lt;code&gt;set&lt;/code&gt;을 해두는 것이 좋고 &lt;code&gt;onBindViewHolder&lt;/code&gt;에 최대한 부담을 없애야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code class=&quot;language-kotlin&quot;&gt;&lt;/code&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;OnBindViewHolder&lt;/code&gt;에서 &lt;code&gt;Html.fromHtml()&lt;/code&gt; 사용 지양&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;html&lt;/code&gt; 를 사용하기 때문에 마찬가지로 &lt;code&gt;onBindViewHolder&lt;/code&gt;에 적합하지 않다.&lt;/li&gt;
&lt;li&gt;성능저하의 원인 중 하나가 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아이템 레이아웃 UI 계층 단순화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;안드로이드 xml&lt;/code&gt;은 &lt;code&gt;View&lt;/code&gt;가 위치한 &lt;code&gt;depth&lt;/code&gt;가 한단계 씩 깊어질 수록 &lt;code&gt;UI 연산&lt;/code&gt;이 &lt;b&gt;배로 증가한다&lt;/b&gt;&lt;br /&gt;&lt;a href=&quot;https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html&quot;&gt;https://android-developers.googleblog.com/2017/08/understanding-performance-benefits-of.html&lt;/a&gt;&lt;br /&gt;&amp;rarr; 성능 저하의 원인으로 이어질 수 있다&lt;/li&gt;
&lt;li&gt;따라서, 특별한 경우를 제외하고 가능하면 xml 에서 &lt;code&gt;ConstraintLayout&lt;/code&gt;를 사용해 &lt;code&gt;1 depth&lt;/code&gt;로 View를 그리는 것이 좋다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이상적코드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1688521047284&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;android.support.constraint.ConstraintLayout&amp;gt; 
&amp;lt;ImageView /&amp;gt; 
&amp;lt;ImageView /&amp;gt; 
&amp;lt;TextView /&amp;gt; 
&amp;lt;EditText /&amp;gt; 
&amp;lt;TextView /&amp;gt; 
&amp;lt;TextView /&amp;gt; 
&amp;lt;EditText /&amp;gt; 
&amp;lt;Button /&amp;gt; 
&amp;lt;Button /&amp;gt; 
&amp;lt;TextView /&amp;gt; 
&amp;lt;/android.support.constraint.ConstraintLayout&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부모 계층 &lt;code&gt;ViewGroup&lt;/code&gt;이 &lt;code&gt;ScrollView&lt;/code&gt; or &lt;code&gt;RecyclerView&lt;/code&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;recyclerView.setNestedScrollingEnabled(false)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상위 뷰에 따라 스크롤이 부드럽지 않는 경우도 있다.&lt;/li&gt;
&lt;li&gt;그럴 때 위 코드를 적용하면 한층 더 좋아짐을 알 수가 있으며 &lt;code&gt;XML&lt;/code&gt;에서도 설정 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;item&lt;/code&gt;에 복잡한 &lt;code&gt;drawable&lt;/code&gt; 사용 지양&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;layout-list&amp;gt;&lt;/code&gt; 로 이루어진 복잡한 &lt;code&gt;drawable&lt;/code&gt;을 &lt;code&gt;item XML&lt;/code&gt;에 가능하면 사용하지 말아야한다.&lt;br /&gt;&amp;rarr; &lt;b&gt;저가용 기기에서 GPU 에 부하를 주기 때문에&lt;/b&gt; 가능하면 심플하게 사용해야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;ViewStub&lt;/code&gt; 사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewStub&lt;/code&gt;으로 지연 인플레이트를 사용하는 것이 좋다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;visible&lt;/code&gt; 로 설정된 경우에 &lt;code&gt;inflate&lt;/code&gt; 하기 때문에 &lt;b&gt;성능 컨트롤에 효율적이다&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;투명색은 가능하면 지양&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;투명색 사용은 저가용 기기에서 상당한 부하가 될 수 있다.&lt;/li&gt;
&lt;li&gt;이는 투명색 처리는 연산처리가 많기 때문에 가급적 피하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 이외의 시도&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;LayoutManager&lt;/code&gt; custom을 통해 &lt;code&gt;onCreateView&lt;/code&gt; 호출을 여러번 미리 가져오기 (&lt;code&gt;PreCache&lt;/code&gt;)&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최적화 원리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewHolder&lt;/code&gt; 생성 ~ 제거 Lifecycle에서 &lt;code&gt;onCreateViewHolder&lt;/code&gt; 는 최초로 &lt;code&gt;ViewHolder&lt;/code&gt; 생성한다.&lt;/li&gt;
&lt;li&gt;하지만 &lt;b&gt;[생성]&lt;/b&gt;을 미리 해놓는 경우, &lt;b&gt;다음 LifeCycle&lt;/b&gt;에서 &lt;b&gt;&lt;code&gt;onCreateViewHolder&lt;/code&gt; 호출 횟수를 줄이는 방법이 존재한다&lt;br /&gt;&lt;/b&gt;&amp;rarr; 바로 &lt;code&gt;RecyclerView&lt;/code&gt;가 &lt;b&gt;내부적으로 인식하는 화면 사이즈를 크게 잡는 것&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;code&gt;사용자가 눈으로 볼 수 있는 화면&lt;/code&gt; + &lt;code&gt;보이지 않는 위아래 가상의 영역의 사이즈&lt;/code&gt;가 있는데 이 &lt;code&gt;전체 사이즈&lt;/code&gt;를 내부적으로 늘리면 &lt;br /&gt;&amp;rarr; &lt;b&gt;기본적으로 갖는 아이템의 양의 수보다 더 많은 아이템을&lt;/b&gt; &lt;code&gt;onCreateViewHolder&lt;/code&gt;&lt;b&gt;로 생성 해놓을 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;스크롤을 하게 되면 &lt;b&gt;이미 &lt;code&gt;onCreateViewHolder&lt;/code&gt; 로 생성한 아이템들로 인해&lt;/b&gt; &lt;code&gt;onCreateViewHolder&lt;/code&gt; 함&lt;b&gt;수의 호출이 적어진다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1688521173692&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class PreCacheLayoutManager(context: Context, private val extraLayoutSpace: Int) :
    LinearLayoutManager(context) {
    override fun getExtraLayoutSpace(state: RecyclerView.State?) = extraLayoutSpace
}
//Activity 에서..
recyclerView.adapter = PreCacheLayoutManager(context,600)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code class=&quot;language-kotlin&quot;&gt;  &lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;그러나,&lt;/b&gt; 사이즈를 너무 크게 잡아버리면 &lt;code&gt;init&lt;/code&gt; 시 호출되는 &lt;code&gt;onCreateViewHolder&lt;/code&gt;가 많아지고 &lt;b&gt;가상으로 인식하는 사이즈가 매우 넓기&lt;/b&gt; 때문에 &lt;code&gt;init&lt;/code&gt; 타이밍에 버벅거림이 존재할 수 있다.&lt;/li&gt;
&lt;li&gt;따라서, 이 방식은 &lt;b&gt;적재적소하게 사용&lt;/b&gt;하는 것이 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;LayoutManager.setItemPrefetchEnabled(true, false)&lt;/code&gt; 사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView 25.0.0&lt;/code&gt; 부터 기본적으로 &lt;b&gt;&lt;code&gt;setItemPrefetchEnabled()&lt;/code&gt;이 true로 설정&lt;/b&gt;되어 있다.&lt;br /&gt;&amp;rarr; 따라서 &lt;code&gt;true&lt;/code&gt;로 하고 싶다면 별도의 설정을 하지 않아도 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;최적화 원리&lt;/h4&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;// false 하고 싶을 때만 직접적으로 명시
layoutManager.itemPrefetchEnabled = false&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;를 &lt;b&gt;몇 프레임 앞서서 미리 호출하는 방식이다&lt;/b&gt;.&lt;br /&gt;&amp;rarr; 유저가 &lt;b&gt;빠른 스크롤&lt;/b&gt;을 할때 &lt;b&gt;미리 &lt;code&gt;onBindViewHolder&lt;/code&gt;를 미리 호출한 것을 보여주기 때문에&lt;/b&gt; 자연스러운 스크롤을 달성할 수 있다.&lt;/li&gt;
&lt;li&gt;(1) &lt;code&gt;UI thread&lt;/code&gt;에서 &lt;code&gt;View&lt;/code&gt;의 &lt;code&gt;inflate&lt;/code&gt; 와 &lt;code&gt;bind&lt;/code&gt;가 완료되면&lt;br /&gt;(2) 순차적으로 &lt;code&gt;GPU Render Thread&lt;/code&gt;에서 렌더링 작업을 하게 되는데,&lt;br /&gt;(3) 이때 &lt;code&gt;UI Thread&lt;/code&gt;는 유휴 상태가 된다.&lt;br /&gt;&amp;rarr; 스크롤 할때 이 순서를 반복&lt;/li&gt;
&lt;li&gt;하지만 스크롤할때 새로운 &lt;code&gt;View&lt;/code&gt;가 등장하기 위해 &lt;code&gt;UI Thread&lt;/code&gt;는 다시 &lt;code&gt;infalte &amp;amp; bind&lt;/code&gt; 작업을 하게 되는데, 문제는 위 작업이 &lt;code&gt;Render Thread&lt;/code&gt; 에서 &lt;b&gt;렌더링이 끝나고 UI가 사용자 눈에 표시 되려 하는 순간과 겹치게 된다.&lt;br /&gt;&lt;/b&gt;&amp;rarr; 그래서 &lt;code&gt;UI Thread&lt;/code&gt; 의 &lt;code&gt;View&lt;/code&gt; &lt;code&gt;infate &amp;amp; bind&lt;/code&gt; 작업은 &lt;b&gt;비용이 매우 크면서 동시에 렌더링 된 UI가 표시&lt;/b&gt;가 되기 때문에 &lt;br /&gt;&amp;rarr; 순간적으로 버벅거림이 생기게 된다.&lt;/li&gt;
&lt;li&gt;따라서 &lt;code&gt;Prefetch 방식&lt;/code&gt;을 통해 스크롤 할때 &lt;code&gt;inflate&lt;/code&gt; 가 필요한 경우,&lt;br /&gt;&lt;code&gt;UI 스레드&lt;/code&gt;가 유휴상태에 들어가는 동안 미리 추가적으로 &lt;code&gt;onBindViewHolder()&lt;/code&gt; 를 호출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용 조건&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt; 버전이 업데이트 되면서 &lt;code&gt;setItemPrefetchEnabled()&lt;/code&gt; 가 기본적으로 &lt;code&gt;true&lt;/code&gt;로 설정되어 있다. 그러나, &lt;b&gt;&lt;i&gt;&lt;code&gt;ViewCache&lt;/code&gt;&lt;/i&gt; 메모리를 추가적으로 사용하게 된다.&lt;br /&gt;&amp;rarr; 추가적인 비용 소요&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;따라서 &lt;code&gt;스크롤이 조금만 발생을 하도록 유도한 UX&lt;/code&gt;, &lt;code&gt;전체 아이템 갯수가 매우 적을 경우&lt;/code&gt; &amp;rarr; &lt;code&gt;false&lt;/code&gt;&lt;br /&gt;반대로 &lt;code&gt;많은 스크롤이 일어나는 &lt;b&gt;일반적인 경우&lt;/b&gt;&lt;/code&gt; &amp;rarr; &lt;b&gt;&lt;code&gt;true&lt;/code&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;init&lt;/code&gt; 시에만 &lt;code&gt;AsycLayoutInflate&lt;/code&gt; 사용, UI 스케줄러 제작&lt;br /&gt;(안드로이드 개발자 Phil Olson의 방식 )&lt;/h3&gt;
&lt;figure id=&quot;og_1688522307251&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Smooth RecyclerView scrolling in Android&quot; data-og-description=&quot;Tips for using complex views without skipping frames&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot; data-og-url=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RIiyD/hyTeeb3A3T/S6EMmdyO0ka8r8KX18LBV0/img.jpg?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800,https://scrap.kakaocdn.net/dn/cC719b/hyTcNHd6QH/EspSeP6uTus1hfuQL5MPmk/img.jpg?width=1358&amp;amp;height=1018&amp;amp;face=581_89_704_222,https://scrap.kakaocdn.net/dn/5SMzZ/hyTcI6Udzw/o4DwJJiZT2UbBRuI5Pdp4K/img.jpg?width=960&amp;amp;height=617&amp;amp;face=0_0_960_617&quot;&gt;&lt;a href=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@polson55/smooth-recyclerview-scrolling-in-android-57e7a9b71ca7&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RIiyD/hyTeeb3A3T/S6EMmdyO0ka8r8KX18LBV0/img.jpg?width=1200&amp;amp;height=800&amp;amp;face=0_0_1200_800,https://scrap.kakaocdn.net/dn/cC719b/hyTcNHd6QH/EspSeP6uTus1hfuQL5MPmk/img.jpg?width=1358&amp;amp;height=1018&amp;amp;face=581_89_704_222,https://scrap.kakaocdn.net/dn/5SMzZ/hyTcI6Udzw/o4DwJJiZT2UbBRuI5Pdp4K/img.jpg?width=960&amp;amp;height=617&amp;amp;face=0_0_960_617');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Smooth RecyclerView scrolling in Android&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Tips for using complex views without skipping frames&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;RecyclerView&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;에 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;adapter&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;를 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;set&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;하면서 동시에 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;RecyclerView Adapter&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 를 사용하는 경우가 아닌, &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;Adapter&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;에 &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;뒤늦게 아이템이 insert&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 되는 경우, &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;AsycLayoutInflate&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt; 를 사용해 미리 &lt;/span&gt;&lt;code style=&quot;letter-spacing: 0px;&quot;&gt;onCreateViewHolder&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;에서 &lt;/span&gt;&lt;b&gt;사용할 &lt;code&gt;View&lt;/code&gt;를 만들어 두어 &lt;code&gt;onCreateViewHolder&lt;/code&gt;의 부담을 더는 방법&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class SmoothListAdapter(val context: Context) :
    ListAdapter&amp;lt;ListItem, SmoothListAdapter.ListItemViewHolder&amp;gt;(ListItemViewHolder.MyDiffCallback()) {
    data class ListItem(val id: String, val text: String)
    class ListItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {

        private val asyncLayoutInflater = AsyncLayoutInflater(context)
        private val cachedViews = Stack&amp;lt;View&amp;gt;()
        init {
            //Create some views asynchronously and add them to our stack 
            for (i in 0..NUM_CACHED_VIEWS) {
                asyncLayoutInflater.inflate(
                    R.layout.list_item,
                    null
                ) { view, layoutRes, viewGroup -&amp;gt; cachedViews.push(view) }
            }
        }

        override fun onCreateViewHolder(
            parent: ViewGroup,
            viewType: Int
        ): ListItemViewHolder {
            //Use the cached views if possible, otherwise if we ran out of cached views inflate a new one 
            val view = if (cachedViews.isEmpty()) {
                LayoutInflater.from(context).inflate(R.layout.list_item, parent, false)
            } else {
                cachedViews.pop().also {
                    it.layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
                }
            }
            return ListItemViewHolder(view)
        }

        fun populateFrom(listItem: ListItem) {
            //TODO: populate your view 
        }
        override fun onBindViewHolder(viewHolder: ListItemViewHolder, position: Int) =
            viewHolder.populateFrom(getItem(position))

        class MyDiffCallback : DiffUtil.ItemCallback&amp;lt;ListItem&amp;gt;() {
            override fun areItemsTheSame(firstItem: ListItem, secondItem: ListItem) =
                firstItem.id == secondItem.id override fun areContentsTheSame(
                    firstItem: ListItem,
                    secondItem: ListItem
                ) = firstItem == secondItem
        }

        companion object {
            const val NUM_CACHED_VIEWS = 5
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 위 코드 처럼 &lt;code&gt;ListAdapter&lt;/code&gt;를 사용한다면 미리 &lt;code&gt;adapter&lt;/code&gt;를 초기화 해두고 &lt;code&gt;submitList(colleaction&amp;lt;T&amp;gt;)&lt;/code&gt; 를 &lt;b&gt;뒤늦게 사용&lt;/b&gt;하게 된다면 미리 만들어둔 &lt;code&gt;view&lt;/code&gt;를 &lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;stack&lt;/b&gt;&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;에 담아놨던걸 그대로 사용&lt;/b&gt;&lt;/span&gt;하면 된다.&lt;/li&gt;
&lt;li&gt;또한 &lt;code&gt;UI 스케줄러&lt;/code&gt;를 &lt;b&gt;따로 만들어 사용 가능&lt;/b&gt;하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1688521813860&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object UIJobScheduler {
    private const val MAX_JOB_TIME_MS: Float = 4f private
            var elapsed = 0L private
                    val jobQueue = ArrayDeque&amp;lt;() -&amp;gt; Unit&amp;gt;() private
                            val isOverMaxTime get () = elapsed &amp;gt; MAX_JOB_TIME_MS * 1_000_000 private
                            val handler = Handler()

    fun submitJob(job: () -&amp;gt; Unit) {
        jobQueue.add(job) if (jobQueue.size == 1) {
            handler.post { processJobs() }
        }
    }

    private fun processJobs() {
        while (!jobQueue.isEmpty() &amp;amp;&amp;amp; !isOverMaxTime) {
            val start =
                System.nanoTime() jobQueue . poll ().invoke() elapsed += System . nanoTime () - start
        } if (jobQueue.isEmpty()) {
            elapsed = 0
        } else if (isOverMaxTime) {
            onNextFrame { elapsed = 0 processJobs () }
        }
    }

    private fun onNextFrame(callback: () -&amp;gt; Unit) =
        Choreographer.getInstance().postFrameCallback { callback() }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 코드는 최대 4ms를 사용한다.&lt;br /&gt;&lt;code&gt;MAX_JOB_TIME_MS&lt;/code&gt; 상수를 바꿔서 원하는 &lt;b&gt;최대 ms를 설정&lt;/b&gt;할 수 있다.&lt;code class=&quot;language-kotlin&quot;&gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AsyncLayoutInflater 를 사용해서 infalte, bind&lt;br /&gt;(Dmitrii Kachan 의 방식)&lt;/h3&gt;
&lt;figure id=&quot;og_1688522297688&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Improve UI Performance with Async RecyclerView Layout Loading&quot; data-og-description=&quot;RecyclerView is one of the most commonly used Android UI components. It can be very powerful but unfortunately it sometimes becomes very&amp;hellip;&quot; data-og-host=&quot;proandroiddev.com&quot; data-og-source-url=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot; data-og-url=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/QmLkX/hyTd92TTdE/kjepOkyCq8KSIkvuz7kCq1/img.jpg?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/cjHLEC/hyTcGgU4gG/0KE5cDtAnKVY1ilCdxrNak/img.jpg?width=759&amp;amp;height=422&amp;amp;face=0_0_759_422&quot;&gt;&lt;a href=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://proandroiddev.com/improve-ui-performance-async-recyclerview-layout-loading-7eb525ab19d0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/QmLkX/hyTd92TTdE/kjepOkyCq8KSIkvuz7kCq1/img.jpg?width=600&amp;amp;height=300&amp;amp;face=0_0_600_300,https://scrap.kakaocdn.net/dn/cjHLEC/hyTcGgU4gG/0KE5cDtAnKVY1ilCdxrNak/img.jpg?width=759&amp;amp;height=422&amp;amp;face=0_0_759_422');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Improve UI Performance with Async RecyclerView Layout Loading&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;RecyclerView is one of the most commonly used Android UI components. It can be very powerful but unfortunately it sometimes becomes very&amp;hellip;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;proandroiddev.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;AsyncLayoutInflater&lt;/code&gt; 를 사용해 &lt;code&gt;onCreateViewHolder&lt;/code&gt; 에서 &lt;code&gt;ViewHolder&lt;/code&gt;를 생성하고,&lt;br /&gt;생성 후 &lt;code&gt;onBindViewHolder&lt;/code&gt;에서 &lt;code&gt;bind&lt;/code&gt;까지 하는 방식&lt;br /&gt;&amp;rarr; 기존에 &lt;code&gt;UI Thread&lt;/code&gt;가 하던 일을 &lt;code&gt;background Thread&lt;/code&gt; 에 맡기는 형식&lt;br /&gt;&amp;rarr; &lt;code&gt;background Thread&lt;/code&gt; 에서 &lt;b&gt;레이아웃 계산&lt;/b&gt;부터 &lt;b&gt;모두 비동기 방식으로 사용이 가능하다&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;open class AsyncCell(context: Context) : FrameLayout(context, null, 0, 0) {
    init {
        layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
    }

    open val layoutId = -1

    // override with your layout Id 
    private var isInflated = false
    private val bindingFunctions: MutableList&amp;lt;AsyncCell.() -&amp;gt; Unit&amp;gt; = mutableListOf()

    fun inflate() {
        AsyncLayoutInflater(context).inflate(layoutId, this) { view, _, _ -&amp;gt;
            isInflated = true addView (createDataBindingView(view)) bindView ()
        }
    }

    private fun bindView() {
        with(bindingFunctions) {
            forEach { it() }
            clear()
        }
    }

    fun bindWhenInflated(bindFunc: AsyncCell.() -&amp;gt; Unit) {
        if (isInflated) {
            bindFunc()
        } else {
            bindingFunctions.add(bindFunc)
        }
    }

    // override for usage with dataBinding
    open fun createDataBindingView(view: View): View? = view
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에 &lt;code&gt;FrameLayout&lt;/code&gt;를 &lt;b&gt;더미로 만들고&lt;/b&gt; &lt;code&gt;addView&lt;/code&gt;를 해서 아래처럼 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class RecyclerViewAsyncAdapter(private val items: List&amp;lt;TestItem&amp;gt;) :
    RecyclerView.Adapter&amp;lt;RecyclerView.ViewHolder&amp;gt;() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
        SmallItemViewHolder(SmallItemCell(parent.context).apply { inflate() })

    override fun getItemCount(): Int = items.size
    override fun onBindViewHolder(
        holder: RecyclerView.ViewHolder,
        position: Int
    ) {
        if (holder is SmallItemViewHolder) {
            setUpSmallViewHolder(holder, position)
        }
    }

    private fun setUpLargeViewHolder(holder: LargeItemViewHolder, position: Int) {
        (holder.itemView as LargeItemCell).bindWhenInflated {
            items[position].let { item -&amp;gt;
                holder.itemView.binding?.item = item
            }
        }
    }

    private fun setUpSmallViewHolder(holder: SmallItemViewHolder, position: Int) {
        (holder.itemView as SmallItemCell).bindWhenInflated {
            items[position].let { item -&amp;gt;
                holder.itemView.binding?.item = item
            }
        }
    }

    private inner class SmallItemViewHolder internal constructor(view: ViewGroup) : RecyclerView.ViewHolder(view)

    private inner class SmallItemCell(context: Context) : AsyncCell(context) {
        var binding: SmallItemCellBinding? = null override
        val layoutId =
            R.layout.small_item_cell override fun createDataBindingView(view: View): View? {
                binding = SmallItemCellBinding.bind(view) return view.rootView
            }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단점으로 복잡한 &lt;code&gt;View&lt;/code&gt;를 가진 아이템에서는 &lt;b&gt;간혹 UI가 제대로 렌더링 못하는 경우&lt;/b&gt;가 있거나, &lt;b&gt;오히려 더 느려지는 문제점&lt;/b&gt;이 생기기도 한다.&lt;br /&gt;&amp;rarr; 마찬가지로 상황에 맞게 사용해야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;ViewHolder&lt;/code&gt; paramter로 넘어가는 &lt;code&gt;View&lt;/code&gt;를 &lt;code&gt;inflate&lt;/code&gt; 해서 생성하지 않고 class로 만들기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;onCreateViewHolder&lt;/code&gt;에서 기본적으로 &lt;code&gt;XML&lt;/code&gt;를 &lt;code&gt;LayoutInflate&lt;/code&gt;로 &lt;code&gt;View&lt;/code&gt;로 &lt;code&gt;inflate&lt;/code&gt; 해서 사용하는게 일반적인 경우&lt;/li&gt;
&lt;li&gt;대신 &lt;code&gt;XML&lt;/code&gt;를 사용하지 않고 &lt;code&gt;ItemView&lt;/code&gt; &lt;b&gt;자체를 class로 직접 만들어 &lt;code&gt;ViewHolder&lt;/code&gt; paramter 로 넘기는 방법&lt;br /&gt;&lt;/b&gt;&amp;rarr; &lt;code&gt;inflate&lt;/code&gt; 을 하지않고 바로 &lt;code&gt;View&lt;/code&gt;를 넘기기 때문에 &lt;code&gt;ViewHolder&lt;/code&gt; 생성 비용을 아낄 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;class ProductCardView : MaterialCardView { 
    ...
}
abstract class MyAdapter : RecyclerView.Adapter&amp;lt;MyAdapter.ViewHolder&amp;gt;() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return RecyclerView.ViewHolder(
            ProductCardView(parent.context)
        )
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 &lt;code&gt;유지보수 측면&lt;/code&gt;에서 &lt;code&gt;XML&lt;/code&gt;에 비해 효율은 좋지 않음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능을 중심으로 코딩 하는 상황이 아니라면 가급적 사용하지 않는 방법.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Compose LazyComposable 사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Compose를 통해 &lt;b&gt;목록형 UI를 만드는 방법&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;늦은 초기화&lt;/code&gt;를 통해 View가 &lt;b&gt;사용자 눈에 보일 때 UI 렌더링&lt;/b&gt; 합니다.&lt;br /&gt;&amp;rarr; 복잡한 Adpater 관리도 사라지며 &lt;code&gt;composable function&lt;/code&gt; 안에서 &lt;b&gt;쉽게 List 관리가 가능&lt;/b&gt;하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XML&lt;/code&gt;과 혼용해 사용할 수 있기 때문에 List부분만 &lt;code&gt;LazyComposable&lt;/code&gt;를 이용해 만드는 것도 하나의 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예시&lt;/h4&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;LazyColumn {

// Add a single item
    item { Text(text = &quot;First item&quot;) }

// Add 5
    items items (5) { index -&amp;gt; Text(text = &quot;Item: $index&quot;) }

// Add another single 
    item item { Text(text = &quot;Last item&quot;) }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IT/Android</category>
      <category>RecyclerView</category>
      <category>안드로이드</category>
      <category>최적화</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/116</guid>
      <comments>https://hodie.tistory.com/116#entry116comment</comments>
      <pubDate>Mon, 10 Jul 2023 11:08:10 +0900</pubDate>
    </item>
    <item>
      <title>[Android] RecyclerView Deep Dive - 1. RecyclerView 정의와 동작원리 및 생명주기</title>
      <link>https://hodie.tistory.com/113</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전에 &lt;code&gt;RecyclerView&lt;/code&gt;에 대한 글을 정리한 적이 있었다. 당시에는, RecyclerView의 등장 의의와 RecyclerView를 사용하는 경우에 구현해야 하는 구현부에 대한 내용을 중심으로 작성했었다.&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://hodie.tistory.com/47&quot;&gt;2022.12.22 - [IT/Android] - [Android] RecyclerView 이해하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;이외에 &lt;code&gt;RecyclerView&lt;/code&gt;에서 사용하는 Adapter를 중심으로 알아봤었다&lt;/li&gt;
&lt;li&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://hodie.tistory.com/48&quot;&gt;2022.12.23 - [IT/Android] - [Android] RecyclerView 갱신의 효율성을 높이는 방법&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;그러나 &lt;code&gt;RecyclerView&lt;/code&gt;를 사용하면서, 근본적인 &lt;code&gt;RecyclerView&lt;/code&gt;에 대한 이해를 필요로 하는 일이 늘어났고 특히, &lt;code&gt;RecyclerView&lt;/code&gt;의 내부 동작에 대한 이해가 필요로 하는 일이 늘어남에 따라 이번에는 RecyclerView의 내부 구조 동작을 중심으로 알아보고자 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목적&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;의 작동 방식 및 LifeCycle을 중심으로 알아볼 예정이다.&lt;/li&gt;
&lt;li&gt;이외에 ListView의 문제점을 비롯한 RecyclerView의 등장 배경과 정의, 성능 향상방법 등을 Google I/0 2016에 소개된 내용을 중심으로 알아볼 예정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RecyclerView의 등장 의의&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;등장 이전: &lt;code&gt;ListView&lt;/code&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;안드로이드에선 &lt;code&gt;List&lt;/code&gt;를 보여주기 위해 &lt;code&gt;ListView&lt;/code&gt;를 사용했었다. 그러나, &lt;code&gt;ListView&lt;/code&gt;에는 고질적인 문제가 존재했다
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;ListView&lt;/code&gt;의 &lt;code&gt;ItemView&lt;/code&gt; 생성속도&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ItemView&lt;/code&gt; 애니메이션 구현의 어려움&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ItemView&lt;/code&gt; 재사용 여부 분기 처리&lt;/li&gt;
&lt;li&gt;변경된 &lt;code&gt;Item Data&lt;/code&gt;의 &lt;code&gt;Position&lt;/code&gt;을 알 수 없음&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ListView 문제점 - 1. &lt;code&gt;ListView&lt;/code&gt;의 &lt;code&gt;ItemView&lt;/code&gt; 생성속도&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대표적인 문제점&lt;/b&gt;으로, 아이템 리스트를 표현하는 &lt;code&gt;ItemView&lt;/code&gt;의 개수가 &lt;b&gt;수백개 이상으로 많아지는 경우&lt;/b&gt;, &lt;code&gt;ListView&lt;/code&gt;가 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;code&gt;ItemView&lt;/code&gt;를 생성하는 속도가 매우 느려진다&lt;/b&gt;&lt;/span&gt;는 것이었다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;이에 따라&lt;/b&gt; 구글은 내부적으로 &lt;code&gt;Adapter&lt;/code&gt;라는 것을 사용해, &lt;b&gt;당장 눈에 보이는&lt;/b&gt; &lt;code&gt;ItemView&lt;/code&gt;&lt;b&gt;만 생성&lt;/b&gt;하는 대응책을 내놓았고, 눈에 안보이던 새로운 &lt;code&gt;ItemView&lt;/code&gt;가 눈에 보여져야 할 때 &lt;b&gt;기존에 생성했던&lt;/b&gt; &lt;code&gt;ItemView&lt;/code&gt;&lt;b&gt;를 그대로 재사용한다&lt;/b&gt;는 &lt;code&gt;속임수&lt;/code&gt;를 사용했었다. (&lt;b&gt;&lt;code&gt;ViewType&lt;/code&gt;이 동일한 경우에만&lt;/b&gt;)&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;code&gt;ListView&lt;/code&gt;가 &lt;code&gt;ItemView&lt;/code&gt;를 재사용하지 않고 한번에 그리기 때문에 속도가 느리다 는 말은 &lt;code&gt;ListView&lt;/code&gt;의 &lt;code&gt;Adapter&lt;/code&gt;라는 &lt;b&gt;재사용 메커니즘을 사용하지 않았을 때 적용되는 말&lt;/b&gt;이다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp;&amp;rarr; 즉, &lt;code&gt;ListView&lt;/code&gt;도 &lt;code&gt;View&lt;/code&gt;를 재활용 할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;문제는, 이 재사용에 의해 후술할 &lt;u&gt;애니메이션 처리&lt;/u&gt;라는 문제점이 생겨났다.&lt;/li&gt;
&lt;li&gt;이러한 문제들을 비롯해 &lt;code&gt;ListView&lt;/code&gt;에 대한 개발자들의 요구사항이 점점 커지고, &lt;code&gt;ListView API&lt;/code&gt;가 &lt;b&gt;지나치게 복잡해짐에 따라 오작동을 일으키는 경우가 빈번&lt;/b&gt;해지기 시작했다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ListView 문제점 - 2. &lt;code&gt;ItemView&lt;/code&gt; 애니메이션 구현의 어려움&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;또한 &lt;code&gt;ListView&lt;/code&gt;가 가진 가장 큰 문제는, &lt;code&gt;ItemView&lt;/code&gt;에 애니메이션을 구현하는 것이 너무 어렵다는 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;ItemView&lt;/code&gt;를 제거하는 애니메이션&lt;/b&gt;을 구현한 경우, &lt;code&gt;ItemView&lt;/code&gt;를 하나씩 클릭하여 제거했을 경우에는 원하는 대로 작동하는 것 같았지만 여러 개의 &lt;code&gt;ItemView&lt;/code&gt;를 클릭한 후 빠르게 스크롤하면 몇몇 &lt;code&gt;ItemView&lt;/code&gt;가 &lt;b&gt;아무런 데이터도 없는 상태로 비어있는 현상이 발생&lt;/b&gt;하게 된다.&lt;br /&gt;(&lt;code&gt;ItemView&lt;/code&gt;의 애니메이션이 진행되는 동안 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;재사용 대상으로 선택 됐기때문이다&lt;/b&gt;&lt;/span&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXLGbT/btsmrSz81S3/Yf1D2zgNM2g3Fw9uDSAab1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXLGbT/btsmrSz81S3/Yf1D2zgNM2g3Fw9uDSAab1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXLGbT/btsmrSz81S3/Yf1D2zgNM2g3Fw9uDSAab1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bXLGbT/btsmrSz81S3/Yf1D2zgNM2g3Fw9uDSAab1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;548&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;애니메이션 효과가 완전히 끝나면 이 &lt;code style=&quot;letter-spacing: 0px;&quot;&gt;ItemView&lt;/code&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;에 해당하는 &lt;/span&gt;&lt;b&gt;객체가 메모리 상에서 제거&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;되지만, 이 &lt;/span&gt;&lt;b&gt;객체는 재사용되기로 약속된 객체&lt;/b&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이기 때문에 다시 사용되어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;하지만 &lt;b&gt;애니메이션으로 인해 메모리 상에서 제거&lt;/b&gt;된 경우, &lt;code&gt;ListView&lt;/code&gt;가 새로운 데이터를 세팅할 &lt;b&gt;재사용 &lt;code&gt;ItemView&lt;/code&gt;를 메모리에서 찾지 못하게&lt;/b&gt; 되는데 이를 &lt;b&gt;`&lt;span style=&quot;color: #ee2323;&quot;&gt;ListView의&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt; &lt;b&gt;붕괴 현상&lt;/b&gt;&lt;/span&gt;`이라고 부른다.&lt;/li&gt;
&lt;li&gt;&amp;rarr; 현재는 &lt;u&gt;&lt;code&gt;&lt;b&gt;ListView&lt;/b&gt;&lt;/code&gt;의 붕괴 현상&lt;/u&gt;을 해결하기 위해 안드로이드가 제공하는 &lt;code&gt;ViewPropertyAnimator&lt;/code&gt; API를 사용하면 된다. 이것을 사용하면 애니메이션 효과가 진행 중인 &lt;code&gt;ItemView&lt;/code&gt;&lt;b&gt;는 작업이 진행중인 객체로 인식되어 재사용 대상으로 선택되지 않게 된다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ListView 문제점 - 3. &lt;code&gt;ItemView&lt;/code&gt; 재사용 여부 분기 처리&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이외에 &lt;code&gt;ListView&lt;/code&gt;를 구현할 때는 &lt;b&gt;개발자가 직접 &lt;code&gt;ItemView&lt;/code&gt;를 생성하는 로직(onCreate)과 데이터를 연결하는 로직(onBind)을 분리해서 코드를 작성&lt;/b&gt;해야 했다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;따라서 개발자들은 if 조건문을 통해 &lt;b&gt;재사용될 &lt;code&gt;ItemView&lt;/code&gt;가 존재하면&lt;/b&gt; 바로 &lt;b&gt;데이터를 재사용될 &lt;code&gt;ItemView&lt;/code&gt;에 연결&lt;/b&gt;하고,&lt;br /&gt;&lt;b&gt;재사용될 &lt;code&gt;ItemView&lt;/code&gt;가 존재하지 않으면(&lt;code&gt;null&lt;/code&gt;)&lt;/b&gt;&amp;nbsp;새로운 &lt;code&gt;ItemView&lt;/code&gt;를 생성하는 코드를 추가해야 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문제는 이 로직 분리 작업을 까먹는 경우도 많았고 이로 인해 &lt;code&gt;ListView&lt;/code&gt;의 &lt;code&gt;ItemView&lt;/code&gt; 재사용 기능이 동작하지 않게 돼 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;앱의 성능을 악화&lt;/b&gt;&lt;/span&gt;시키는 원인이 되기도 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ListView 문제점 - 4. 변경된 &lt;code&gt;Item Data&lt;/code&gt;의 &lt;code&gt;Position&lt;/code&gt;을 알 수 없음&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ListView&lt;/code&gt;에 보여질 데이터가 바뀐 경우, &lt;b&gt;기존의 ListView의 Adapter는 데이터가 변경되었다는 사실만 알 수 있을 뿐 구체적으로 목록에서 몇 번째 데이터가 변경되었는지 인식할 수 없었다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어, 사용자의 눈에 보이고 있는 &lt;code&gt;ItemView&lt;/code&gt; 중 &lt;code&gt;3번째 ItemView&lt;/code&gt;에 연결된 &lt;b&gt;데이터가 어떠한 처리에 의해 바뀌게 되면&lt;/b&gt; &lt;code&gt;Adapter&lt;/code&gt;는 &lt;b&gt;데이터가 바뀌었다는 사실만 알 뿐 &lt;code&gt;몇 번째 ItemView&lt;/code&gt;에 연결된 데이터를 바꿔야 하는지는 알 수 없었다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;따라서 &lt;code&gt;3번째 ItemView&lt;/code&gt;의 데이터를 바꾸기 위해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;모든 ItemView에 데이터를 다시 연결해야 했다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; 이러한 문제는 애니메이션 처리를 더욱 어렵게 만드는 원인이 되기도 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;위와 같은 문제들이 발생하자 Google Android API 개발팀은 &lt;b&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LqBlYJTfLP4&quot;&gt;Google I/O 2016&lt;/a&gt;&lt;/b&gt;에서 기존 &lt;code&gt;ListView&lt;/code&gt; 설계에 실수가 있었음을 인지하고 이 실수를 반복하지 않고자 &lt;code&gt;RecyclerView&lt;/code&gt;를 개발했다고 발표하였다&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@dabin/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9CRecycle-View#2%EA%B0%80%EC%A7%80-%EC%86%8D%EC%9E%84%EC%88%98%EA%B0%80-%ED%95%B4%EA%B2%B0%ED%95%98%EC%A7%80-%EB%AA%BB%ED%95%9C-%EA%B2%83---4-%EB%B3%80%EA%B2%BD%EB%90%9C-item-data%EC%9D%98-position%EC%9D%84-%EC%95%8C-%EC%88%98-%EC%97%86%EC%9D%8C&quot;&gt;요약&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ListView&lt;/code&gt;는 한번에 &lt;code&gt;ItemView&lt;/code&gt;를 그리기 어려웠음&lt;br /&gt;&amp;rarr; 속임수 1) 눈에 보이는 ItemView만 생성&lt;br /&gt;&amp;rarr; 속임수 2) 맨위에 있던 ItemView를 그려져야할 위치에 재사용&lt;br /&gt;&amp;rarr; 문제 1) 개발자들 요구사항 증가&lt;br /&gt;&amp;rarr; 문제 2) 다른 뷰에서 제공하는 비슷한 기능이 ListView에 추가되며 혼란 &amp;rarr; 문제3)애니메이션 처리 문제&lt;br /&gt;&amp;rarr; 문제 3) 재사용될 ItemView인지 분기처리 필요&lt;br /&gt;&amp;rarr; 문제 4) Adapter는 변경된 Item Data의 Position 알 수 없음&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;rArr; RecyclerView 등장&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RecyclerView의 정의&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;는 안드로이드 프레임워크에서 제공하는 View 레이아웃 중 하나이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;는 사용자가 관리하는 많은 수의 &lt;b&gt;데이터 집합(Data Set)&lt;/b&gt;을 &lt;b&gt;개별 아이템 단위로 구성해&lt;/b&gt; 화면에 출력하는 &lt;b&gt;&lt;code&gt;뷰그룹&lt;/code&gt;(ViewGroup)&lt;/b&gt;이며, 한 화면에 표시되기 힘든 많은 수의 데이터를 &lt;b&gt;스크롤 가능한 리스트로 표시해주는 &lt;code&gt;위젯&lt;/code&gt;이다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RecycleView&lt;/code&gt;는 &lt;b&gt;Android Jetpack 구성 요소 중 하나&lt;/b&gt;이기 때문에 &lt;code&gt;안드로이드 프레임워크&lt;/code&gt;에서 라이브러리 형식으로 제공하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 개발자는 이 라이브러리를 가져다 사용해서 원하는 모양대로 응용하기만 하면 되는 것이다&lt;/p&gt;
&lt;h1&gt;RecyclerView의 성능 향상 방법&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewHolder 패턴&lt;/code&gt;을 통해 &lt;code&gt;ListView&lt;/code&gt;의 성능을 &lt;code&gt;RecyclerView&lt;/code&gt;에서 크게 향상할 수 있었다. 재활용하는 &lt;code&gt;View&lt;/code&gt;들의 클래스를 &lt;code&gt;View 태그&lt;/code&gt; 또는 &lt;code&gt;Array&lt;/code&gt;에 저장하고, 필요할 때 바로 가져와서 사용하는 방법으로 성능을 크게 향상하였다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ListView&lt;/code&gt;에서 재활용되는 &lt;code&gt;View&lt;/code&gt;를 해당 포지션에 맞게 가져오는 곳에서 성능을 향상할 수 있었다면,&lt;br /&gt;&lt;code&gt;RecyclerView&lt;/code&gt;는 가져온 &lt;code&gt;View&lt;/code&gt;에 데이터를 &lt;code&gt;Bind&lt;/code&gt;하는 경우 최적화할 수 있는 방법을 제공하고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RecyclerView의 동작 원리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;b&gt;내부 아키텍처&lt;/b&gt;는 &lt;b&gt;컴포넌트 기반의 아키텍처&lt;/b&gt;이며, 내부 아키텍처를 구성하는 컴포넌트는 &lt;code&gt;RecyclerView&lt;/code&gt;, &lt;code&gt;LayoutManager&lt;/code&gt;, &lt;code&gt;ItemAnimator&lt;/code&gt;, &lt;code&gt;Adapter&lt;/code&gt; 등이며, 이 중 아래의 &lt;b&gt;3가지 컴포넌트가 가장 중요&lt;/b&gt;하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트 기반 아키텍처: 이렇게 &lt;code&gt;RecyclerView&lt;/code&gt;, &lt;code&gt;LayoutManager&lt;/code&gt;, &lt;code&gt;ItemAnimator&lt;/code&gt;, &lt;code&gt;Adapter&lt;/code&gt; 등의 &lt;code&gt;컴포넌트&lt;/code&gt;가 &lt;b&gt;서로 상호작용하여 하나의 목록형 레이아웃을 동작하게끔 만들기 때문에 컴포넌트 기반 아키텍처&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czNtje/btsmnBzbAU6/kFYOZRPW8QmHf5yWjBGrq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czNtje/btsmnBzbAU6/kFYOZRPW8QmHf5yWjBGrq0/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czNtje/btsmnBzbAU6/kFYOZRPW8QmHf5yWjBGrq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczNtje%2FbtsmnBzbAU6%2FkFYOZRPW8QmHf5yWjBGrq0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;281&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 &lt;b&gt;3가지 컴포넌트가 적절하게 상호작용&lt;/b&gt;해야 &lt;code&gt;ItemView&lt;/code&gt;를 올바르게 &lt;code&gt;RecyclerView&lt;/code&gt;안에 배치할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. LayoutManager&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LayoutManager 정의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;LayaoutManager&lt;/code&gt;은 &lt;code&gt;RecyclerView&lt;/code&gt;가 Item을 화면에 표시할 때, &lt;code&gt;ItemVIew&lt;/code&gt;들이 &lt;code&gt;RecyclerView&lt;/code&gt; 내부에서 배치되는 형태를 관리하는 컴포넌트이다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;LayoutManager&lt;/code&gt;는 &lt;code&gt;선형&lt;/code&gt;, &lt;code&gt;그리드&lt;/code&gt;, &lt;code&gt;엇갈린 그리드&lt;/code&gt; 모양으로 &lt;code&gt;RecyclerView&lt;/code&gt;의 모습이 보여지도록 하는 작업을 담당하는 &lt;code&gt;컴포넌트&lt;/code&gt;이다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Layoutmanager&lt;/code&gt;는 각각 &lt;code&gt;LinearLayoutManager&lt;/code&gt;(선형), &lt;code&gt;GridlayoutManager&lt;/code&gt;(그리드), &lt;code&gt;StaggeredLayoutManager&lt;/code&gt;(크기가 각각인 엇갈린 그리드)로 분류된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉, &lt;code&gt;RecyclerView&lt;/code&gt;는 자신이 &lt;b&gt;어떤 모습으로 그려질지는 모르&lt;/b&gt;며, 오직 &lt;code&gt;&lt;b&gt;LayoutManager&lt;/b&gt;&lt;/code&gt;&lt;b&gt;가 담당하며,&lt;/b&gt;&amp;nbsp;&lt;code&gt;RecyclerView&lt;/code&gt;의 모양을 만드는 작업을 책임지고, &lt;code&gt;RecyclerView&lt;/code&gt; 모양에 따라 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;code&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ItemView&lt;/span&gt;&lt;/b&gt;&lt;/code&gt;&lt;b&gt;를 적절한 위치에 배치하는 작업을 담당&lt;/b&gt;&lt;/span&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;LayoutManager가 하는일&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) 만약 유저가 리스트를 더 보기 위해 &lt;code&gt;RecyclerView&lt;/code&gt;를 위로 스크롤 했다고 가정해보는 경우,&lt;/li&gt;
&lt;li&gt;(2) &lt;code&gt;RecyclerView&lt;/code&gt;는 새로운 &lt;code&gt;ItemView&lt;/code&gt;를 보여줘야한다는 것을 인식한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IldDb/btsmsRf6gGh/Q6E8BrFJCF7C2HVJjCU0Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IldDb/btsmsRf6gGh/Q6E8BrFJCF7C2HVJjCU0Kk/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IldDb/btsmsRf6gGh/Q6E8BrFJCF7C2HVJjCU0Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIldDb%2FbtsmsRf6gGh%2FQ6E8BrFJCF7C2HVJjCU0Kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;368&quot; height=&quot;442&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(3) 하지만 새로운 &lt;code&gt;ItemView&lt;/code&gt;를 &lt;b&gt;어디에 배치할 지는 &lt;code&gt;LayoutMananger&lt;/code&gt;가 알고 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;(4) 그러므로, 위 상황에서 &lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;code&gt;LayoutManager&lt;/code&gt;에게 &lt;b&gt;새로운 ItemView를 보여달라고 메시지를 전달&lt;/b&gt;한다.&lt;br /&gt;(5) 그럼 &lt;code&gt;LayoutManager&lt;/code&gt;는 &lt;b&gt;적절한 위치에 데이터가 연결된 ItemView를 연결&lt;/b&gt;하게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Adapter&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;에서도 &lt;code&gt;ListView&lt;/code&gt;와 동일하게 &lt;b&gt;&lt;code&gt;Adapter&lt;/code&gt;에 의존한다.&lt;/b&gt;&lt;br /&gt;&lt;b&gt;(다른 adapter처럼 뷰와 데이터를 Bind)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;하지만 &lt;code&gt;ListView Adapter&lt;/code&gt;와의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;차이점&lt;/b&gt;&lt;/span&gt;은 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;code&gt;ItemView&lt;/code&gt;&lt;/span&gt; 생성 외에도&lt;/b&gt; &lt;u&gt;&lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ViewHolder&lt;/b&gt;&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;라는 것을 생성하는 작업을 담당&lt;/b&gt;&lt;/span&gt;&lt;/u&gt;한다는 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;code&gt;ViewHolder&lt;/code&gt;를 기본적으로 사용하기 때문에, &lt;code&gt;View&lt;/code&gt;를 생성하지 않고, &lt;code&gt;inflated&lt;/code&gt;(XML의 레이아웃이 메모리에 객체화된)&lt;code&gt;뷰&lt;/code&gt;를 갖는 &lt;code&gt;ViewHolder&lt;/code&gt;를 생성하게 된다.&lt;br /&gt;&amp;rarr; &lt;code&gt;ViewHolder&lt;/code&gt;가 생성되어 캐시가 쌓이면 필요할 때 재사용된다&lt;/li&gt;
&lt;li&gt;이 때 기억해야 할 점은 &lt;code&gt;ViewHolder&lt;/code&gt;는 &lt;code&gt;position&lt;/code&gt;이 아닌 &lt;code&gt;itemViewType&lt;/code&gt;에 의해 생성되는 것이다.&lt;br /&gt;&amp;rarr; 이렇게 함으로써 &lt;b&gt;재사용할 뷰를 더 쉽게 찾고 쉽게 추가&lt;/b&gt;할 수 있게된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Adapter가 하는 일&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;View&lt;/code&gt;와 &lt;code&gt;ViewHolder&lt;/code&gt;를 &lt;b&gt;만든다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ViewHolder&lt;/code&gt;에 아이템을 &lt;b&gt;바인드&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;에게 &lt;b&gt;데이터셋이 변경 되었을 때 이를 알린다&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;각각 &lt;b&gt;데이터의 변경 이벤트를 처리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아이템의 상호작용&lt;/b&gt;(ex: 클릭)을 처리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ItemView&lt;/code&gt;의 &lt;code&gt;ViewType&lt;/code&gt;이 &lt;b&gt;여러 가지일 경우를 처리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;재활용 실패에 대한 수습&lt;/b&gt;&lt;/span&gt;을 처리 (&lt;code&gt;onFailedToRecyclerView&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;onBindViewHolder()&lt;/code&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder()&lt;/code&gt;는 &lt;code&gt;parameter&lt;/code&gt;로 &lt;code&gt;ViewHolder&lt;/code&gt; 객체와 &lt;code&gt;position&lt;/code&gt; 값을 받고 Return 타입은 없다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;position&lt;/code&gt; 값은 &lt;code&gt;Adapter&lt;/code&gt; 클래스 property 인 &lt;b&gt;데이터셋의 특정 포지션이다&lt;/b&gt;.&lt;br /&gt;예를 들어 데이터 셋이 배열 자료구조로 구성되었으면 &lt;code&gt;position&lt;/code&gt;은 배열의 특정 &lt;code&gt;index&lt;/code&gt;인 것이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ViewHolder&lt;/code&gt;객체는 데이터 셋의 특정 &lt;code&gt;position&lt;/code&gt;에 저장되어 있는 아이템을 보여주기 위해 &lt;b&gt;업데이트 되어야하는 &lt;code&gt;ViewHolder&lt;/code&gt;&lt;/b&gt;이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder()&lt;/code&gt; 메소드는 &lt;code&gt;특정 position&lt;/code&gt;의 데이터(item)을 보여주기 위해 &lt;code&gt;RecyclerView&lt;/code&gt;가 호출하는 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;즉 호출하는 주체가 &lt;/b&gt;&lt;code&gt;&lt;b&gt;RecyclerView&lt;/b&gt;&lt;/code&gt;인데 &lt;code&gt;RecyclerView&lt;/code&gt;는 내부의 &lt;code&gt;tryBindViewHolderByDeadLine()&lt;/code&gt;이라는 메서드 내부에서 &lt;code&gt;mAdapter.bindViewHolder()&lt;/code&gt;를 호출한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;bindViewHolder()&lt;/code&gt;에서 &lt;code&gt;onBindViewHolder()&lt;/code&gt;가 호출된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉 &lt;code&gt;RecyclerView&lt;/code&gt;가 &lt;b&gt;특정 상황이 발생&lt;/b&gt;했을 때 이를 알리기 위해 &lt;code&gt;bindViewHolder&lt;/code&gt;를 호출하고,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이 알림을 받았을때 해야하는 작업&lt;/b&gt;을 우리가 &lt;code&gt;Adapter&lt;/code&gt;내에 &lt;code&gt;onBindViewHolder()&lt;/code&gt;메소드를 오버라이딩해 &lt;b&gt;함수 내부&lt;/b&gt;에서는 &lt;code&gt;RecyclerView.ViewHolder.itemView&lt;/code&gt;의 &lt;u&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;컨텐츠를 업데이트하는 작업이 실행된다&lt;/b&gt;.&lt;/span&gt;&lt;/u&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;rarr; 업데이트만하고 반환되는 작업이 없기에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;onBindViewHolder()&lt;/span&gt;&lt;/code&gt;&lt;b&gt;는 콜백함수&lt;/b&gt;&lt;/span&gt;라는 걸 알 수 있다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewHolder의 LifeCycle&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewHolder&lt;/code&gt;의 &lt;code&gt;LifeCycle&lt;/code&gt;는 대부분의 개발자들은 &lt;code&gt;ViewHolder&lt;/code&gt;를 작성하는 것에 시간을 많이 쓰기 때문에, 아는 것이 매우 중요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewHolder Lifecycle - 1. Birth&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; background-color: #faecde;&quot;&gt;&lt;b&gt;1. 요구하는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ItemView&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;가 캐시에 저장되어 있을 경우&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7bCQb/btsmsODGvy4/CtqDrrR91kubqsBlgFXDI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7bCQb/btsmsODGvy4/CtqDrrR91kubqsBlgFXDI0/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7bCQb/btsmsODGvy4/CtqDrrR91kubqsBlgFXDI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7bCQb%2FbtsmsODGvy4%2FCtqDrrR91kubqsBlgFXDI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;518&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;(When) 유저가 스크롤 이벤트를 발생시&lt;/b&gt;&lt;br /&gt;&lt;code&gt;RecyclerView&lt;/code&gt;가 &lt;code&gt;LayoutManager&lt;/code&gt;에게 새로운 &lt;code&gt;ItemView&lt;/code&gt;의 위치를 요구한다.&lt;/li&gt;
&lt;li&gt;(1) &lt;code&gt;LayoutManager&lt;/code&gt;는 메시지를 받고 &amp;rarr; 어떤 포지션에 새로운 &lt;code&gt;ItemView&lt;/code&gt;가 배치되어야 하는지를 계산하고&lt;br /&gt;(2) 이 위치를 다시 &lt;code&gt;RecyclerView&lt;/code&gt;에게 알리는데,&lt;br /&gt;(3) 위치를 보내주고 이 위치에 배치할 &lt;code&gt;ItemView&lt;/code&gt;를 요청한다.&lt;br /&gt;(4) &lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;캐시에 해당 포지션에 배치되도록 지정된 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ItemView&lt;/span&gt;&lt;/code&gt;&lt;/span&gt;가 있는지를 확인&lt;/b&gt;&lt;/span&gt;한다&lt;br /&gt;(&lt;code&gt;recyclerview.getViewForPosition()&lt;/code&gt;)&lt;br /&gt;(5) 만약 이 포지션에 배치되어야 하는 &lt;b&gt;&lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ItemVie&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;w&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;가 캐시되어 있을 경우&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&amp;rarr; 이 &lt;code&gt;ItemView&lt;/code&gt;를 받아서 다시 &lt;code&gt;LayoutManager&lt;/code&gt;에게 전달한다.&lt;br /&gt;(&lt;code&gt;Recyclerview&lt;/code&gt; 내부 동작 원리 상 일정 양의 ItemView를 캐시에 저장해 놓기 때문)&lt;/li&gt;
&lt;/ul&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #faecde; color: #000000;&quot;&gt;&lt;b&gt;2. 요구하는&lt;b&gt;&amp;nbsp;ItemView가&amp;nbsp;&lt;/b&gt;캐시에 없을 경우&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2hlP/btsmmQJTwzO/awvKUDtOHrVKc9jxMWNqO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2hlP/btsmmQJTwzO/awvKUDtOHrVKc9jxMWNqO1/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2hlP/btsmmQJTwzO/awvKUDtOHrVKc9jxMWNqO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2hlP%2FbtsmmQJTwzO%2FawvKUDtOHrVKc9jxMWNqO1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;518&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;(4) Cache&lt;/b&gt;에 저장된 &lt;code&gt;ItemView&lt;/code&gt;가 없다는 메시지가 날아오면&lt;br /&gt;(5) &lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;code&gt;Adapter&lt;/code&gt;에게(&lt;code&gt;RecyclerView &amp;rarr; Adapter&lt;/code&gt;)&lt;br /&gt;해당하는 &lt;code&gt;ItemView&lt;/code&gt;의 &lt;code&gt;ViewType&lt;/code&gt;을 &lt;code&gt;getViewType&lt;/code&gt; 메소드를 통해 물어본다 (&lt;code&gt;adapter.getViewType()&lt;/code&gt;)&lt;br /&gt;(6) &lt;code&gt;Adapter&lt;/code&gt;는 해당하는 &lt;code&gt;ViewType&lt;/code&gt;을 알려주고&lt;br /&gt;(7) &lt;code&gt;RecyclerView&lt;/code&gt;는 이 &lt;code&gt;ViewType&lt;/code&gt;을 가지고 &lt;code&gt;Recycled Pool&lt;/code&gt;에서 &lt;b&gt;해당하는 ViewHolder가 있는지 물어본다&lt;/b&gt; (&lt;code&gt;getViewHolderByType&lt;/code&gt; 메소드)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 사용하는 &lt;code&gt;Recycled Pool&lt;/code&gt;은 &lt;b&gt;공유되는 Recycled Pool일 수도 있고&lt;/b&gt;,&lt;br /&gt;오직 &lt;b&gt;이 &lt;code&gt;RecyclerView&lt;/code&gt;에서만 사용하는 유일한 &lt;code&gt;Recycled Pool&lt;/code&gt;일 수도 있다&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #faecde; color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;3. Recycled Pool에 ViewHolder가 존재하지 않는 경우&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/opcQn/btsmuo5AJgX/6McVJxQ8KiELQEZ7eJsxD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/opcQn/btsmuo5AJgX/6McVJxQ8KiELQEZ7eJsxD0/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/opcQn/btsmuo5AJgX/6McVJxQ8KiELQEZ7eJsxD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FopcQn%2Fbtsmuo5AJgX%2F6McVJxQ8KiELQEZ7eJsxD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;459&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(8) &lt;code&gt;ViewType&lt;/code&gt;에 대한 &lt;code&gt;ViewHolder&lt;/code&gt;가 없을 경우&lt;br /&gt;(9) &lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;code&gt;Adapter&lt;/code&gt;에 새로운 &lt;code&gt;ViewHolder&lt;/code&gt;를 생성하라는 지시를 내리고&lt;br /&gt;(&lt;code&gt;createViewHolder&lt;/code&gt;)&lt;br /&gt;(10) &lt;code&gt;Adapter&lt;/code&gt;는 생성한 &lt;code&gt;ViewHolder&lt;/code&gt;를 &lt;code&gt;RecyclerView&lt;/code&gt;에 전달&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #faecde; color: #000000;&quot;&gt;&lt;b&gt;4. &lt;code&gt;ViewHolder&lt;/code&gt;가 존재하는 경우 (이미 생성된 경우)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHGup0/btsmnDqef54/rqW5hmQmdKTgbjEbMssgo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHGup0/btsmnDqef54/rqW5hmQmdKTgbjEbMssgo1/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHGup0/btsmnDqef54/rqW5hmQmdKTgbjEbMssgo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHGup0%2FbtsmnDqef54%2FrqW5hmQmdKTgbjEbMssgo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;459&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Recycled Pool&lt;/code&gt;에 해당 &lt;code&gt;ViewType&lt;/code&gt;을 위한 해당하는 &lt;code&gt;ViewHolder&lt;/code&gt;가 존재하는 경우&lt;br /&gt;(8) &lt;code&gt;Recycled Pool&lt;/code&gt;은 &lt;code&gt;ViewHolder&lt;/code&gt;를 &lt;code&gt;RecyclerView&lt;/code&gt;에 전달하고&lt;br /&gt;(9) &lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;code&gt;Adapter&lt;/code&gt;에게 &lt;code&gt;position&lt;/code&gt;과 &lt;code&gt;ViewHolder&lt;/code&gt;를 넘겨주며 &lt;code&gt;bind해달라는 명령&lt;/code&gt;을 내린다.&lt;br /&gt;(10) &lt;code&gt;Adapter&lt;/code&gt;는 bind 작업이 완료된 &lt;code&gt;ItemView&lt;/code&gt;를 &lt;code&gt;RecyclerView&lt;/code&gt;에 전달하고&lt;br /&gt;(11) &lt;code&gt;RecyclerView&lt;/code&gt;는 이 &lt;code&gt;ItemView&lt;/code&gt;를 &lt;code&gt;&lt;b&gt;LayoutManager&lt;/b&gt;&lt;/code&gt;&lt;b&gt;에게 최종적으로 전달한다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #faecde; color: #000000;&quot;&gt;&lt;b&gt;&lt;b&gt;5. 마지막 단계&lt;/b&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yCzkB/btsmsz09dhZ/JmaS9VwJWFLXiNrMzWDGu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yCzkB/btsmsz09dhZ/JmaS9VwJWFLXiNrMzWDGu0/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yCzkB/btsmsz09dhZ/JmaS9VwJWFLXiNrMzWDGu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyCzkB%2Fbtsmsz09dhZ%2FJmaS9VwJWFLXiNrMzWDGu0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;459&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(12) 마지막으로 LayoutManager는 해당 위치에 ItemView를 배치하고 RecyclerView에게 배치가 완료되었다는 메시지를 보낸다 (addView())&lt;br /&gt;(13) 그러면 RecyclerView는 Adapter에게 해당하는 position에 ItemView가 잘 배치되었다는 메시지를 보낸다 (onViewAttachedToWindow())&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 결국 &lt;b&gt;전체 과정을 살펴보면&lt;/b&gt; &lt;code&gt;Layout Manager&lt;/code&gt;&lt;b&gt;가 어떤 position에 배치될&lt;/b&gt; &lt;code&gt;ItemView&lt;/code&gt;&lt;b&gt;를 요청할 경우 해당&lt;/b&gt; &lt;code&gt;ItemView&lt;/code&gt;&lt;b&gt;가 Cache에 저장되어 있다면&lt;/b&gt; &lt;code&gt;Adapter&lt;/code&gt;&lt;b&gt;를 거치지 않고 바로&lt;/b&gt; &lt;code&gt;Layout Manager&lt;/code&gt;&lt;b&gt;에게 해당&lt;/b&gt; &lt;code&gt;ItemView&lt;/code&gt;&lt;b&gt;를 전달&lt;/b&gt;할 수 있음을 알 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://velog.io/@dabin/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0-RecyclerView%EC%9D%98-%EB%AA%A8%EB%93%A0-%EA%B2%83-2%ED%83%84ViewHolder%EC%88%98%EB%AA%85%EC%A3%BC%EA%B8%B0#viewholder%EC%9D%98-%EC%83%9D%EC%84%B1&quot;&gt;요약&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 레이아웃 매니저가 getViewForPosition 으로 view를 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) &lt;code&gt;RecyclcerView&lt;/code&gt;는 캐시에 &lt;code&gt;getViewForPosition&lt;/code&gt; 으로 확인. 있으면 &lt;code&gt;LayoutManager&lt;/code&gt;에게 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 캐시에 없으면 &lt;code&gt;adapter&lt;/code&gt;에게 &lt;code&gt;type&lt;/code&gt;이 뭔지 물어보고 &lt;code&gt;Recycled Pool&lt;/code&gt;에 &lt;code&gt;getViewHolderByType&lt;/code&gt;으로 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) &lt;code&gt;Pool&lt;/code&gt;에 &lt;b&gt;있으면&lt;/b&gt; 반환, &lt;b&gt;없으면&lt;/b&gt; &lt;code&gt;adapter&lt;/code&gt;에게 &lt;code&gt;createViewHolder&lt;/code&gt;로 아이템 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) &lt;code&gt;View&lt;/code&gt;를 찾으면 &lt;code&gt;adapter&lt;/code&gt;에서 &lt;code&gt;bindview&lt;/code&gt;를 하고 &lt;code&gt;LayoutManager&lt;/code&gt;에게 Return.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) &lt;code&gt;LayoutManager&lt;/code&gt;는 &lt;code&gt;RecyclerView&lt;/code&gt;에게 &lt;code&gt;addView&lt;/code&gt;를 수행하고 &lt;code&gt;adapter&lt;/code&gt;의 &lt;code&gt;onViewAttachedToWindow&lt;/code&gt; 가 호출됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;캐시&lt;/code&gt;와 &lt;code&gt;Recycled Pool&lt;/code&gt;은 &lt;code&gt;RecyclerView&lt;/code&gt;안에 이미 선언되어있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;setItemViewCacheSize&lt;/code&gt;함수로 캐시 사이즈 변경 가능.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewHolder Lifecycle - 2. Recycle &lt;b&gt;(ItemView를 캐시에 저장하는 원리)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewHolder&lt;/code&gt;의 Birth한 다음의 재활용 과정&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #faecde; color: #000000;&quot;&gt;&lt;b&gt;사용자가 스크롤을 위로 올려 기존의 &lt;code&gt;ItemView&lt;/code&gt;가 화면에서 사라지는 경우 일어나는 일&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coC1sf/btsmsbsoK2f/dHwKPYl9rHypy94nyDK6Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coC1sf/btsmsbsoK2f/dHwKPYl9rHypy94nyDK6Ak/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coC1sf/btsmsbsoK2f/dHwKPYl9rHypy94nyDK6Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoC1sf%2FbtsmsbsoK2f%2FdHwKPYl9rHypy94nyDK6Ak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;801&quot; height=&quot;511&quot; data-origin-width=&quot;801&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) &lt;code&gt;LayoutManager&lt;/code&gt;가 화면에서 벗어난 &lt;code&gt;ItemView&lt;/code&gt;의 position을 계산하고 &lt;code&gt;RecyclerView&lt;/code&gt;에게 이를 알린다 (&lt;code&gt;removeAndRecycleView&lt;/code&gt;)그러면&lt;br /&gt;(2) &lt;code&gt;RecyclerView&lt;/code&gt;는 화면에서 &lt;code&gt;ItemView&lt;/code&gt;를 제거한 후 &lt;code&gt;Adapter&lt;/code&gt;에게 이를 알리게 되고, (&lt;code&gt;onViewDetachedFromWindow()&lt;/code&gt;) &amp;rarr; &lt;code&gt;ItemView&lt;/code&gt; 안에 있는 것들의 &lt;b&gt;캐싱을 해제할 수 있게 된다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;(3) &lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;b&gt;Cache에게&lt;/b&gt; 제거되는 position의 &lt;code&gt;ItemView&lt;/code&gt;가 캐시에 계속 남아있어도 되는 것인지를 &lt;b&gt;Cache에게 물어본다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약, &lt;b&gt;이 &lt;code&gt;ItemView&lt;/code&gt;가 사용된 지 오래된 &lt;code&gt;ItemView&lt;/code&gt;&lt;/b&gt;라면,&lt;br /&gt;(3&amp;ndash;1) Cache는 &lt;code&gt;Recycled Pool&lt;/code&gt;에게 이 &lt;b&gt;오래된 &lt;code&gt;ItemView&lt;/code&gt;를 전달&lt;/b&gt;하고,&lt;br /&gt;(3&amp;ndash;2) &lt;code&gt;Recycled Pool&lt;/code&gt;은 &lt;code&gt;Adapter&lt;/code&gt;에게 이 &lt;code&gt;ItemView&lt;/code&gt;를 &lt;b&gt;메모리에서 제거해도 된다는 메시지&lt;/b&gt;를 보낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그렇지 않고 &lt;b&gt;계속 캐시에 저장할 필요가 있는 경우,&lt;/b&gt;&lt;br /&gt;(4) Cache에 &lt;b&gt;계속 저장하라는 지시&lt;/b&gt;를 내리고, 이를 통해 나중에 &lt;code&gt;LayoutManager&lt;/code&gt;가 해당 position에 대한 &lt;code&gt;ItemView&lt;/code&gt;를 요청할 경우 &lt;code&gt;Adapter&lt;/code&gt;를 거치지 않고 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewHolder LifeCycle - 3. Fancy Reserves&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;LayoutManager&lt;/code&gt;가 다시 레이아웃을 계산하는 도중 &lt;code&gt;adapter&lt;/code&gt;가 변경되는 경우이다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;text-align: left;&quot;&gt;몇몇&amp;nbsp;&lt;/span&gt;View&lt;span style=&quot;text-align: left;&quot;&gt;가&amp;nbsp;&lt;/span&gt;&lt;b&gt;더 이상 사용하지 않게 되었을 경우의 상황&lt;/b&gt;은 아래 그림과 같다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KiBkV/btsmuoLkF3r/SwJyCkpc9KqhsZSABGvTA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KiBkV/btsmuoLkF3r/SwJyCkpc9KqhsZSABGvTA1/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KiBkV/btsmuoLkF3r/SwJyCkpc9KqhsZSABGvTA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKiBkV%2FbtsmuoLkF3r%2FSwJyCkpc9KqhsZSABGvTA1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;781&quot; height=&quot;631&quot; data-origin-width=&quot;781&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(1)&lt;/b&gt;&lt;/span&gt; &lt;code&gt;RecyclerView&lt;/code&gt;가 &lt;code&gt;LayoutManager&lt;/code&gt;에게 &lt;b&gt;사용되지 않는 &lt;code&gt;ItemView&lt;/code&gt;가 있음을 알린 후&lt;/b&gt;,&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;(2)&lt;/span&gt;&lt;/b&gt; 사라지는 &lt;code&gt;ItemView&lt;/code&gt;들을 다시 &lt;code&gt;ViewGroup&lt;/code&gt;에 추가한다.&lt;br /&gt;그런 다음, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(3)&lt;/b&gt;&lt;/span&gt; &lt;code&gt;LayoutManager&lt;/code&gt;에서 이 &lt;code&gt;ItemView&lt;/code&gt;들을 숨긴다.&lt;br /&gt;이 때 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(4)&lt;/b&gt;&lt;/span&gt; &lt;code&gt;RecyclerView&lt;/code&gt;는 이 &lt;code&gt;ItemView&lt;/code&gt;들에 대해 &lt;code&gt;ItemAnimator&lt;/code&gt;에게 &lt;b&gt;애니메이션 처리를 요구&lt;/b&gt;하게 되고, 애니메이션 처리가 끝나면 (&lt;code&gt;onAnimationFinished&lt;/code&gt;)&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(5)&lt;/b&gt;&lt;/span&gt; &lt;code&gt;Adapter&lt;/code&gt;의 &lt;code&gt;onViewDetachedFromWindow&lt;/code&gt;를 호출해 &lt;b&gt;제거가 완료되었음을 알린다.&lt;/b&gt;&lt;br /&gt;그 후 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;(6)&lt;/b&gt;&lt;/span&gt; &lt;code&gt;Cache&lt;/code&gt;와 &lt;code&gt;Recycled Pool&lt;/code&gt;에게 &lt;b&gt;캐시를 업데이트&lt;/b&gt;하고 제거된 &lt;code&gt;ItemView&lt;/code&gt;를 재활용하라고 지시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ViewHolder Lifecycle - 4. Death&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ViewHolder&lt;/code&gt;를 잃는 경우이다&lt;/li&gt;
&lt;li&gt;이 경우 &lt;b&gt;성능 문제가 발생&lt;/b&gt;하기 때문에 이는 매우 중요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #faecde; color: #333333;&quot;&gt;&lt;b&gt;Death Case #1&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;581&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zIlcJ/btsmtCQxWK9/w6M1UaPKBK3otb7gKBTARK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zIlcJ/btsmtCQxWK9/w6M1UaPKBK3otb7gKBTARK/img.png&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zIlcJ/btsmtCQxWK9/w6M1UaPKBK3otb7gKBTARK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzIlcJ%2FbtsmtCQxWK9%2Fw6M1UaPKBK3otb7gKBTARK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;821&quot; height=&quot;581&quot; data-origin-width=&quot;821&quot; data-origin-height=&quot;581&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;amp;nbsp;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1번째 &lt;code&gt;ViewHolder&lt;/code&gt; 소멸은 &lt;code&gt;ItemAnimator&lt;/code&gt;를 사용하지 않고 애니메이션을 구현하는 경우 발생가능하다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) &lt;code&gt;LayoutManager&lt;/code&gt;는 &lt;code&gt;ItemView&lt;/code&gt;가 더 이상 필요하지 않고 이 &lt;code&gt;ItemView&lt;/code&gt;가 &lt;code&gt;transientState&lt;/code&gt;에 빠진 경우(&lt;b&gt;애니메이션이 진행 중인 상태&lt;/b&gt;, 예를 들어 &lt;code&gt;Fading out&lt;/code&gt;) 이를 &lt;code&gt;RecyclerView&lt;/code&gt;에게 알리는데,&lt;/li&gt;
&lt;li&gt;(2) &lt;code&gt;RecyclerView&lt;/code&gt;는 이 &lt;code&gt;ItemView&lt;/code&gt;가 &lt;b&gt;유효한지 확인&lt;/b&gt;한 후, &lt;b&gt;유효하지 않다면&lt;/b&gt; 이를 &lt;code&gt;Recycled Pool&lt;/code&gt;에 알린다.&lt;/li&gt;
&lt;li&gt;(3) &lt;code&gt;Recycled Pool&lt;/code&gt;에서 다시 한 번 &lt;code&gt;transientState&lt;/code&gt;인지 확인하고, &lt;b&gt;해당 상태라면&lt;/b&gt; &lt;b&gt;재활용에 사용할 수 없으므로&lt;/b&gt; &lt;code&gt;Adapter&lt;/code&gt;의 &lt;code&gt;onFailedToRecycle()&lt;/code&gt;을 호출한다.&lt;/li&gt;
&lt;li&gt;&amp;rarr; 여기서 우리는 임의로&lt;span&gt;&amp;nbsp;&lt;/span&gt;Adapter의&lt;span&gt;&amp;nbsp;&lt;/span&gt;onFailedToRecycle의 리턴값을&lt;span&gt;&amp;nbsp;&lt;/span&gt;true로 재구현해&lt;span&gt;&amp;nbsp;&lt;/span&gt;Recycled Pool에게 &amp;ldquo;날 믿고 재활용해라&amp;rdquo; 라는 메시지를 전달 할 수도 있다.&lt;/li&gt;
&lt;li&gt;하지만 기본적으로 &lt;code&gt;Adapter&lt;/code&gt;의 &lt;code&gt;onFailedToRecycle()&lt;/code&gt;을 호출하면 &lt;code&gt;false&lt;/code&gt;를 리턴하도록 구현되어 있어서 &lt;code&gt;ViewHolder&lt;/code&gt;는 파괴된다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&amp;rarr; 이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;우리가 원치 않는 결과&lt;/b&gt;&lt;/span&gt;이며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;ItemAnimator를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;사용하지 않고 애니메이션을 구현할 경우&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;해당 상황에 빠질 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; &lt;code&gt;ItemAnimator&lt;/code&gt;를 사용하면 &lt;b&gt;올바른 Lifecycle 이벤트를 수신&lt;/b&gt;하여 &lt;code&gt;ItemView&lt;/code&gt;를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;재활용할 수 있게 도와준다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #faecde; color: #333333;&quot;&gt;&lt;b&gt;Death Case #2&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;625&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ3iq0/btsmmPYxkvR/jaCJVt2NElUvAaHQbowtK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ3iq0/btsmmPYxkvR/jaCJVt2NElUvAaHQbowtK0/img.png&quot; data-alt=&quot;출처:https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ3iq0/btsmmPYxkvR/jaCJVt2NElUvAaHQbowtK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ3iq0%2FbtsmmPYxkvR%2FjaCJVt2NElUvAaHQbowtK0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2000&quot; height=&quot;625&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;625&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(1) `RecyclerView`에서 `ItemView`에 대한 처리를 마쳤고 이를 `Recycled Pool`에게 알린다.&lt;/li&gt;
&lt;li&gt;Recycled Pool은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;제한된 크기&lt;/b&gt;&lt;/span&gt;를 가지고 있기 때문에 해당하는 `ViewHolder`에 대해 더 이상 남는 자리가 없는 경우 `ViewHolder`가 제거된다.&lt;/li&gt;
&lt;li&gt;일반적으로 이는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;다음과 같은 경우에 발생&lt;/b&gt;&lt;/span&gt;한다&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;1. 많은 `ViewHolder`들이 같은 타입인 경우 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;캐싱이 되지않아&lt;/b&gt;&lt;/span&gt; `Recycled Pool`로 넘어가서 제거되었다&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2.&amp;nbsp;모든&amp;nbsp;`ItemView`들이&amp;nbsp;애니메이션이&amp;nbsp;존재할&amp;nbsp;때,&amp;nbsp;`notifyItemRangeChanged(0,&amp;nbsp;getItemCount())가`&amp;nbsp;호출되면서,&amp;nbsp;애니메이션이&amp;nbsp;끝나고&amp;nbsp;이&amp;nbsp;`ItemView`들을&amp;nbsp;`Recycled&amp;nbsp;Pool`에&amp;nbsp;다시&amp;nbsp;추가하려고하는데,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;이 때, R`ecycled Pool`은 이렇게 많은 아이템이 필요하지 않다고 판단하고 제거된다.&lt;/li&gt;
&lt;li&gt;이름 방지하기 위해선 다음 방법을 사용할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1. 조금 더 세밀하게 adapter를 업데이트하기 (ex: &lt;code&gt;notifyItemChanged(3)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;2. &lt;code&gt;pool.setMaxRecycledViews(type, count)&lt;/code&gt;를 통해 pool의 크기를 늘리기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RecycledViewPool&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위에서 캐시와 &lt;code&gt;Recycled&lt;/code&gt; Pool을 탐색하며, &lt;code&gt;RecyclerView&lt;/code&gt;가 &lt;code&gt;ViewHolder&lt;/code&gt;를 찾는 과정과, &lt;code&gt;ViewHolder&lt;/code&gt;의 생성을 알 수 있었다. 그렇다면, &lt;code&gt;RecycledViewPool&lt;/code&gt;이란 어떤 것일까?&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static class RecycledViewPool {
        private static final int DEFAULT_MAX_SCRAP = 5;

        public void setMaxRecycledViews(int viewType, int max) {
            ScrapData scrapData = getScrapDataForType(viewType);
            scrapData.mMaxScrap = max;
            final ArrayList&amp;lt;ViewHolder&amp;gt; scrapHeap = scrapData.mScrapHeap;
            while (scrapHeap.size() &amp;gt; max) {
                scrapHeap.remove(scrapHeap.size() - 1);
            }
        }

        public int getRecycledViewCount(int viewType) {
            return getScrapDataForType(viewType).mScrapHeap.size();
        }

        @Nullable
        public ViewHolder getRecycledView(int viewType) {
            final ScrapData scrapData = mScrap.get(viewType);
            if (scrapData != null &amp;amp;&amp;amp; !scrapData.mScrapHeap.isEmpty()) {
                final ArrayList&amp;lt;ViewHolder&amp;gt; scrapHeap = scrapData.mScrapHeap;
                for (int i = scrapHeap.size() - 1; i &amp;gt;= 0; i--) {
                    if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
                        return scrapHeap.remove(i);
                    }
                }
            }
            return null;
        }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecycledViewPool&lt;/code&gt;은 &lt;code&gt;RecyclerView.Recycler&lt;/code&gt;의 &lt;code&gt;inner class&lt;/code&gt;이다.&lt;br /&gt;&lt;code&gt;getRecycledView&lt;/code&gt;의 &lt;code&gt;parameter&lt;/code&gt;로 &lt;code&gt;ViewType&lt;/code&gt;을 전달하면, &lt;code&gt;ViewType&lt;/code&gt;에 맞는 &lt;code&gt;ViewHolder&lt;/code&gt;를 return해준다는 것을 알 수 있고, &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ViewType&lt;/span&gt;&lt;/code&gt;마다&lt;/span&gt; &lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ViewHolder Pool&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;을 가지고 있다는 것을 알 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;즉 캐시에서 원하는 &lt;code&gt;ViewHolder&lt;/code&gt;를 찾지 못한 경우 &lt;b&gt;마지막으로&lt;/b&gt; &lt;code&gt;RecycledViewPool&lt;/code&gt;의 &lt;code&gt;getRecycledView&lt;/code&gt;로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;해당 &lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ViewType&lt;/span&gt;&lt;/code&gt;에 해당하는 &lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;ViewHolder&lt;/span&gt;&lt;/code&gt;를 달라고 요청하는 것&lt;/b&gt;&lt;/span&gt;이다&lt;/li&gt;
&lt;li&gt;또한 상수로 &lt;code&gt;DEFAULT_MAX_SCRAP =5&lt;/code&gt;로 선언되어 있는 것은 &lt;code&gt;ViewType&lt;/code&gt;별로 가지고 있는 &lt;code&gt;pool&lt;/code&gt;의 기본 용량이 5개라는 것이다&lt;/li&gt;
&lt;li&gt;&lt;code&gt;setMaxRecycledViews&lt;/code&gt;의 &lt;code&gt;parameter&lt;/code&gt;로 &lt;code&gt;ViewType&lt;/code&gt;과 &lt;code&gt;pool&lt;/code&gt;이 가지고 있는 &lt;code&gt;ViewHolder&lt;/code&gt;의 개수를 전달하면 &lt;code&gt;pool&lt;/code&gt;의 용량을 늘리거나 줄일 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #faecde; color: #333333;&quot;&gt;&lt;b&gt;이렇게 &lt;code&gt;pool&lt;/code&gt;의 용량을 개발자가 직접 조절할 수 있다는 것은 매우 중요한데,&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;만약 화면에 동일한 &lt;code&gt;viewType&lt;/code&gt;을 가지는 &lt;b&gt;아이템이 몇십개 존재&lt;/b&gt;하면 이들이 동시에 변경되어야 할땐 해당 &lt;code&gt;viewType&lt;/code&gt;을 가지는 &lt;code&gt;pool&lt;/code&gt;의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;용량을 크게 설정&lt;/b&gt;&lt;/span&gt;하는 게 좋다. &lt;br /&gt;&lt;code&gt;ViewHolder&lt;/code&gt;를 많이 저장해두면 &lt;b&gt;재사용할 수 있는 ViewHolder도 많아지기 때문이다.&lt;/b&gt;&lt;br /&gt;반면 &lt;b&gt;화면에 딱 하나만 보여지는 &lt;code&gt;ViewType&lt;/code&gt;&lt;/b&gt;이 있다면 &lt;b&gt;용량을 1로 설정하면 &lt;span style=&quot;color: #ee2323;&quot;&gt;메모리를 절약&lt;/span&gt;&lt;/b&gt;할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;또 하나 중요한 점은 &lt;code&gt;RecycledViewPool&lt;/code&gt;가 &lt;code&gt;public&lt;/code&gt;으로 설정된 &lt;code&gt;class&lt;/code&gt;라는 것이다. 즉 &lt;code&gt;RecyclerView.RecycledViewPool()&lt;/code&gt; 처럼 &lt;code&gt;RecycledViewPool&lt;/code&gt; 객체를 생성해 해당 Pool을 '공유'할 수 있다.&lt;br /&gt;&amp;rarr; 즉 &lt;code&gt;여러 RecyclerView&lt;/code&gt;들이 &lt;code&gt;같은 Pool&lt;/code&gt;을 공유해 메모리를 절약할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Dirty View&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위처럼 &lt;code&gt;pool&lt;/code&gt;에 있는 &lt;code&gt;View&lt;/code&gt;들을 &lt;code&gt;dirty view&lt;/code&gt;라고 부른다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dirty view&lt;/code&gt;는 &lt;code&gt;pool&lt;/code&gt;에 들어올 때 &lt;code&gt;View&lt;/code&gt;와 &lt;code&gt;ViewType&lt;/code&gt;만 남기고 &lt;code&gt;potition&lt;/code&gt;, &lt;code&gt;flags&lt;/code&gt;등의 상태는 초기화 되기 때문에 &lt;code&gt;pool&lt;/code&gt;에 존재하는 &lt;code&gt;dirty view&lt;/code&gt;들을 꺼내 쓰려면 &lt;b&gt;데이터를 다시 바인딩해주어야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;반면 &lt;code&gt;pool&lt;/code&gt;이 아닌 &lt;code&gt;캐시&lt;/code&gt;에 있는 &lt;code&gt;view&lt;/code&gt;는 &lt;code&gt;position&lt;/code&gt;, &lt;code&gt;flags&lt;/code&gt;등의 상태를 그대로 가지고 있기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;바인딩없이 그대로 재사용&lt;/b&gt;&lt;/span&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Cache&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원하는 &lt;code&gt;ViewHoler&lt;/code&gt;가 있는지 &lt;code&gt;RecyclerdViewPool&lt;/code&gt;에서 찾기 전에 &lt;code&gt;Cache&lt;/code&gt;를 먼저 방문한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Cache&lt;/code&gt;는 &lt;code&gt;ViewHolder&lt;/code&gt;로 이루어진 &lt;code&gt;리스트&lt;/code&gt;로,&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;code&gt;&lt;span style=&quot;background-color: #faecde;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;RecyclerdViewPool&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;background-color: #faecde;&quot;&gt;&lt;b&gt;와 다르게 &lt;code&gt;viewType&lt;/code&gt;으로 &lt;code&gt;ViewHolder&lt;/code&gt;를 구분하지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대신 &quot;&lt;code&gt;position&lt;/code&gt;&quot;을 기준으로 탐색한다. 따라서 &lt;code&gt;캐시에 있는 ViewHolder&lt;/code&gt;는 데이터를 &lt;b&gt;다시 바인딩 할 필요 없이&lt;/b&gt; 원래 &lt;b&gt;위치해 있던&lt;/b&gt; &lt;code&gt;position&lt;/code&gt;&lt;b&gt;에 그대로 재사용&lt;/b&gt; 될 수 있는 것이다&lt;/li&gt;
&lt;li&gt;예를 들어 가장 위에 있던 &lt;code&gt;position 5&lt;/code&gt; 아이템이 위로 스크롤 되어 화면에서 벗어난 후 다시 아래로 스크롤되어 화면에 보여질 때 &lt;code&gt;position 5&lt;/code&gt; 에 해당하는 &lt;code&gt;ViewHolder&lt;/code&gt;가 &lt;code&gt;cache&lt;/code&gt;에 있었다면 &lt;code&gt;5라는 position&lt;/code&gt;을 다시 &lt;b&gt;바인딩할 필요없이 바로 재사용&lt;/b&gt; 할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약&lt;br /&gt;즉, ViewHolder를 &lt;b&gt;cache&lt;/b&gt;에서 찾았다면 &amp;rarr; view 변경없이 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;바로 재활용&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;b&gt;pool에서 찾았다면 &lt;/b&gt;&amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;바인딩 필요&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;ViewHolder가 어디에도 존재하지 &lt;b&gt;않으면&lt;/b&gt; &amp;rarr; &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;새로 생성되고 바인딩됨&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ViewHolder 생성의 시점과 개수&lt;br /&gt;(RecyclerView의 동작방식)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;가 &lt;code&gt;ViewHolder&lt;/code&gt;를 재활용하는 과정에서 가장 상단에 있는 &lt;code&gt;View&lt;/code&gt;가 사라지면 해당 &lt;code&gt;ViewHolder&lt;/code&gt;가 스크롤시 하단에 나타나는 &lt;code&gt;View&lt;/code&gt;에 바로 재활용된다고 생각하기 쉽다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그러나 &lt;code&gt;ViewHolder&lt;/code&gt;는 &quot;바로&quot; 재활용 되지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;가장 처음 &lt;code&gt;RecyclerView&lt;/code&gt;가 &lt;code&gt;Adapter&lt;/code&gt;에 &lt;code&gt;set 되었을 때&lt;/code&gt; 호출되는 &lt;code&gt;콜백함수&lt;/code&gt;를 보면
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView&lt;/code&gt;가 화면에 &lt;code&gt;onAttachedToRecyclerView&lt;/code&gt;가 호출되어 붙음&lt;/li&gt;
&lt;li&gt;화면에 총 4개의 &lt;code&gt;ViewHolder&lt;/code&gt;를 그리기 위한 &lt;code&gt;onCreateViewHolder&lt;/code&gt; 호출&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LayoutManager&lt;/code&gt;가 &lt;code&gt;addView&lt;/code&gt;를 호출해 &lt;code&gt;itemView&lt;/code&gt;를 &lt;code&gt;position&lt;/code&gt;에 잘 붙인 후 &lt;code&gt;RecyclerView&lt;/code&gt;에게 알리면 &lt;code&gt;RecyclerView&lt;/code&gt;는 &lt;code&gt;Adapter&lt;/code&gt;에게 &lt;code&gt;onViewAttachedToWindow&lt;/code&gt;호출&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;927&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cbjob/btsmtDPsWwE/6twxkTZALakmIdTqh97kdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cbjob/btsmtDPsWwE/6twxkTZALakmIdTqh97kdk/img.png&quot; data-alt=&quot;출처:https://velog.io/@dabin/안드로이드-공식문서-파헤치기-RecyclerView의-모든-것-2탄ViewHolder수명주기#recyclerdviewpool&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cbjob/btsmtDPsWwE/6twxkTZALakmIdTqh97kdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCbjob%2FbtsmtDPsWwE%2F6twxkTZALakmIdTqh97kdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;963&quot; height=&quot;927&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;927&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:https://velog.io/@dabin/안드로이드-공식문서-파헤치기-RecyclerView의-모든-것-2탄ViewHolder수명주기#recyclerdviewpool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;중요한건, 화면에 최초로 보이는 4개의 ViewHolder를 생성하고 난 후 스크롤을 내렸을 때&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;바로 위에 있는 &lt;code&gt;ViewHolder&lt;/code&gt;인 &lt;code&gt;0번 holder&lt;/code&gt;가 사라지고 &lt;code&gt;5번째 itemView&lt;/code&gt;가 나타나면서 &lt;code&gt;0번째 ViewHolder&lt;/code&gt;를 재사용할 것이기 때문에 바로 &lt;code&gt;onCreateViewHolder&lt;/code&gt;를 건너뛰고 &lt;code&gt;onBindViewHolder&lt;/code&gt;를 호출할 것이라 생각한다.&lt;/li&gt;
&lt;li&gt;하지만 &lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;5번째 itemView&lt;/b&gt;&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;가 나타나며 또 &lt;code&gt;ViewHolder&lt;/code&gt;를 생성한다.&lt;/b&gt;&lt;/span&gt;&amp;nbsp;&lt;br /&gt;&lt;code&gt;8번째 itemView&lt;/code&gt;가 나타날때까지 &lt;code&gt;ViewHolder&lt;/code&gt;는 재활용되지 않고 계속 생성된다.&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&amp;rarr; 이는 &lt;code&gt;RecyclerView&lt;/code&gt;가 사라진 &lt;code&gt;ViewHolder&lt;/code&gt;를 '바로' 재활용하지 않기 때문에 나타나는 현상이다&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;계속 스크롤하다가 &lt;code&gt;9번째 itemView&lt;/code&gt;가 만들어지는 순간 가장 먼저 &lt;code&gt;detached&lt;/code&gt;되었던 &lt;code&gt;0번째 ViewHolde&lt;/code&gt;r가 &lt;code&gt;onViewRecycled&lt;/code&gt;되어 나타난다. &lt;code&gt;onViewRecycled&lt;/code&gt;는 &lt;code&gt;&quot;재활용할 홀더를 가지고 왔음&quot;&lt;/code&gt;을 알리는 메서드이다.&lt;/li&gt;
&lt;li&gt;이후 &lt;code&gt;9번째 itemView&lt;/code&gt;부터는 &lt;code&gt;onCreateViewHolder&lt;/code&gt;가 호출되지 않고 &lt;code&gt;detached&lt;/code&gt;되었던 &lt;code&gt;ViewHolder&lt;/code&gt;들이 차례로 &lt;code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;onViewRecycled&lt;/b&gt;&lt;/span&gt;&lt;/code&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;되어 재사용&lt;/b&gt;&lt;/span&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;931&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3wb2G/btsmupQXFIu/CaHB7aKZXoRO9t5dAk10bk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3wb2G/btsmupQXFIu/CaHB7aKZXoRO9t5dAk10bk/img.png&quot; data-alt=&quot;출처:https://velog.io/@dabin/안드로이드-공식문서-파헤치기-RecyclerView의-모든-것-2탄ViewHolder수명주기#recyclerdviewpool&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3wb2G/btsmupQXFIu/CaHB7aKZXoRO9t5dAk10bk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3wb2G%2FbtsmupQXFIu%2FCaHB7aKZXoRO9t5dAk10bk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;939&quot; height=&quot;931&quot; data-origin-width=&quot;939&quot; data-origin-height=&quot;931&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:https://velog.io/@dabin/안드로이드-공식문서-파헤치기-RecyclerView의-모든-것-2탄ViewHolder수명주기#recyclerdviewpool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정리하자면, &lt;code&gt;9번째 itemView&lt;/code&gt;가 나타났을 때 &lt;code&gt;가장 먼저 사라졌던 0번째 홀더&lt;/code&gt;가 재사용됨을 알리는 &lt;code&gt;onViewRecycled:0&lt;/code&gt; 이 호출되고 이 &lt;code&gt;holder&lt;/code&gt;가 &lt;code&gt;9번째 홀더&lt;/code&gt;가 되어 &lt;b&gt;bind되는 것이다&lt;/b&gt;.&lt;br /&gt;&amp;rarr; 이는 바로 &lt;code&gt;ViewHolder&lt;/code&gt;를 재사용한다는 의미의 콜백인 &lt;code&gt;onViewRecycled&lt;/code&gt;가 호출되지 않는다는 것을 통해 알 수 있다&lt;/li&gt;
&lt;li&gt;이후 &lt;code&gt;RecyclerView&lt;/code&gt;를 파괴하게 되면 &lt;b&gt;화면에서 사라짐과 동시&lt;/b&gt;에 &lt;code&gt;onDetachedFromRecyclerView&lt;/code&gt;가 호출된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RecyclerView Adapter 메소드 호출 순서&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;RecyclerView Adapter&lt;/code&gt;는 아래의 4개의 메소드를 통해서 &lt;b&gt;데이터를 바인딩&lt;/b&gt;한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;onCreateViewHolder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getItemCount&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getItemViewType&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;호출되는 순서를 살펴보면,
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;예를 들어 단말 화면에 10개의 리스트가 보인다면, &lt;b&gt;맨 처음 &lt;code&gt;getItemCount()&lt;/code&gt;가 호출&lt;/b&gt;되면서 총 item의 갯수가 몇개인지 판단한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;getItemViewType()&lt;/code&gt;이 호출되면서, 현재 &lt;b&gt;&lt;code&gt;Item View&lt;/code&gt;의 &lt;code&gt;Position&lt;/code&gt;에 해당하는 &lt;code&gt;ViewType&lt;/code&gt;이 무엇인지를 판단&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onCreateViewHolder&lt;/code&gt;에서 &lt;code&gt;ViewType&lt;/code&gt;에 해당하는 &lt;code&gt;ViewHolder&lt;/code&gt;를 &lt;b&gt;생성하여 리턴&lt;/b&gt;한다. &lt;br /&gt;(ViewHolder는 각 Item View의 정보를 가지고 있는 애이기 때문에, Adapter는 계속해서 ViewHolder를 이용하여 Item View를 관리할 것이다. )&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onBindViewHolder&lt;/code&gt;에서는 생성된 &lt;code&gt;ViewHolder&lt;/code&gt;와 &lt;code&gt;Position&lt;/code&gt;을 전달받아서, 현재 &lt;code&gt;Position&lt;/code&gt;에 맞는 &lt;b&gt;&lt;code&gt;data&lt;/code&gt;를 &lt;code&gt;ViewHolder&lt;/code&gt;가 관리하는 &lt;code&gt;View&lt;/code&gt;들에 binding&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;code&gt;getItemCount()&lt;/code&gt;가 1번 호출되고,&lt;br /&gt;그 후 &lt;code&gt;getItemViewType()&lt;/code&gt; &amp;rarr; &lt;code&gt;onCreateViewHolder()&lt;/code&gt; &amp;rarr; &lt;code&gt;onBindViewHolder()&lt;/code&gt;가 10번씩 호출될 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;출처 및 참고자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=LqBlYJTfLP4&quot;&gt;RecyclerView ins and outs - Google I/O 2016&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/hongbeomi-dev/recyclerview-deep-dive-with-google-i-o-2016-21e0895819d2&quot;&gt;RecyclerView Deep Dive with Google I/O 2016&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kimdabang.tistory.com/entry/Recycler-View-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-RecyclerView-lifecycle&quot;&gt;Recycler View 제대로 이해하기 - RecyclerView lifecycle&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@dabin/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9CRecycle-View#3viewholder&quot;&gt;[안드로이드 공식문서 파헤치기] RecyclerView의 모든 것! - 1편(구조, 탄생배경)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cncn6666.tistory.com/entry/RecyclerView%EC%97%90-%EB%8C%80%ED%95%9C-%EB%AA%A8%EB%93%A0-%EA%B2%83&quot;&gt;RecyclerView에 대한 모든 것&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ppizil.tistory.com/38&quot;&gt;[안드로이드] Recyclerview 제대로 알고 쓰자 !&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://pluu.github.io/blog/android/2021/09/20/lifecycleowner/&quot;&gt;Pluu Dev - RecyclerView#ViewHolder에서 ViewTreeLifecycleOwner 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://gift123.tistory.com/67&quot;&gt;안드로이드 개발 (34) RecyclerView 성능 향상&lt;/a&gt;&lt;/p&gt;</description>
      <category>IT/Android</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/113</guid>
      <comments>https://hodie.tistory.com/113#entry113comment</comments>
      <pubDate>Wed, 5 Jul 2023 07:14:02 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin/Coroutine] 7. Coroutine 작성시의 테스트와 디버깅</title>
      <link>https://hodie.tistory.com/103</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Coroutine의 유닛 테스팅&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Coroutine&lt;/code&gt;의 &lt;code&gt;suspend Function&lt;/code&gt;들은 &lt;code&gt;runblocking&lt;/code&gt; 코드블럭으로 수행하면된다.&lt;/li&gt;
&lt;li&gt;다만, &lt;code&gt;동적으로 Scope&lt;/code&gt;&lt;b&gt;를 만드는 경우&lt;/b&gt;에는 &lt;code&gt;Dispatchers&lt;/code&gt;를 &lt;code&gt;parameter&lt;/code&gt;&lt;b&gt;를 넘겨받는&lt;/b&gt; &lt;code&gt;ViewModel&lt;/code&gt;&lt;b&gt;을 사용해야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Android Architecture Component와 함께 사용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Android Architecture Component와 함께 사용하면, &lt;code&gt;LifeCycle&lt;/code&gt; 관리등 장점도 많이 있지만, 주의해야할 점들도 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Andorid&lt;/code&gt; 특징을 고려해서는 &lt;code&gt;Coroutine&lt;/code&gt; &lt;b&gt;코드는&lt;/b&gt; &lt;code&gt;Viewmodel&lt;/code&gt;&lt;b&gt;에 놓는 것이 적합하다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;code&gt;Job&lt;/code&gt; Debug Mode&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Debug mode&lt;/code&gt;에서 &lt;code&gt;Job&lt;/code&gt;을 &lt;code&gt;toString&lt;/code&gt;하면, 아래와 같은 형식으로 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;ldquo;coroutine#1&amp;rdquo;:BlockingCoroutine{Active}@6d311334&lt;/code&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Debugging &lt;code&gt;Coroutine&lt;/code&gt; and &lt;code&gt;Threads&lt;/code&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Coroutine&lt;/code&gt;은 &lt;code&gt;한 Thread&lt;/code&gt;에서 실행되어 &lt;code&gt;다른 Thread&lt;/code&gt;&lt;b&gt;에서&lt;/b&gt; &lt;code&gt;Resume&lt;/code&gt;&lt;b&gt;될 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;심지어 &lt;code&gt;Single-Threaded Dispatcher&lt;/code&gt;의 경우에도 &lt;b&gt;어떤 일을 어디서 언제 수행하는지 알아내기 힘들 수 있다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;일반적인 Debugging 방법&lt;/code&gt;은 &lt;b&gt;**&lt;code&gt;Thread name&lt;/code&gt;&lt;/b&gt;을 Log로 출력**하는 것이다, 이는 &lt;code&gt;Logging Framework&lt;/code&gt;를 통해 쉽게 할 수 있다,.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;coroutine&lt;/code&gt;을 사용할 때, &lt;code&gt;쓰레드&lt;/code&gt;만 찍으면, &lt;code&gt;context&lt;/code&gt; 정보를 충분히 알 수 없기 때문에, &lt;code&gt;kotlinx.coroutine&lt;/code&gt;에 &lt;b&gt;debugging 용도로 함수들&lt;/b&gt;이 들어가 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;'-Dkotlinx.coroutines.debug'&lt;/code&gt; &lt;b&gt;*&lt;i&gt;JVM option 과 함께 아래 코드를 실행하면&lt;code&gt;Thread.currentThread().name&lt;/code&gt; 만 출력해도 *&lt;/i&gt;&amp;ldquo;&lt;/b&gt;&lt;code&gt;main @coroutine#1&lt;/code&gt;&lt;b&gt;&amp;rdquo; 과 같이 출력&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;JVM 이 &lt;code&gt;-ea&lt;/code&gt; 옵션으로 실행되는 한 debug mode 는 항상 켜져 있으며,&lt;br /&gt;debug mode 일 때는 &lt;code&gt;coroutine#1&lt;/code&gt; 의 숫자는 순차적으로 assign 되어 출력된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1690359707812&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun log(msg: String) = println(&quot;[${Thread.currentThread().name}] $msg&quot;)
 
runBlocking{
    val a = async{
        log(&amp;ldquo;I&amp;rsquo;m a&amp;rdquo;)
        6
    }
    val b = async{
        log(&amp;ldquo;I&amp;rsquo;m b&amp;rdquo;)
        7
    }
    log(&amp;ldquo;answer is ${a.await() * b.await()}&amp;rdquo;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 경우 아래와 같이 3개의 &lt;code&gt;Coroutine&lt;/code&gt;이 있다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;answer 를 print 하는 main coroutine (#1)&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;m a 를 print 하는 main coroutine (#2)&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;m b 를 print 하는 main coroutine (#3)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Naming Coroutine For Debugging&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자동으로 Assign된 id도 로그를 자주 찍으면 상호관계를 보기에는 좋다.&lt;/li&gt;
&lt;li&gt;그러나, &lt;code&gt;coroutine&lt;/code&gt;이 &lt;code&gt;특정 request&lt;/code&gt;&lt;b&gt;의 processing에 묶여있거나&lt;/b&gt;, &lt;code&gt;특정 background task&lt;/code&gt;&lt;b&gt;를 수행&lt;/b&gt;한다면, &lt;b&gt;Debug 용도로 이름&lt;/b&gt;을 주는 것이 좋다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CoroutineName&lt;/code&gt;도 &lt;code&gt;Context Element&lt;/code&gt;로 &lt;code&gt;Thread Name&lt;/code&gt;과 같은 기능을 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마찬가지로 Debugging mode가 켜져 있다면, &lt;code&gt;thread name&lt;/code&gt;과 함께 표시된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1690359726628&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;runBlocking(CoroutineName(&amp;ldquo;main&amp;rdquo;)){
    log(&amp;ldquo;Started main coroutine&amp;rdquo;)
    val v1 = async(CoroutineName(&amp;ldquo;v1coroutine&amp;rdquo;)){
        delay(500L)
        log(&amp;ldquo;Computing v1&amp;rdquo;)
        252
    }
 
    val v2 = async(CoroutineName(&amp;ldquo;v2coroutine&amp;rdquo;)){
        delay(1000L)
        log(&amp;ldquo;Computing v2&amp;rdquo;)
        6
    }
 
    log(&amp;ldquo;The answer for v1 / v2 = ${v1.await() / v2.await(){&amp;ldquo;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;/* OUTPUT */
[main @main#1] Started main coroutine
[main @v1coroutine#2] Computing v1
[main @v2coroutine#3] Computing v2
[main #main#1] The answer for v1 / v2 = 42&lt;/code&gt;&lt;/pre&gt;</description>
      <category>IT/Kotlin</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/103</guid>
      <comments>https://hodie.tistory.com/103#entry103comment</comments>
      <pubDate>Mon, 19 Jun 2023 00:18:02 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin/Coroutine] 6. Coroutine의 Structured Concurrency(구조화된 동시성)</title>
      <link>https://hodie.tistory.com/100</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Structured Concurrency(구조화된 동시성)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;누가 기존의 동작을 취소할 수 있는가?&lt;/code&gt;&lt;br /&gt;&lt;code&gt;해당 동작은 Application의 LifeCycle을 충분히 준수하는가?&lt;/code&gt;&lt;br /&gt;&lt;code&gt;예외가 발생하면 이를 누가 처리하는가?&lt;/code&gt;&lt;br /&gt;&amp;rarr; 이에 대한 물음을 해소하기 위해 &lt;code&gt;Coroutine&lt;/code&gt;은 &lt;code&gt;Structed Concurrency&lt;/code&gt;를 내세운다.&lt;/li&gt;
&lt;li&gt;이 개념을 베이스로 만들어진, &lt;code&gt;Scope&lt;/code&gt;, &lt;code&gt;launch&lt;/code&gt;나 &lt;code&gt;async&lt;/code&gt; 블록을 사용하면,&lt;br /&gt;개발자는 메모리 누수 및 동시성을 가진 &lt;code&gt;Function&lt;/code&gt;들을 올바르게 처리할 수 있게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;GlobalScope.launch&lt;/code&gt;는 &lt;code&gt;top-level Coroutine&lt;/code&gt;을 만든다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비록 Light-Weight이기는 하나, 여전히 &lt;b&gt;Memory를 비롯한 Resource를 사용&lt;/b&gt;한다.&lt;br /&gt;&amp;rarr; 새롭게 &lt;code&gt;luanch&lt;/code&gt;된 &lt;code&gt;coroutine&lt;/code&gt;이 돌고있다는 사실을 잊는다면, &lt;code&gt;GloabalScope&lt;/code&gt;는 &lt;code&gt;process&lt;/code&gt;가 살아 있는 한, 계속 유지되기 때문에 &lt;b&gt;로직이 계속 돌것&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GlobalScope&lt;/code&gt;에 계속해서 &lt;code&gt;launch&lt;/code&gt;하는 작업은, 반복해서 의미없는 코드가 돌거나, 너무 많은 &lt;code&gt;Coroutine&lt;/code&gt;이 돌아 &lt;b&gt;메모리면에서 OOM 현상이 발생&lt;/b&gt;할 수 있고, &lt;b&gt;관리면에서 쉽게 에러&lt;/b&gt;를 유발할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;만약 &lt;code&gt;GloablScope.launch&lt;/code&gt;만 사용한다면, 이를 관리하기 위해 &lt;code&gt;reference(job)&lt;/code&gt;을 모두 가지고 있으면서 &lt;code&gt;join&lt;/code&gt;을 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;수동으로 관리&lt;/b&gt;&lt;/span&gt;해야 한다 &amp;rarr; 이는 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;에러를 발생시키기 쉬운 형태&lt;/b&gt;&lt;/span&gt;이다.&lt;/li&gt;
&lt;li&gt;이러한 수동작업을 해결하기 위해, &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;Structured Concurrency(구조화된 동기화)를 사용&lt;/b&gt;&lt;/span&gt;할수 있다.&lt;/li&gt;
&lt;li&gt;이는 &lt;code&gt;GlobalScope&lt;/code&gt;에서 &lt;code&gt;launch&lt;/code&gt; 하는 대신, &lt;code&gt;Thread&lt;/code&gt;를 사용하듯이 우리가 사용하는 &lt;code&gt;Context&lt;/code&gt; 범위 내에서 특정한 &lt;code&gt;Coroutine Scope&lt;/code&gt;를 만들고, 그 안에서 &lt;b&gt;새로운&lt;/b&gt; &lt;code&gt;CoroutineBuilder&lt;/code&gt;로 &lt;code&gt;Coroutine&lt;/code&gt;을 &lt;code&gt;launch&lt;/code&gt;하는 것이다.&lt;/li&gt;
&lt;li&gt;그렇게 시작하면 생성된 &lt;code&gt;CoroutineScope&lt;/code&gt;가 코드블럭의 &lt;code&gt;CoroutineScope&lt;/code&gt;에 더해진다.&lt;br /&gt;= 모든 &lt;code&gt;CoroutineBuilder&lt;/code&gt;는 코드 블럭에 &amp;ldquo;&lt;code&gt;CoroutineScope&lt;/code&gt;&amp;rdquo; Instance를 추가한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;Coroutine&lt;/code&gt;이 다른 &lt;code&gt;Coroutine&lt;/code&gt;의 &lt;code&gt;coroutinScope&lt;/code&gt;내에서 실행되면,&lt;br /&gt;&lt;code&gt;CoroutineScopre.corotuineContext&lt;/code&gt;를 통해, &lt;code&gt;Context&lt;/code&gt;를 상속받고,&lt;br /&gt;&lt;code&gt;부모 Coroutine job&lt;/code&gt;의 자식이 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GlobalScope&lt;/code&gt;는 &lt;code&gt;부모 Coroutine&lt;/code&gt;의 &lt;b&gt;영향을 받지 않는다&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;따라서 &lt;b&gt;외부 &lt;code&gt;Scope&lt;/code&gt;는 내부의 &lt;code&gt;Coroutine&lt;/code&gt;들이 종료되기 전까지는 종료되지 않으므로&lt;/b&gt;, 우리는 이 &lt;code&gt;scope&lt;/code&gt;에서 &lt;code&gt;join&lt;/code&gt; 을 명시적으로 부를 필요없이 &lt;code&gt;coroutine&lt;/code&gt;을 &lt;code&gt;launch&lt;/code&gt;하지 않는 간단한 코드를 만들수 있다.&lt;br /&gt;&amp;rarr; &lt;code&gt;부모 coroutine&lt;/code&gt;은 항상 &lt;code&gt;children&lt;/code&gt;의 &lt;b&gt;완료를 기다리며&lt;/b&gt;, &lt;code&gt;parent&lt;/code&gt;는 명시적으로 children의 &lt;code&gt;launch&lt;/code&gt;를 &lt;b&gt;track할 필요가 없고&lt;/b&gt; &lt;code&gt;Job.join&lt;/code&gt;으로 그들을 &lt;b&gt;기다릴 필요도 없다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1690359425031&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;runBlocking{
    val request = launch{
        repeat(3) { i -&amp;gt;
            launch{
                delay((i + 1) * 200L)
                println(&amp;ldquo;Coroutine $i is done&amp;rdquo;)
            }
        }
        println(&amp;ldquo;request: I&amp;rsquo;m done and I don&amp;rsquo;t explicitly join my children that are still active&amp;rdquo;)
    }
    request.join()
    println(&amp;ldquo;Now processing of the request is complete&amp;rdquo;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;  /* OUTPUT */
  request: I&amp;rsquo;m done and I don&amp;rsquo;t explicitly join my children that are still active
  Coroutine 0 id done
  Coroutine 1 id done
  Coroutine 2 id done
  Now processing of the request is complete&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Coroutine&lt;/code&gt; &lt;b&gt;외부 블럭&lt;/b&gt;은 &lt;b&gt;내부에서 실행되는&lt;/b&gt; &lt;code&gt;Coroutine&lt;/code&gt;&lt;b&gt;이 모두 완료&lt;/b&gt;되야만 &lt;b&gt;외부 블럭이 완료&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1690359450113&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun test2_1() {
    runBlocking {
        val jobs = List(10) {
            launch {
                delay(1000L)
                Log.e(TAG, &quot;aaa&quot;)
            }
        }
        // join을 하고 안하고에 따라 End runBlock이 먼저 찍힐지 끝나고 찍힐지가 결정된다
        // jobs.forEach { it.join() }

        Log.e(TAG, &quot;End runBlock &quot;)
    }
    Log.e(TAG, &quot;End function&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;위 코드에서는 &lt;code&gt;runBlocking coroutinebuilder&lt;/code&gt;로 함수에 &lt;code&gt;coroutine&lt;/code&gt; 영역으로 만들었다.&lt;/li&gt;
&lt;li&gt;그리고 그 안에서 새로운 &lt;code&gt;coroutine&lt;/code&gt;을 &lt;code&gt;launch&lt;/code&gt;한다 (list를 생성하면서 10개를 launch 시킴)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;runBlocking&lt;/code&gt;은 &lt;code&gt;내부 coroutine&lt;/code&gt;인 list의 열번이 다 수행될때까지 &lt;b&gt;block&lt;/b&gt; 되며, &lt;code&gt;launch&lt;/code&gt;로 실행한 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;비동기 동작(자식)이 모두 끝나야&lt;/b&gt; &lt;code&gt;runBlocking&lt;/code&gt;&lt;b&gt;{} 블럭이(부모) 종료&lt;/b&gt;&lt;/span&gt;된다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-kotlin&quot;&gt;End runBlock
aaa
aaa
...
End Function&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1690359473894&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;End runBlock
aaa
aaa
...
End Function&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그는 위와 같이 나오며&lt;/li&gt;
&lt;li&gt;주석처리된 &lt;code&gt;join()&lt;/code&gt;을 넣으면, 아래와 같은&lt;span style=&quot;color: #d44c47;&quot;&gt; &lt;b&gt;코드 실행순서가 보장&lt;/b&gt;&lt;/span&gt;되면서 찍힌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1690359496814&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;aaa
aaa
...
End runBlock
End Function&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;join&lt;/code&gt;은 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;실행 순서를 보장&lt;/b&gt;&lt;/span&gt;하기위해 쓰였다.&lt;/li&gt;
&lt;li&gt;코드 순서를 보장하기 위해 &lt;code&gt;내부 coroutine&lt;/code&gt;을 &lt;code&gt;join&lt;/code&gt;시킬수도 있지만,&lt;br /&gt;&lt;code&gt;Coroutine Scope&lt;/code&gt;를 &lt;span style=&quot;color: #d44c47;&quot;&gt;&lt;b&gt;내부에 만들어 순서를 보장&lt;/b&gt;&lt;/span&gt;할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1690359528363&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun main() = runBlocking { // this: CoroutineScope
    launch { 
        delay(200L)
        println(&quot;Task from runBlocking#2&quot;)
    }
    
    coroutineScope { // Creates a new coroutine scope
        launch {
            delay(500L) 
            println(&quot;Task from nested launch#3&quot;)
        }
    
        delay(100L)
        println(&quot;Task from coroutine scope#1&quot;) // This line will be printed before nested launch
    }
    
    println(&quot;Coroutine scope is over#4&quot;) // This line is not printed until nested launch completes
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;Task from coroutine scope#1
Task from runBlocking#2
Task from nested launch#3
Coroutine scope is over#4&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;coroutineScope&lt;/code&gt;을 이용하여 &lt;code&gt;내부에 또다른 scope&lt;/code&gt;을 만든다.&lt;/li&gt;
&lt;li&gt;위에서 모든 &lt;code&gt;coroutine&lt;/code&gt; 블럭은 내부(자식) 코루틴이 모두 완료될때까지 대기해야 한다.&lt;/li&gt;
&lt;li&gt;이런 이유로 &quot;&lt;i&gt;Coroutine scope is over&lt;/i&gt;&quot;은 &lt;code&gt;coroutineScope{...}&lt;/code&gt;이 끝날때 까지 기다렸다가 찍힌다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT/Kotlin</category>
      <category>coroutine</category>
      <category>Structured Concurrency</category>
      <category>구조화된 동시성</category>
      <category>코루틴</category>
      <author>Hodie!</author>
      <guid isPermaLink="true">https://hodie.tistory.com/100</guid>
      <comments>https://hodie.tistory.com/100#entry100comment</comments>
      <pubDate>Fri, 19 May 2023 20:32:04 +0900</pubDate>
    </item>
  </channel>
</rss>