<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>개발 삽질 블로그</title>
        <link>https://velog.io/</link>
        <description>안녕하세요 송훈기입니다.</description>
        <lastBuildDate>Sun, 06 Nov 2022 00:44:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>개발 삽질 블로그</title>
            <url>https://velog.velcdn.com/images/willow_ryu/profile/a49d9bbe-6bfb-43ce-a3e8-3f4c9b87d9f2/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 개발 삽질 블로그. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/willow_ryu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[RecyclerView와 Fragment간 Transition Animation]]></title>
            <link>https://velog.io/@willow_ryu/RecyclerView%EB%A6%AC%EC%82%AC%EC%9D%B4%ED%81%B4%EB%9F%AC%EB%B7%B0%EC%97%90%EC%84%9C-Transition-Animation-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@willow_ryu/RecyclerView%EB%A6%AC%EC%82%AC%EC%9D%B4%ED%81%B4%EB%9F%AC%EB%B7%B0%EC%97%90%EC%84%9C-Transition-Animation-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 06 Nov 2022 00:44:03 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/willow_ryu/post/822f19bc-b4ef-4d9f-b943-eed23d7c5266/image.gif" alt=""></p>
<blockquote>
<p>출처: 안드로이드 공식 문서 (<a href="https://developer.android.com/guide/fragments/animate">https://developer.android.com/guide/fragments/animate</a>)</p>
</blockquote>
<h1 id="개요">개요</h1>
<blockquote>
<p>Transition Animation은 <strong><em>기존에 떠있는 뷰가 다음 뷰의 위치와 연결되면서 움직이는 듯한 효과</em></strong> 를 얘기합니다. 마치 재활용 되는 듯한 느낌을 낼 수 있고, 보다 자연스럽게 화면 전환이 이루어지는 효과를 낼 수 있습니다.</p>
</blockquote>
<p>이번 글에서는 <code>Fragment</code>에서 <code>RecyclerView</code> 아이템을 클릭했을 때  <code>Transition Animation</code>을 적용할 수 있는 방법에 대해 얘기할 것입니다.</p>
<h1 id="원인">원인</h1>
<p><a href="https://developer.android.com/guide/navigation/navigation-animate-transitions">https://developer.android.com/guide/navigation/navigation-animate-transitions</a>
위 링크에서 <code>프래그먼트 대상으로 공유 요소 전환</code> 이라는 섹션의 가이드를 따라해보면 이 방법이 <code>RecyclerView</code>에서 <code>Fragment</code>로의 작동되지 않는다는 것을 알 수 있습니다. (몸으로 꺠달았습니다)</p>
<p>이유는 다음과 같습니다</p>
<ul>
<li>TransitionName을 Unique하게 지어야 한다.</li>
</ul>
<p>RecyclerView의 각각의 item에 <strong>동일한 TransitionName</strong>을 적용시켰기 떄문에 어디서 어떻게 움직여야 하는지 알 수가 없게 되어 작동하지 않게 됩니다.</p>
<p>그래서 유니크한 이름을 정할 수 있도록 작업을 진행하고, 이를 받을 수 있도록 작업한다면 Transition Animation을 만들 수 있습니다.</p>
<h1 id="해결">해결</h1>
<p>가장 먼저 유니크하게 값을 줄 수 있도록 xml resource를 선언해줍니다.</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;resources&gt;
    &lt;string name=&quot;characterImageTransition&quot;&gt;characterImage %s&lt;/string&gt;
    &lt;string name=&quot;characterNameTransition&quot;&gt;characterName %s&lt;/string&gt;
&lt;/resources&gt;</code></pre><p>이후 기본적으로 <code>DataBinding</code>을 사용하고 있다는 가정하에, <code>RecyclerView</code>의 각각의 아이템의 고유한 값을 적용시켜 <code>StringValue</code>를 완성시키면 됩니다.</p>
<pre><code>    &lt;data&gt;

        &lt;variable
            name=&quot;character&quot;
            type=&quot;com.ssong_develop.core_model.Characters&quot; /&gt;
    &lt;/data&gt;
.... 중략 ....
    android:transitionName=&quot;@{@string/characterImageTransition(String.valueOf(character.id))}&quot;</code></pre><p>만약 앱이 API 14이하를 제공한다면, <code>xml attribute</code>가 아닌 직접 메서드를 호출하여 <code>setTransitionName(name: String)</code>적용시킵니다.</p>
<p>그 다음 <code>Transition</code>의 효과를 선언하기 위한 xml file을 하나 더 생성합니다</p>
<pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;transitionSet&gt;
    &lt;changeBounds /&gt;
    &lt;changeTransform /&gt;
    &lt;changeClipBounds /&gt;
    &lt;changeImageTransform /&gt;
&lt;/transitionSet&gt;</code></pre><blockquote>
<p>changeBounds : 장면의 변경 전후에 대상 뷰의 경계를 캡처, 전환 중에 해당 내용을 애니메이션으로 만듬
changeTransform : 뷰의 스케일(scale) 또는 회전을 캡처하고 전환 중 해당 내용을 애니메이션으로 만듬
changeClipBounds : 변경 전후 getClipBounds() 를 캡처 및 전환 중 해당 내용을 애니메이션으로 만듬
changeImageTransform : ImageView의 매트릭스를 캡처 및 전환 중 해당 내용을 애니메이션으로 만듬</p>
</blockquote>
<p>그외에도 여러 <code>attribute</code>가 존재하지만 검색하시면서 사용하시길 바랍니다</p>
<p>여기까지 됐다면 이젠 연결만 남았습니다.
기존의 Adapter와 click을 연결한 <code>interface</code> 혹은 <code>lamda</code>를 수정해 애니메이션을 적용하고자 하는 <code>view</code>와 <code>TransitionName</code>을 인자로 넘겨줍니다</p>
<pre><code>/** click interface **/ 
interface ItemClickDelegate {
    fun onItemClick(
        characterImageView: View,
        characterNameView: View,
        characterImageTransitionName: String,
        characterNameTransitionName: String,
        characters: Characters
    )
}</code></pre><pre><code>/** viewHolder **/
    override fun onClick(view: View) {
        itemClickDelegate.onItemClick(
            characterImageView = binding.ivCharacterImage,
            characterNameView = binding.tvCharacterName,
            characterImageTransitionName = binding.ivCharacterImage.transitionName,
            characterNameTransitionName = binding.tvCharacterName.transitionName,
            characters = characters
        )
    }</code></pre><p>각각의 리사이클러뷰 구현이 다를테니 부분만 캡쳐해서 확인하였습니다.
이후엔 공식문서에 나와있는 대로 <code>RecyclerView</code>를 구현하고 있는 <code>fragment</code>에서 메서드를 호출하고<code>FragmentNavigatorExtras</code>를 적용해주면 끝이 납니다.</p>
<pre><code>/** fragment **/
 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        postponeEnterTransition()
        (binding.root.parent as? ViewGroup)?.doOnPreDraw {
            startPostponedEnterTransition()
        }
        ... 중략 ...
    }</code></pre><pre><code>    override fun onItemClick(
        characterImageView: View,
        characterNameView: View,
        characterImageTransitionName: String,
        characterNameTransitionName: String,
        characters: Characters
    ) {
        val bundle = Bundle()
        bundle.putParcelable(CHARACTER_KEY, characters)
        val extras = FragmentNavigatorExtras(
            characterImageView to characterImageTransitionName,
            characterNameView to characterNameTransitionName
        )
        findNavController().navigate(
            R.id.action_characterFragment_to_characterDetailFragment,
            args = bundle,
            navOptions = null,
            navigatorExtras = extras
        )
    }
</code></pre><h1 id="결론">결론</h1>
<p>이와 같은 방법을 통해서 RecyclerView와 Fragment간의 Transition Animation 구현을 만들었습니다.</p>
<h1 id="예시코드">예시코드</h1>
<p><a href="https://github.com/SSong-develop/RickMorty">https://github.com/SSong-develop/RickMorty</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android Ui 상태 표현하는 여러가지 방법]]></title>
            <link>https://velog.io/@willow_ryu/Android-Ui-%EC%83%81%ED%83%9C-%ED%91%9C%ED%98%84%ED%95%98%EB%8A%94-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@willow_ryu/Android-Ui-%EC%83%81%ED%83%9C-%ED%91%9C%ED%98%84%ED%95%98%EB%8A%94-%EC%97%AC%EB%9F%AC%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sun, 30 Oct 2022 08:14:30 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p><a href="https://developer.android.com/topic/architecture/ui-layer">https://developer.android.com/topic/architecture/ui-layer</a></p>
<p>안드로이드 개발문서 중 아키텍처와 관련된 내용을 다루고 있는 페이지 입니다.
아키텍처의 레이어 중에서 Ui 레이어에 대한 설명이 자세하게 나와 있으니 이를 확인하시면 좋을 거 같습니다.
이 글은 위의 글을 읽고 생각을 정리하고 다양한 방법을 적어놓은 글입니다.</p>
<h1 id="내용">내용</h1>
<p>프로젝트를 진행하면서 느끼는 거지만 UI는 확실히 단편적인 것이 좋은거 같다.
안드로이드 프로그래밍을 하다보면 많은 스트림(Flow, Observable, etc..)들이 생겨나고, 스트림들을 엮어서 다른 상태의 스트림을 다시 만들어낸다 (버튼의 활성화 조건, 혹은 여러가지 이벤트)
이 방법은 확실히 규모가 커지고, 화면 내에서 체크해야 하는 조건이 많아질 수록 가독성은 떨어지는 것 같다.
예를 한번 들어보자
<code>아이디</code> 와 <code>비밀번호</code>를 입력받고 원하는 조건이 성립하면 버튼을 활성화 해주는 로직을 ViewModel에 작성한다고 해보자
<code>TwowayDataBinding</code>과 <code>StateFlow</code> (혹은 <code>LiveData</code>)를 잘 알고 있는 개발자라면 이와 같이 작성할 수 있다.</p>
<pre><code>class MainViewModel : ViewModel() {

    var idState = MutableStateFlow(&quot;&quot;)

    var passwordState = MutableStateFlow(&quot;&quot;)

    val enableState = combine(
        idState,
        passwordState
    ) { id, password -&gt;
        id.length in 1..10 &amp;&amp; password.length in 2..10
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000L),
        initialValue = false
    )
}</code></pre><p><code>StateFlow</code>는 항상 최신의 값을 emit할 수 있도록, <code>BufferSize</code>는 <code>replay</code>로 1를 걸어놨기 때문에 언제든 사용자의 인터랙션에 변화해 최신의 값을 반환한다.
그러므로 이 로직자체에서 사이드 이펙트를 찾기는 어렵다
물론 감춰지지 않은 var 필드의 setter를 이상한 레이어에서 사용한다면..그것도 사이드 이펙트라면 어마어마한 사이드 이펙트가 존재하긴 한다.
그러나 화면에서 조건이 늘어나서, <code>주소</code>란과 <code>상세 주소</code>란이 생기고 <code>전화번호</code>까지 생겨난다면 어떨까?
그 조건에서 버튼의 활성 조건이 더욱 세분화 되고 다양해 진다면, <code>combine</code>(엮어진) 스트림인 <code>enableState</code>는 아마...처참해질 것이다.</p>
<p>그래서 최근 이러한 무분별한 스트림의 생성을 어떻게 하면 가독성이 좋게, 할 수 있을까 생각하다가 <code>공식문서</code>를 확인할 수 있었고, 그곳에서 새로운 방법을 찾아내었다.</p>
<pre><code>
class Main2ViewModel : ViewModel() {
    var uiState = MutableStateFlow(Main2UiState())
        private set

    fun updateId(id: String) {
        uiState.value = uiState.value.copy(id = id)
        updateButtonEnable()
    }

    fun updatePassword(password: String) {
        uiState.value = uiState.value.copy(password = password)
        updateButtonEnable()
    }

    private fun updateButtonEnable() {
        uiState.value = uiState.value.copy(
            buttonEnable = ((uiState.value.id.length in 1..10) &amp;&amp; (uiState.value.password.length in 2..10))
        )
    }
}

data class Main2UiState(
    val id: String = &quot;&quot;,
    val password: String = &quot;&quot;,
    val buttonEnable: Boolean = false
)</code></pre><p>결론부터 얘기하자면, 화면의 <code>상태(State)</code>를 나타내는 단 <code>1</code>개의 스트림을 두는 것이 좋지 않을까라고 생각했고, 공식문서에서도 이와 같은 방식을 권장했다.
이렇게 된다면, ui가 변경되는 변경지점을 제한할 수 있고, 무분별하게 스트림이 파생되어 가독성이 떨어지는 경우는 없을 것이다.
물론, 이게 완벽하다거나 규모가 늘어났을 때 가독성이 좋아진다!는 잘 모르겠다. 하지만 적어도 상태를 나타내는 단 하나의 스트림만 확인하고 이곳에서 부터 타고타고 가는 방식은 프로젝트에 대해 전혀 모르는 개발자가 보아도 이해할 수 있지 않을까 생각한다.
그외 뭐...변경가능성을 줄이고 이런 저런 여러가지 장점이 존재하는데 이는 위에 있는 공식문서를 확인하면 감사하겠다.</p>
<p>&lt;개인적 의견&gt;
이 방법이 오히려 fragment나 혹은 Compose에서 arguments를 받아서 처리해야하는 로직을 작성할 때 더 가독성이 좋다고 생각한다.</p>
<pre><code>class Main2ViewModel @Inject constructor(
   private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    var uiState = MutableStateFlow(Main2UiState())
        private set

    // (1)
    init {
       savedStateHandle.get&lt;String&gt;(&quot;transferData&quot;)?.let {
           uiState.value = uiState.value.copy(id = it)
       }
    }

    fun updateId(id: String) {
        uiState.value = uiState.value.copy(id = id)
        updateButtonEnable()
    }

    fun updatePassword(password: String) {
        uiState.value = uiState.value.copy(password = password)
        updateButtonEnable()
    }

    private fun updateButtonEnable() {
        uiState.value = uiState.value.copy(
            buttonEnable = ((uiState.value.id.length in 1..10) &amp;&amp; (uiState.value.password.length in 2..10))
        )
    }
}

data class Main2UiState(
    val id: String = &quot;&quot;,
    val password: String = &quot;&quot;,
    val buttonEnable: Boolean = false
)</code></pre><p>1번과 같은 식으로 표현이 가능해지는데, 만약 개별 스트림으로 작성해서 표현한다면</p>
<pre><code>class MainViewModel @Inject constructor(
   private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val transferData = savedStateHandle.getStateFlow&lt;String&gt;(&quot;transferData&quot;)

    var idState = MutableStateFlow(&quot;&quot;)

    val combined = combine(transferData, idState) { data, id -&gt; ... }

    var passwordState = MutableStateFlow(&quot;&quot;)

    val enableState = combine(
        idState,
        passwordState
    ) { id, password -&gt;
        id.length in 1..10 &amp;&amp; password.length in 2..10
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000L),
        initialValue = false
    )
}</code></pre><p>이런식으로 작성이 되는데, 나의 경우엔 첫번째 방법이 좀 더 보기에 좋지 않나 싶긴하다.</p>
<p>앞으로 이러한 내용을 표현하는 다양한 방법이 존재한다면 이곳에 적어두려고 한다.
예제 코드는 아래 링크에서 확인할 수 있습니다.</p>
<p><a href="https://github.com/SSong-developSampleAndroid/SampleUiState">https://github.com/SSong-developSampleAndroid/SampleUiState</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[안드로이드 메모리 누수]]></title>
            <link>https://velog.io/@willow_ryu/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98</link>
            <guid>https://velog.io/@willow_ryu/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EB%88%84%EC%88%98</guid>
            <pubDate>Mon, 03 Oct 2022 15:33:31 GMT</pubDate>
            <description><![CDATA[<p>스터디에서 발표했던 안드로이드 메모리 누수와 관련된 글을 노션에 작성했는데, 블로그에 옮겨놔야할거 같아서 옮겨 놓는다.</p>
<h2 id="android-memory-types">Android Memory Types</h2>
<p><strong>Random Access Memory(RAM)</strong></p>
<ul>
<li>RAM은 Application을 실행하는 동안 임시로 저장하는데 사용</li>
<li>여기서 Application은 현재 Foreground에서 돌고 있는 App과 Background에서 돌고 있는 App을 의미한다.</li>
<li>이것은 임시적이여서, App이 죽거나, 기기가 재시작되면 정보를 잃게 된다.</li>
</ul>
<p><strong>zRAM</strong></p>
<ul>
<li>RAM에 일부로서 사용되지 않는 자원을 압축, 예약된 영역으로 이동해 사용 가능한 메모리량을 늘리는 역할을 한다.</li>
<li>이러한 압축하는 과정에서 CPU의 작업을 더 많이 필요로 하기 때문에 장치 작업을 느리게 할 수 있다.</li>
</ul>
<p><strong>Storage</strong></p>
<ul>
<li>기기가 재부팅되어도 유지되어야 하는 데이터, 사진, 비디오, 음악, 문서등을 가지고 있다.</li>
<li>예로는 SharedPreference , SQLite , Realm 등이 존재한다.</li>
<li>Android의 스토리지는 다양하지만, 현재의 스토리지는 일반적으로 32GB , 64GB , 128GB , 256GB로 상당히 많은 용량을 할 수 있다. (Galaxy S21 기준 256GB인데 microSD카드는 사용하지 못하도록 한다.)</li>
<li>microSD 카드 같은 것을 통해서 더 확장할 수도 있다.(최근 나온 것은 안되게 하는 경우도 존재한다)</li>
</ul>
<p>메모리 관리 프로세스는 안드로이드 런타임(ART)와 그 이전 버전인 달빅 가상 머신으로 수행이 된다.</p>
<h2 id="what-are-memory-leaks">What are memory leaks</h2>
<p>그럼 이제 메모리 릭이 왜 나는지를 알아야 하는데, 그전에 그렇다면 JVM은 운영체제에게서 받은 메모리를 어떻게 구분해서 사용하는지를 다시 기억을 되새김해봅시다.</p>
<p><strong>JVM Runtime Data Area</strong></p>
<p>기본적으로 큰 영역은 3가지입니다.</p>
<p><strong>Method Area(메서드 영역)</strong></p>
<ul>
<li>코드에서 사용되는 클래스(~~.class)들을 클래스 로더로 읽어 클래스별로 런타임 상수풀 , 필드 데이터 , 메소드 데이터 , 메서드 코드 생성자 코드 등을 분류해서 저장합니다.</li>
<li>이 영역은 JVM이 시작할 때 생성되고, 모든 스레드가 공유하는 영역이 됩니다.</li>
</ul>
<p><strong>Heap (힙)</strong></p>
<ul>
<li>객체와 배열이 생성되는 영역</li>
<li>즉, 인스턴스와 배열이 동적으로(dynamic) 생성되는 공간입니다.</li>
<li>이곳에서 생성된 객체와 배열은 JVM 스택 영역의 변수나 다른 객체의 필드에서 참조를 하게 된다.</li>
<li>참조하는 변수나 필드가 존재하지 않는다면, 의미가 없는 객체로 판단하고 이를 GC가 힙에서 자동 제거 한다.</li>
<li>힙에 대해서 자세히 알고 싶다면 아래 자료를 확인하면 될 듯 싶다.</li>
<li>지금은 힙 얘기를 하려는 것은 아니니 이 정도면 충분하다고 생각한다.</li>
</ul>
<p><a href="https://jithub.tistory.com/296">Java Heap (with GC)</a></p>
<p><strong>Thread</strong></p>
<ul>
<li>위의 2 영역은 정확히 영역으로 구분 되지만 이 쓰레드는 여러개가 생성될 수 있다.</li>
<li>자바에서 쓰레드를 구성하는 요소는 <strong>PC Register , JVM Stack , Native Method Stack</strong>이다.</li>
</ul>
<p><strong>PC Register</strong></p>
<ul>
<li>현재 수행 중인 JVM Instruction  주소를 가짐</li>
</ul>
<p><strong>Native Method Stack</strong> </p>
<ul>
<li>Java 외의 언어로 작성된 네이티브 코드를 위한 Stack(JNI 같은걸 말하겠죠??)</li>
</ul>
<p><strong>JVM Stack</strong></p>
<ul>
<li>각 쓰레드 별로 1개씩 존재하고 스레드가 시작될 떄 할당된다.</li>
<li>이는 메서드를 호출할 때마다 프레임이라는 단위로 추가하고 메서드가 종료되면 해당 프레임을 제거하는 동작을 수행한다.</li>
<li>프레임 내부에는 로컬 변수 스택이 있고, 기본 타입 변수, 참조 타입 변수가 추가 되거나 제거 된다. </li>
<li>변수가 이 영역에 생성되는 시점은 초기화가 될 때, 즉 최초로 변수에 값이 저장될 때임</li>
<li>변수는 선언된 블록 안에서만 스택에 존재하고 블록을 벗어나면 스택에서 제거가 된다.</li>
<li>기본 타입 변수는 스택 영역에 직접 값을 가지고 있다.</li>
<li>참조 타입 변수는 값이 아니라 힙 영역이나 메서드 영역의 객체 주소를 가진다. ( 그림 1 참조 )</li>
</ul>
<p><img src="https://velog.velcdn.com/images/willow_ryu/post/3589be76-3a40-429e-a1c4-9256a6a34733/image.png" alt=""></p>
<blockquote>
<p>그림 1</p>
</blockquote>
<p>이렇듯 Heap영역에서 만들어진 객체를 회수하기 위해서 Android에서는 GC(Garbage Collector)를 사용합니다.</p>
<p>Heap영역을 추적해 더이상 사용하지 않는 객체를 힙영역에서부터 회수함으로써 메모리 영역을 남기는 것이다.</p>
<p>그렇다면 생각을 한번 해보면 우리가 사용하지 않는 힙영역의 객체를 계속해서 유지하고 있다면 GC는 이 객체가 계속해서 사용중이라고 생각할 거고 쓰레기라고 생각하지 않을 것이다.</p>
<p>이는 즉 객체를 메모리에서 해제하지 못하게 되는 것이고 이때 메모리 릭이 발생한다.</p>
<p>이러한 데이터들을 하나 , 둘씩 제거하지 못한다면 어드샌가 UI는 굳고 뭐...앱이 망가는 그런 상태가 발생하는거죠 OutOfMemory가 발생하게 되는 것입니다.</p>
<p>자 그럼 지금은 자바의 예로 들었으니까 이를 Android에 대입해서 생각을 한번 해봅시다</p>
<p>MainActivity 1개만 존재하는 App이 있습니다</p>
<pre><code class="language-kotlin">class MainActivity : AppCompatActivity(){

   override onCreate(savedStateInstance : Bundle?) {
       // 그냥 생각나는 대로 적은 겁니다 일단 있다고 합시다.
   }
} // (1)</code></pre>
<p>이 상황에서 어떠한 다른 Thread를 생성하지 않았기 때문에 Main Thread 즉 UI Thread만 존재합니다.</p>
<p>그럼 MainThread 의 JVMStack 영역에는 MainActivity class에 대한 값이 있어야 하는데</p>
<p>그 값이 실 객체 값을 가지는 Call by Value 형태가 아닌 Heap 영역에 MainActivity에 대한 구현체는 있고 JvmStack에는 MainActivity 클래스의 주소인 주소 값만을 가지고 있는 형태가 될 것입니다.</p>
<p>뭐 기타..여러가지 Context라던가 이러한 Activity를 구성하기위한 여러가지 상위 객체들 또한 Stack에 올라가있는 형태가 되겠지요(MainThread Stack에)</p>
<p>여기서 </p>
<pre><code class="language-kotlin">class SingletonExample(context : Context) {
   private var mContext = context 

   companion object{
       var instance : SingletonExample? = null

       fun getSingleton(context : Context) : SingletonExample {
            if(instance == null) {
                instance = SingletonExample(context)
            }
            return instance as SingletonExample
       }
   }
}</code></pre>
<p>이와 같은 SingletonExample이라는 클래스를 만들어 Context를 Singleton 형태로 받을 수 있도록 해봅시다. ( 예시일 뿐입니다 결코 좋은 형태가 아닙니다. )</p>
<pre><code class="language-kotlin">class MainActivity : AppCompatActivity(){

   override onCreate(savedStateInstance : Bundle?) {
       val singleton = SingletonExample.getSingleton(this)
   }
} // (2)</code></pre>
<p>여기서 singleton이라는 객체는 kotlin이라서 안적혀 있지만 new라는 것을 통해서 동적으로 생성된 객체입니다.</p>
<p>SingletonExample이 MainActivity에서 호출된 시점부터 SingletonExample은 MainActivity의 Context를 가지게 된 것이고 MainActivity가 죽게 되더라도, 즉 JVMStack영역에서 참조되는 것이 사라져 GC에서 제거 대상으로 포함해 제거를 하더라도, SingletonExample에서 계속 참조 중이기에 인스턴스를 아직 사용하고 있다고 간주하고 회수를 할 수 없게 됩니다. 이러한 상황이 메모리 누수를 일으킬 수 있게 됩니다.</p>
<h2 id="일반적인-누수-패턴">일반적인 누수 패턴</h2>
<h3 id="1-정적-참조에-대한-activity-누수">1) 정적 참조에 대한 Activity 누수</h3>
<p>Activity는 여러번 만들어지고 제거될 수 있는 객체이다.</p>
<p>하지만 정적 참조로 Activity를 참조한다면 앱이 메모리에 있는 동안 계속 유지되기 때문에 생명주기에 의해 제거되어도 GC가 수거할 수 없다.</p>
<p>따라서 Activity에서 정적 변수를 사용할 때 매우 주의 해야한다. 만약 정적 변수가 Activity를 직접 혹은 간접적으로 참조할 가능성이 있는 경우 <code>onDestroy()</code> 에서 참조를 끊어줘야한다.</p>
<blockquote>
<p>세부적인 case</p>
</blockquote>
<ol>
<li>static 변수에서 Activity를 참조하는 경우</li>
<li>static View 에서 Activity를 참조하는 경우.</li>
<li>싱글턴 객체에 Activity 참조를 넘기는 경우.</li>
</ol>
<h3 id="2-inner--익명-클래스-사용으로-인한-activity-누수">2) Inner &amp; 익명 클래스 사용으로 인한 Activity 누수</h3>
<p>inner class는 outer class 변수에 접근 가능하다는 특징이 있다. 이 경우 inner class는 outer class의 참조를 유지해야한다는 특성이 있기 때문에, 내부/익명 클래스가 outer class 보다 오랫동안 유지 될 경우 누수가 발생한다.</p>
<p>따라서 누수 위험을 피하기 위해서는 내부/익명 클래스는 정적 클래스를 사용한다.</p>
<blockquote>
<p>주로 중요하게 작용하는 지점은 다음과 같다
Activity 내부에서 Thread, handler, AsyncTask 를 사용하는경우</p>
</blockquote>
<h3 id="3-스레드-사용으로-인한-activity-누수">3) 스레드 사용으로 인한 Activity 누수</h3>
<p>스레드의 경우 Activity보다 오래 작업할 수 있다. 따라서 Thread의 작업을 <code>onDestroy()</code> 에서 종료해야한다.</p>
<h2 id="메모리-누수가-일어나면-왜-좋지-않은가">메모리 누수가 일어나면 왜 좋지 않은가?</h2>
<h3 id="1-메모리-부족">1. 메모리 부족</h3>
<p>메모리 누수가 발생하면, 기존에 차지한 메모리가 반환되지 않는다. 이 상황에서 다시 메모리를 요청하며 결과적으로 더 많은 메모리를 차지하게 된다.</p>
<p>하지만 메모리에는 한계가 있기 때문에, 더 이상 메모리에 할당할 수 없다면 앱은 메모리 부족으로 인해 강제 종료 된다.</p>
<p>⇒ 메모리 부족으로 앱이 종료되는 경우 사용자 입장에서 강제 종료의 경험을 겪는다. </p>
<h3 id="2-gc-발생시-ui렌더링과-이벤트-처리가-중단된다">2. GC 발생시 UI렌더링과 이벤트 처리가 중단된다.</h3>
<p>메모리 누수가 발생하면 결과적으로 Android 시스템은 GC를 빈번하게 일으키게 된다. GC가 발생하면 UI 랜더링 및 이벤트 처리가 중단된다. </p>
<p>안드로이드는 대략 화면을 16ms로 그린다고 한다.
만약 GC가 오래걸리면 안드로이드는 프레임이 떨어진다. 
⇒ 결과적으로 사용자 입장에서 앱이 느리다는 것을 인지하게 된다. 또한 심각한 경우 ANR 조건으로 ANR이 발생할 수 있다.</p>
<blockquote>
<p>사용자는 일반적으로 100ms~200ms 이상 걸리는 작업에서 앱이 느리다고 인지하게 된다.</p>
</blockquote>
<h3 id="3-재현이-어려움-qa-테스트로-찾기-어려움">3. 재현이 어려움, QA 테스트로 찾기 어려움.</h3>
<p>안드로이드 시스템이 메모리 할당을 거부할 때를 재현하기에는 어려움이 있다. </p>
<p>더불어 크래시 리포트로 추론하기도 어려움이 있다.</p>
<h2 id="그럼-어떻게-해야할까">그럼 어떻게 해야할까?</h2>
<p>결국 프로그래머가 GC가 어떻게 작동하는지 잘 이해해야 한다.</p>
<p>그리고 메모리 누수를 찾기 위해서 코드 작성과 리뷰에 노력을 해야한다.</p>
<p>안드로이드에서 일부 코드가 의심스러울 경우 누수를 예측할 수 있는 방법을 소개하면 다음과 같다.</p>
<ol>
<li><p>안드로이드 스튜디오 메모리 프로파일러</p>
<p> 이 경우, 개발자가 결국 GC를 잘 이해하고 어디에서 누수가 날지 인지해야한다. 누수를 찾는 방법은 아래와 같은 순서를 거친다.</p>
<ol>
<li>디버그 모드 실행</li>
<li>의심스러운 Activity로 이동하고 이전으로 돌아간뒤 다시 실행한다.(예측된 누수 상황 재현)</li>
<li>메모리 프로파일러 메모리 섹션 → GC 시작 버튼</li>
<li>Dump java heap 버튼</li>
<li>d 과정에서 .hprof파일이 열리는데 해당 뷰어에서 다음과 같은 작업으로 누수를 탐지할 수 있다.
Activity 객체가 하나 이상인경우 누수가 있다는 것이다.<ul>
<li>Analyzer Tasks 도구를 사용하여 누수되는 Activity 자동 탐지</li>
<li>Class List View 를 Package Tree View로 변경하여 Destory해야하는 Activity를 찾는다.</li>
</ul>
</li>
<li>누수되는 Activity를 찾았다면 하단 참조트리 창에서 Activity를 참조하는 객체를 찾으면 된다.</li>
</ol>
</li>
<li><p>Square - Leak Canary</p>
<p> 앱 액티비티들에 약한 참조를 만들어 GC 이후에 참조가 지워지는지 확인한다.</p>
<p> 그렇지 않은 경우 .hprof 를 이용하여(힙 덤프) 분석하고 누수 발생을 확인한다.</p>
<p> 만약 누수가 있는경우 알림이 표시되고 별도 앱을 통해 누수가 발생된 위치를 트리형태로 표시한다.</p>
<p> 주의) 개발/테스트 빌드시에만 Leak Canary를 설치하는 것이 좋다. 사용자 빌드 전에 개발자와 QA가 미리 메모리 누수를 찾기 위함.</p>
</li>
</ol>
<p>결과적으로 프로그래머 자신이 GC의 작동과 어떤 경우에 누수의 위험이 있는지 잘 인지하고 이를 신경쓰고, 주의하여 코드를 작성해야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[삽질] Room Database Blob으로 한 짓거리]]></title>
            <link>https://velog.io/@willow_ryu/%EC%82%BD%EC%A7%88-Room-Database-Blob%EC%9C%BC%EB%A1%9C-%ED%95%9C-%EC%A7%93%EA%B1%B0%EB%A6%AC</link>
            <guid>https://velog.io/@willow_ryu/%EC%82%BD%EC%A7%88-Room-Database-Blob%EC%9C%BC%EB%A1%9C-%ED%95%9C-%EC%A7%93%EA%B1%B0%EB%A6%AC</guid>
            <pubDate>Sun, 04 Sep 2022 11:44:49 GMT</pubDate>
            <description><![CDATA[<p>Room DataBase에 Blob의 형태로 ByteArray를 담아서 유저에게 사진을 제공하는 기능을 삽질하던 중에 생긴 의문점..?이 있어서 블로그에 적어본다.</p>
<ol>
<li><p>Room DataBase에 Blob의 형태로 ByteArray를 담는다.</p>
</li>
<li><p>Flow로 ByteArray를 받아, 이를 map연산자를 활용해 ImageBitmap으로 변환 해 이를 LazyColumn에서 보여준다.</p>
</li>
<li><p>이 과정에서 ImageCache를 위해 LruCache를 활용해 담아둔다.</p>
</li>
</ol>
<p>이와 같은 방식으로 앱을 만드는데, LazyColumn에서 이미지를 보여주기는 하나 이미지가 2개만 되어도 렉이 걸린다. 무슨 이유인지 잘 모르겠다.</p>
<p>구조를 잘못 짠건지...뭔지..혹시나 이 글을 읽는 사람 중에 구조를 보고 이상하다 생각하면 댓글이든 뭐든 남겨줬으면 싶다.</p>
<p><a href="https://github.com/SSong-develop/ImCrazyAboutTodo/blob/master/feature-todo/src/main/java/com/ssong_develop/feature_todo/subscreen/removetodo/RemoveTodoScreen.kt">https://github.com/SSong-develop/ImCrazyAboutTodo/blob/master/feature-todo/src/main/java/com/ssong_develop/feature_todo/subscreen/removetodo/RemoveTodoScreen.kt</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Letter Combinations of a Phone Number]]></title>
            <link>https://velog.io/@willow_ryu/Letter-Combinations-of-a-Phone-Number</link>
            <guid>https://velog.io/@willow_ryu/Letter-Combinations-of-a-Phone-Number</guid>
            <pubDate>Wed, 31 Aug 2022 03:22:54 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://leetcode.com/problems/letter-combinations-of-a-phone-number/">https://leetcode.com/problems/letter-combinations-of-a-phone-number/</a></p>
<h2 id="문제-해석">문제 해석</h2>
<p>2 ~ 9까지 주어진 숫자에는 각각 알파벳을 형상화 한다.
예를 들어 2 -&gt; a,b,c를 표현할 수 있다. 뭐 이런식으로 말이다.
이랬을 떄 숫자가 주어지면 만들 수 있는 문자열을 전부 나열하라는 문제다.</p>
<h2 id="문제-해결">문제 해결</h2>
<p>필요한 거</p>
<ol>
<li>각 숫자마다 매칭해 넣을 문자열 map</li>
<li>완전 탐색 기술</li>
</ol>
<h2 id="코드">코드</h2>
<pre><code>class Solution {
    val map = mapOf&lt;Char,String&gt;(
        &#39;2&#39; to &quot;abc&quot;,
        &#39;3&#39; to &quot;def&quot;,
        &#39;4&#39; to &quot;ghi&quot;,
        &#39;5&#39; to &quot;jkl&quot;,
        &#39;6&#39; to &quot;mno&quot;,
        &#39;7&#39; to &quot;pqrs&quot;,
        &#39;8&#39; to &quot;tuv&quot;,
        &#39;9&#39; to &quot;wxyz&quot;
    )

    fun letterCombinations(digits: String): List&lt;String&gt; {
        val res = mutableListOf&lt;String&gt;()
        if (digits.isEmpty()) {
            return res
        }
        combination(res,digits,&quot;&quot;,0)
        return res
    }

    fun combination(res: MutableList&lt;String&gt;,digits: String, temp: String, level: Int) {
        if (level == digits.length) {
            res.add(temp)
            return
        }
        val c = map.get(digits.get(level))
        c?.toCharArray()?.forEach {
            combination(res,digits,temp + it.toString(),level + 1)
        }
    }
}</code></pre><p>핵심은 combination 함수인거 같다.</p>
<p>먼저 함수가 digits 문자열을 끝까지 탐색했는지를 알려 줄 수 있는 level 변수를 하나 둔다.
이후 재귀의 종결 조건을 <code>level == digits.length</code>로 하고, 종결되었을 경우, 만들어진 문자열 <code>temp</code>를 리스트에 넣고 종료한다.</p>
<p>재귀의 종결 조건을 만들었다면 재귀해야할 로직을 넣어줘야 하는데, 먼저 digits의 각 문자가 표현할 수 있는 문자들을 map에서 찾아내고, 각각 이 변수들을 반복해서 <code>temp</code>에 넣어주고 재귀를 반복하면 결국엔 <code>temp</code>변수에 가능한 문자열들이 생겨나게 될 것이다.</p>
<p>상태공간트리를 만들어서 문제를 푼다면 더욱 쉽게 이해할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[LeetCode] Number of Islands]]></title>
            <link>https://velog.io/@willow_ryu/LeetCode-Number-of-Islands</link>
            <guid>https://velog.io/@willow_ryu/LeetCode-Number-of-Islands</guid>
            <pubDate>Mon, 29 Aug 2022 13:21:20 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://leetcode.com/problems/number-of-islands/">https://leetcode.com/problems/number-of-islands/</a></p>
<p><img src="https://velog.velcdn.com/images/willow_ryu/post/674b6103-5b89-40bf-950a-86518028752c/image.png" alt=""></p>
<h2 id="문제-해석">문제 해석</h2>
<p>간단한 DFS문제이다. (BFS로도 가능하다)
1이면 땅이고, 0이면 물이기 때문에 상하좌우를 탐색해서 1인 녀석들이 존재하지 않을 때까지 반복하고, 한 그룹이 끝났다면 +1을 해준다. 
이런 식으로 반복해서 총 몇개의 그룹이 존재하는지 찾는 문제이다.</p>
<h2 id="문제-풀이">문제 풀이</h2>
<p>나의 경우 처음으로 생각난게 BFS였다.(아직 부족해서 DFS가 바로 떠오르지 않나 보다..)
문제를 보자마자 이차원 배열 내에서 1을 찾는 순간 주변에 몇개가 있는지를 파악하고 전부 0이 된다면 +1 을 한다가 먼저 생각난거 같다.
생각을 조금만 전환한다면, 시작지점을 찾았다면, 상하좌우로 다시한번 너비로 탐색을 한다면 굳이 Queue를 사용하지 않고도 보다 깔끔하게 코드를 작성할 수 있다.</p>
<h2 id="코드">코드</h2>
<pre><code>&lt;BFS&gt;
class Solution {
    // 왼, 상단, 오른쪽, 하단
    private val nx = listOf(-1, 0, 1, 0)
    private val ny = listOf(0, 1, 0, -1)

    fun numIslands(grid: Array&lt;CharArray&gt;): Int {
        var cnt = 0
        // 방문 했는지에 대한 ARRAY가 있으면 좋은게 아니라 필요해 싯팔
        val visited = Array(grid.size) {
            BooleanArray(grid[0].size) { false }
        }

        val queue: Queue&lt;Pair&lt;Int, Int&gt;&gt; = LinkedList&lt;Pair&lt;Int, Int&gt;&gt;()
        for (i in 0 until grid.size) {
            for (j in 0 until grid[i].size) {
                if (grid[i][j] == &#39;1&#39; &amp;&amp; !visited[i][j]) {
                    queue.add(Pair(i, j))
                    visited[i][j] = true

                    while (queue.isNotEmpty()) {
                        val pos = queue.poll()

                        for (k in 0 until 4) {
                            if ((pos.first + nx[k] &lt; 0 || pos.first + nx[k] &gt;= grid.size) || (pos.second + ny[k] &lt; 0 || pos.second + ny[k] &gt;= grid[0].size)){
                                continue
                            }
                            else {
                                if (grid[pos.first + nx[k]][pos.second + ny[k]] == &#39;1&#39; &amp;&amp; !visited[pos.first + nx[k]][pos.second + ny[k]]) {
                                    queue.add(Pair(pos.first + nx[k], pos.second + ny[k]))
                                    visited[pos.first + nx[k]][pos.second + ny[k]] = true
                                }
                            }
                        }
                    }
                    cnt++
                }
            }
        }
        return cnt
    }
}</code></pre><pre><code>&lt;DFS&gt;
class Solution2 {
    fun numIsland(grid: Array&lt;CharArray&gt;): Int {
        if (grid.size == 0) return 0
        var result: Int = 0

        grid.forEachIndexed { i, it -&gt;
            it.forEachIndexed { j, value -&gt;
                if (grid[i][j] == &#39;1&#39;) {
                    result += dfs(grid,i,j)
                }
            }
        }
        return result
    }

    fun dfs(grid: Array&lt;CharArray&gt;, i: Int, j: Int): Int {
        if (i &lt; 0 || j &lt; 0 || i &gt;= grid.size || j &gt;= grid[0].size || grid[i][j] == &#39;0&#39;) {
            return 0
        }
        grid[i][j] = &#39;0&#39;
        dfs(grid,i + 1, j)
        dfs(grid, i - 1, j)
        dfs(grid, i, j - 1)
        dfs(grid, i, j + 1)
        return 1
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Jetpack Compose CheckBox Single Selection]]></title>
            <link>https://velog.io/@willow_ryu/Jetpack-Compose-CheckBox-Single-Selection</link>
            <guid>https://velog.io/@willow_ryu/Jetpack-Compose-CheckBox-Single-Selection</guid>
            <pubDate>Wed, 17 Aug 2022 15:57:32 GMT</pubDate>
            <description><![CDATA[<p>CheckBox를 활용해 LazyColumn에서 아이템을 보여주고 클릭된 상태를 보여주는 기능을 만들었습니다.
과정 중에 CheckBox의 SingleSelection하는 기능을 RecyclerView로는 바로 생각났는데, Compose로 생각을 전환하려니까 바로바로 되지 않았습니다. (머리가 늙은건가;;;)</p>
<blockquote>
<p><strong>아이디어는 단순한 Boolean 값에 의존하는 것이 아니라 SingleSelection의 핵심은 선택한 포지션을 저장한다는 것에 있다는 것을 잊지 않는다는 것입니다.</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/willow_ryu/post/d9b1fbeb-55bb-42f5-8fe4-7a2b0930f645/image.png" alt=""></p>
<p>간단하게 코드 설명을 한다면, Composable Scope 상단에 </p>
<pre><code>var selectedPosition by remember { mutableStateOf(-1) }</code></pre><p>이 선언되어 있는 상태에서 각 아이템의 checkBox마다 선택 시, 선택 index를 담아두는 기능을 달아둡니다.
이후 선택을 표시해야할 경우, selectedPosition == index 인 경우 이므로 위의 코드 처럼 표현을 해준다면, 간단하게 singleSelection을 구현할 수 있습니다.</p>
<p>또한 기존의 리스트를 Flow로 받든, List 형태로 받던, selectedPosition 변수가 선택 index를 가지고 있기 때문에, 리스트 원소 접근에도 문제가 없습니다. </p>
<h2 id="question">Question</h2>
<p>저의 경우 저 selectedPosition 데이터를 Composable이 가져도 된다고 생각합니다. ViewModel에 있는 것보다 말입니다.
ViewModel이 보관해야할 Business Data가 아닌 Ui Data에 가까워서 저는 이렇게 생각하긴 하는데, 다른 분들은 어떻게 생각하는지 댓글 남겨주시면 감사하겠습니다.</p>
<p>물론 반박도 좋습니다!!! 감사합니다.👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[suspendCoroutine, CallbackFlow]]></title>
            <link>https://velog.io/@willow_ryu/suspendCoroutine-CallbackFlow</link>
            <guid>https://velog.io/@willow_ryu/suspendCoroutine-CallbackFlow</guid>
            <pubDate>Tue, 16 Aug 2022 02:16:00 GMT</pubDate>
            <description><![CDATA[<h1 id="서론">서론</h1>
<p>안드로이드는 Callback의 연속이다.
당장 우리가 Default로 앱을 생성하게 되면 가장 먼저 만들어져 있는 것은 callback이다. ( <em>override fun onCreate</em> )
이러한 callback 패턴에는 제약이 많이 생긴다.
Retrofit을 사용한다고 예를 들어보자 Call객체를 활용해서 retrofit객체에 enqueue를 하게 되면 callback으로 우리는 이러한 형태의 로직을 작성하게 된다</p>
<pre><code>object : Callback&lt;Wrapper&lt;Info, Characters&gt;&gt; {
                override fun onResponse(
                    call: Call&lt;Wrapper&lt;Info, Characters&gt;&gt;,
                    response: Response&lt;Wrapper&lt;Info, Characters&gt;&gt;
                ) {
                    // response가 성공이거나 실패이거나 등등의 내용의 Logic
                }

                override fun onFailure(call: Call&lt;Wrapper&lt;Info, Characters&gt;&gt;, t: Throwable) {
                   // 실패했을 경우의 Logic
                }</code></pre><p>이러한 형태가 나쁘다는 것은 아니지만, 머리로 생각한 알고리즘을 코드로 풀어나가면서 부자연스러워진다. (개인적으로 난 그랬다. 이게 편하다면.. 이 다음의 내용은 읽지 않는 것을 추천한다.)
코드로 풀어나가기 힘들다는 게 어떤 의미인지를 생각해보면, callback은 한국말로 표현하자면 _&quot;우리 이런 행동을 하기로 약속하고, 넌 저 약속을 어떻게 실행할지 나에게 보여줘!&quot;_같은 느낌이다. 순전히 내 느낌이다. 
그러나 세상의 요구사항은 그리 편하지 않다. 때론 callback이 아니라 값의 형태로 이를 받아내고 반환된 값으로 연속적으로 다른 행동을 할 수 있도록, 즉 _&quot;이 작업을 하고 그 결과 값을 나에게 줘!!&quot;_를 하고 싶어진다는 것이다.
이러한 표현방법을 할 수 있도록 Kotlin에서는 suspendCoroutine , CallbackFlow을 제공한다.
Coroutine이며, Flow의 기본 지식이 있어야 사용하는데 수월함이 있다.</p>
<blockquote>
<p>이 글은 suspendCoroutine, CallbackFlow를 어떻게 활용할지에 대한 글이지 내부가 어떻게 생겼는지를 얘기하는 글은 아닙니다.
추천하는 블로그 아티클은 <a href="https://tourspace.tistory.com/442?category=797357">https://tourspace.tistory.com/442?category=797357</a> 여기입니다.
굉장히 잘 작성되어 있습니다.</p>
</blockquote>
<h2 id="공통점">공통점</h2>
<p>suspendCoroutine, callbackFlow 둘다 callback의 결과를 반환받을 수 있다. </p>
<h2 id="차이점">차이점</h2>
<p>간단하게 얘기하면
suspendCoroutine -&gt; 단일 객체
callbackFlow -&gt; Flow 스트림을 반환한다.</p>
<p>callback을 진행하면 익명의 객체가 특정 상태일 떄마다 callback을 실행한다. 그때 값을 단 한번만 받아서 사용한다면 suspendCoroutine, 계속해서 callback이 참조된 함수를 호출하며 값이 계속 변할거 같다. 즉, observe 패턴이 적용되는게 더 어울릴거 같을 떄 callbackFlow를 사용한다.</p>
<h2 id="구현">구현</h2>
<h3 id="suspendcoroutine">suspendCoroutine</h3>
<pre><code>    Repository.kt

    suspend fun fetchCharacter(page: Int): Wrapper&lt;Info, Characters&gt;? {
        val result = suspendCancellableCoroutine&lt;Wrapper&lt;Info, Characters&gt;?&gt; { continuation -&gt;
            val callbackImpl = object : Callback&lt;Wrapper&lt;Info, Characters&gt;&gt; {
                override fun onResponse(
                    call: Call&lt;Wrapper&lt;Info, Characters&gt;&gt;,
                    response: Response&lt;Wrapper&lt;Info, Characters&gt;&gt;
                ) {
                    val res = response.body()!!.apply {
                        isNetworkSuccessTag = &quot;success&quot;
                    }
                    continuation.resume(res) {}
                }

                override fun onFailure(call: Call&lt;Wrapper&lt;Info, Characters&gt;&gt;, t: Throwable) {
                    // Wrapper 클래스에 성공과 실패를 나눠줄 수 있는 Value를 둬서 그걸로 Trigger해도 괜찮다,
                    // 대신 더미로 만든다던지 Response의 값이 전부 nullable하게 된다는 단점이 존재한다.
                }
            }
            // Coroutine scope이 cancel 될 때 호출된다.
            continuation.invokeOnCancellation {
                // Thread - safe한 함수만 호출되어야 한다.

            }

            service.fetchCharacter(page).enqueue(callbackImpl)
        }

        return result
    }</code></pre><h3 id="callbackflow">callbackFlow</h3>
<pre><code>Repository.kt

suspend fun fetchCharacterWithFlow(page: Int): Flow&lt;Wrapper&lt;Info, Characters&gt;&gt; =
        callbackFlow&lt;Wrapper&lt;Info, Characters&gt;&gt; {
            val callbackImpl = object : Callback&lt;Wrapper&lt;Info, Characters&gt;&gt; {
                override fun onResponse(
                    call: Call&lt;Wrapper&lt;Info, Characters&gt;&gt;,
                    response: Response&lt;Wrapper&lt;Info, Characters&gt;&gt;
                ) {
                    trySend(response.body()!!.apply { isNetworkSuccessTag = &quot;success&quot; })
                }

                override fun onFailure(call: Call&lt;Wrapper&lt;Info, Characters&gt;&gt;, t: Throwable) {
                    close()
                }
            }

            service.fetchCharacter(page).enqueue(callbackImpl)

            // coroutineScope 이 cancel 또는 close 될때 호출

            // ProducerScope block의 코드의 실행이 완료되고 나서 바로 종료되는 것을 막는 코드.
            // addListener로 특정 이벤트를 관찰하는 겨웅에는 callback이 호출되는 걸 지속적으로 관찰해야 하기 때문에 api를 사용해 지속적으로 callback을 전달 받을 수 있도록 flow를 유지한다.

            // awaitClose는 flow가 cancel되거나 close 될 떄(channel close()가 명시적으로 호출될 때) 해당 블록을 호출
            // 해당 block안에서는 resource를 해제하는 코드가 들어가야한다.
            awaitClose {
                cancel()
            }
        }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[CreationExtras must have a value by `SAVED_STATE_REGISTRY_OWNER_KEY` 이슈]]></title>
            <link>https://velog.io/@willow_ryu/CreationExtras-must-have-a-value-by-SAVEDSTATEREGISTRYOWNERKEY-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@willow_ryu/CreationExtras-must-have-a-value-by-SAVEDSTATEREGISTRYOWNERKEY-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Mon, 15 Aug 2022 17:12:09 GMT</pubDate>
            <description><![CDATA[<h2 id="이슈-발생-현상">이슈 발생 현상</h2>
<p>Navigation과 ViewModel Hoisting을 위해서 Hilt Navigation을 사용했습니다.
또한 이번에 Hilt 버전을 가장 최신인 2.43.2로 올리면서 Kotlin과 Compose 버전을 최신으로 올렸습니다.
버전을 최신으로 바꿈으로써 hiltViewModel()을 사용할 때 위와 같은 에러가 발생하며 앱이 크래쉬 나서 죽는 현상이 발생했습니다.</p>
<h2 id="이슈-원인">이슈 원인</h2>
<p>원인은 찾는대로 글을 갱신하겠습니다.</p>
<h2 id="이슈-해결법">이슈 해결법</h2>
<ul>
<li>androidx.navigation:navigation-compose:${currentVersion}</li>
<li>androidx.fragment:fragment-ktx:${currentVersion}</li>
</ul>
<p>이 2개를 dependency에 추가하게 되면 이슈를 해결할 수 있습니다.</p>
<p>짐작으로는 fragment ktx에서 ViewModel을 만드는 factory가 사용되면서 navigate할 때 주입이 된것으로 보인는데 정확하게는 분석해서 원인란에 적어놓겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Android Wifi plugin]]></title>
            <link>https://velog.io/@willow_ryu/Android-Wifi-plugin</link>
            <guid>https://velog.io/@willow_ryu/Android-Wifi-plugin</guid>
            <pubDate>Sat, 05 Feb 2022 12:06:55 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>개발자들은 편리함을 만들어 내기 위해 공부한다. 나는 이게 항상 모순됐다고 생각했다. <strong>편리함을 위해, 보다 게으르기 위해 부지런히 공부하고 만들어낸다.</strong>
제목에서 _Wifi plugin_이라 붙여서 &#39;플러그인 설정방법&#39;으로 생각할 수 있는데 아쉽지만 이 글은 그런게 아니다.</p>
<p>그렇다면 이글은 무엇인가? 항상 나는 왜 이런게 탄생했을까? 에 대해 궁금해 하는 편이다. 이번 글도 그런것과 비슷하다. 저 플러그인이 왜 탄생했고, 어떤 귀찮은 일을 덜어준것일까? 에 대한 고찰이다.</p>
<h1 id="adb">adb</h1>
<p>안드로이드는 리눅스 기반 &quot;<strong>운영체제</strong>&quot;다. 
안드로이드 프로그래밍을 하면서 Application Layer(그..안드로이드 구성요소 그려진 사진을 생각하면 된다)에서 코딩을 하기에 나는 가끔씩 운영체제임을 놓칠 때가 있다.(그럼 안되는데..)
여튼 운영체제고 리눅스 기반이기에 명령어로써 모든 기능들을 할 수 있다. IO작업 또한 마찬가지이다.
여기서 adb는 안드로이드 장치를 조작할 수 있는 다리의 역할을 하는 녀석이다 원문은 <strong>Android Debug Bridge</strong> 말 그대로 다리다.</p>
<h1 id="wifi-connect">Wifi Connect</h1>
<p>자 기계를 처리하는 명령어를 가진 녀석이 adb라는 것은 알았다.
그렇다면 왜 wifi plugin으로 adb 얘기를 꺼낸 것인가 그걸 알아야 한다.
결론부터 얘기하자면 adb 명령어로 안드로이드 기기를 wifi로 연결할 수 있고, 이를 대신 해주는 것이 plugin이라는 녀석이기 때문이다.
생각보다 방법은 심플하다.</p>
<ol>
<li>안드로이드 터미널을 켜든, 뭐든 터미널을 킨다(리눅스여야 한다. 난 윈도우이기 때문에 터미널에서 켰다)
1-1. terminal은 정확하게 특정 프로젝트를 만들어야만 쓸 수 있기 때문에 프로젝트 앱과 관련된 것들이 세팅되는 것이다.</li>
<li>wifi 연결을 하고 싶은 device가 usb로 컴퓨터와 연결 되어 있어야한다. 처음 한번은 필요하다. 또한 <strong>현재 내 안드로이드 기기가 연결된 wifi의 ip주소를 알아야 한다.</strong></li>
<li><strong>adb devices</strong>로 연결이 되어 있는지 확인</li>
<li><strong>adb tcpip 5555</strong>로 포트번호를 설정한다. (5555가 기본임)</li>
<li><strong>adb connect (wifi-ip)</strong>로 device를 연결한다.
이러면 끝이다.
심플하지 않은가?</li>
</ol>
<h1 id="그렇다면-plugin은-왜">그렇다면 plugin은 왜</h1>
<p>저런 과정으로 adb가 안드로이드 운영체제와 연결이 되지만, 사람이 1개의 프로젝트만 할 수 없는지라 변경될 때마다 저 명령어를 쳐줘야 한다.
이는 usb 연결선을 항상 가지고 다녀야하는 귀찮음도 동반된다.
편할려고 한건데 생각보다 편해지지 않게 된것이다.
그래서 그런 것을 대신 해주는 plugIn이 나온게 아닐까 라고 생각한다.(물론 내 생각이다)</p>
<h1 id="번외">번외</h1>
<p>adb명령어를 최근 자주 쓰게 되면서 안드로이드 apk 빌드 과정에서 재밌는 것을 찾았다.
우린 단순하게 ide에서 삼각형 버튼?을 통해서 기기에 apk를 빌드하는데
결국 그것들은 전부 명령어로써 이루어 질텐데 어떤 명령어로 될까?에 대한 궁금증이였다.
window에서도 해볼 수 있다.</p>
<ol>
<li>apk파일 폴더 경로까지 찾아간다.</li>
<li>Shift + 마우스 우클릭을 통해서 power shell로 shell을 연다</li>
<li>root 권한을 받은 상태에서 adb remount를 통해 기기의 read, write 권한을 얻어온다.</li>
<li>adb push (apk 경로)를 통해서 기기에 apk를 push 해놓는다.</li>
<li>adb shell ps를 사용해 현재 내 앱이 사용하는 process의 id값을 찾는다.(grep으로 찾아도 됨)</li>
<li>adb shell로 root 권한을 해제하고, 찾았던 pid로 프로세스를 죽인다.</li>
<li>그렇게 되면 push 되었던 녀석이 process에 재생될 것이다.
이와 같은 방법으로 안드로이드가 빌드하고 있는게 아닐까 라고 생각했다.</li>
</ol>
<p>꽤 재밌다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스]크레인 인형뽑기 게임(2019 카카오 개발자 인턴십)]]></title>
            <link>https://velog.io/@willow_ryu/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4%ED%81%AC%EB%A0%88%EC%9D%B8-%EC%9D%B8%ED%98%95%EB%BD%91%EA%B8%B0-%EA%B2%8C%EC%9E%842019-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%9D%B8%ED%84%B4%EC%8B%AD</link>
            <guid>https://velog.io/@willow_ryu/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4%ED%81%AC%EB%A0%88%EC%9D%B8-%EC%9D%B8%ED%98%95%EB%BD%91%EA%B8%B0-%EA%B2%8C%EC%9E%842019-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%9D%B8%ED%84%B4%EC%8B%AD</guid>
            <pubDate>Sun, 16 Jan 2022 08:34:24 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/64061">https://programmers.co.kr/learn/courses/30/lessons/64061</a></p>
<h1 id="풀이">풀이</h1>
<p>여러가지 방법이 존재한다고 생각한다
처음에 생각했던 것은 board로 받아내는 것을 stack 리스트로 변경해서 표현하는 방식을 생각도 해봤는데 크기가 최대 30 * 30이라서 이 방법은 바로 접었다.
두번째로 무난하게 떠올랐던 것은 board의 size를 반복하는 i와 moves를 표현하는 j를 사용하여 board[i][j-1]의 값이 0이 아니면 stack에 담는 방법으로 문제를 해결했다.
이때 담는 과정 중에 stack.peek의 값이 같다면 넣지말고 pop하는 식으로 해 While문을 줄여서 사용했다</p>
<h1 id="코드">코드</h1>
<pre><code>// 처음에 생각한 거
fun solution2(board: Array&lt;IntArray&gt;, moves: IntArray): Int {
        var answer = 0
        val stack = Stack&lt;Int&gt;()
        for(i in 0 until moves.size){
            loop@for(j in 0 until board.size) {
                if(board[j][moves[i]-1] != 0){
                    stack.add(board[j][moves[i]-1])
                    board[j][moves[i]-1] = 0
                    break@loop
                }
            }
        }
        while(!stack.isEmpty()){
            val pop = stack.pop()

            if(!stack.isEmpty()){
                if(pop == stack.peek()){
                    answer += 2
                    stack.pop()
                }
            } else {
                break
            }
        }
        return answer
    }</code></pre><pre><code>// stack 연산을 굳이 빼서 하지 않고, board 반복문 내에서 처리
fun solution(board : Array&lt;IntArray&gt;, moves: IntArray) : Int {
        var answer = 0
        val stack = Stack&lt;Int&gt;()
        moves.forEach {
            for(i in 0 until board.size){
                if(board[i][it -1] != 0){
                    if(stack.isNotEmpty() &amp;&amp; stack.peek() == board[i][it-1]){
                        answer += 2
                        stack.pop()
                    } else {
                        stack.push(board[i][it-1])
                    }
                    board[i][it-1] = 0
                    break
                }
            }
        }
        return answer
    }</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스]실패율(Kakao BLIND RECRUITMENT)]]></title>
            <link>https://velog.io/@willow_ryu/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4%EC%8B%A4%ED%8C%A8%EC%9C%A8Kakao-BLIND-RECRUITMENT</link>
            <guid>https://velog.io/@willow_ryu/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4%EC%8B%A4%ED%8C%A8%EC%9C%A8Kakao-BLIND-RECRUITMENT</guid>
            <pubDate>Sun, 16 Jan 2022 08:28:40 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/42889">https://programmers.co.kr/learn/courses/30/lessons/42889</a></p>
<h1 id="풀이">풀이</h1>
<p>문제 자체는 굉장히 심플했다. _<strong>Map</strong>_을 얼마나 자유자재로 사용하는가에 대해서 묻는 질문 같았다.(정작 나도 제대로 못 사용하고 있다;;;)</p>
<ul>
<li>스테이지, 현재 멈춰있는 유저의 수를 담아줄 Map</li>
<li>스테이지, 실패율을 담아두는 Map</li>
</ul>
<p>이렇게 2개를 활용해서 문제를 해결했다.
고민했던 부분은 분수를 어떻게 표현할까였는데, Kotlin의 기초적인 것을 까먹은거 같아서 굉장히 자괴감이 많이 들었던 문제다...</p>
<h1 id="코드">코드</h1>
<pre><code>fun solution(N : Int, stages: IntArray) : IntArray {
        var answer = IntArray(N)
        val m1 = mutableMapOf&lt;Int,Int&gt;() // 스테이지 번호, 현재 멈춰있는 유저 수
        val m2 = mutableMapOf&lt;Int,Double&gt;() // 스테이지, 실패율

        stages.forEach {
            if(m1.containsKey(it)) {
                m1[it] = m1.getValue(it) + 1
            } else {
                m1[it] = 1
            }
        }

        var users = stages.size

        for(i in 1..N) {
            if(m1.containsKey(i)){
                m2[i] = (m1.getValue(i) / users.toDouble()) // 분수 표현식이네요
                users -= m1.getValue(i)
            } else {
                m2[i] = 0.0
            }
        }

        val list = m2.toList().sortedByDescending { (_,value) -&gt;
            value
        }

        for(i in 0 until N){
            answer[i] = list[i].first
        }

        return answer
    }
</code></pre><h1 id="느낀점">느낀점</h1>
<p>분수를 표현하는 방법을 까먹지 말자...;;;
문제 푸는 것에 급급해서 가끔 자료형을 까먹거나 놓치는 경우가 많은거 같다. 이 점 유의해야할 거 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[앱 만드는 일기4]]></title>
            <link>https://velog.io/@willow_ryu/%EC%95%B1-%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B04</link>
            <guid>https://velog.io/@willow_ryu/%EC%95%B1-%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B04</guid>
            <pubDate>Mon, 13 Dec 2021 15:54:18 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>최근 취업준비 때문에 오픈소스 프로젝트를 못했었다(굉장히 슬픈 일이야...😂)
지원서들을 적고나서 일단락 된 거 같아 다시 내 프로젝트들을 보면서 보안할 점이나 새로 만들어볼만한 것을 찾고 있었는데 그 중 ToDoReminder가 오픈소스 사이트에서 소개되고 있는거 같았다.</p>
<blockquote>
<p><strong>앱 링크</strong>
<a href="https://github.com/SSong-develop/ToDoReminder">https://github.com/SSong-develop/ToDoReminder</a></p>
</blockquote>
<blockquote>
<p><strong>웹사이트 링크</strong>
<a href="https://awesomeopensource.com/project/SSong-develop/ToDoReminder">https://awesomeopensource.com/project/SSong-develop/ToDoReminder</a></p>
</blockquote>
<h2 id="느낀-점">느낀 점</h2>
<p>이전 프로젝트가 올라간 적은 있어도 개인 프로젝트가 올라간건 처음인데, 생각해보면 오픈소스 프로젝트는 그냥 올라가도 되는게 맞는게 아닐까 생각한다. 결국 오픈소스니까..동의없이 크롤링 해가는게 맞는거 아닐까 생각한다.
그래도 이렇게 게시되어 유입된 사람도 있는걸 보니 뿌듯했다.
많은 이들이 이 프로젝트를 보고 도움을 받았으면 좋겠다.
그리고 도움 받았으면 좀 흔적으로...Star라던가 issue 같은걸 만들어 줘도 좋을거 같다.(그럼 내가 뿌듯할거 같음)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[BOJ2309]]></title>
            <link>https://velog.io/@willow_ryu/BOJ2309</link>
            <guid>https://velog.io/@willow_ryu/BOJ2309</guid>
            <pubDate>Thu, 28 Oct 2021 07:57:46 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p><a href="https://www.acmicpc.net/problem/2309">https://www.acmicpc.net/problem/2309</a></p>
<h2 id="풀이">풀이</h2>
<h3 id="문제-해결-방법">문제 해결 방법</h3>
<p>첫번째로 9명의 난쟁이 중 가짜는 2명임을 알 수 있으므로 9C2 = 36가지 이므로 2중 for문을 사용해서 해결할 수 있는 문제다.
(난쟁이의 수가 터문이 없이 커진다면 다른 방법을 선택해야하는데, 그 경우는 아직 모르겠다. 댓글로 남겨주면 행복할 거 같습니다.)
순서도를 그려보게 되면
<img src="https://images.velog.io/images/willow_ryu/post/68d4fab2-82af-448c-9c96-e3b6bfcd68cc/flow1.PNG" alt=""></p>
<p>이와같은 방법으로 문제를 해결할 수 있다.</p>
<h3 id="자료구조">자료구조</h3>
<p>두번째로 입력받은 9명의 난쟁이 키 데이터를 어떻게 보관할 것인가이다.
결국엔 2개의 데이터를 제거해야하기 때문에 LinkedList에 데이터를 담는 것이 효과적이라고 생각한다.
하지만, 내가 처음 문제를 풀었을 때는 리스트 2개를 사용해서 문제를 해결했다;;;</p>
<h2 id="코드">코드</h2>
<pre><code>fun solve(){
        val l : ArrayList&lt;Int&gt; = arrayListOf()
        val tempL = arrayListOf&lt;Int&gt;()
        repeat(9){
            val h = readLine()!!.toInt()
            tempL.add(h)
        }
        l.addAll(tempL)

        for(i in 0 until l.size){
            for(j in (i+1) until l.size){
                val v1 = l[i]
                val v2 = l[j]
                l.remove(v1)
                l.remove(v2)
                if(l.sum() == 100){
                    l.sort()
                    l.forEach {
                        println(it)
                    }
                    return
                }else{
                    l.clear()
                    l.addAll(tempL)
                }
            }
        }
    }</code></pre><p>문제를 처음 해결 했을 때는 리스트를 2개 사용했다
알고리즘 자체는 생각을 그대로 표현해서</p>
<ol>
<li><p>리스트에 data 2개 제외</p>
</li>
<li><p>합이 100이 되는가?</p>
</li>
<li><p>된다면 정답출력</p>
</li>
<li><p>안된다면 원본 리스트를 다시 담는다.
이 알고리즘의 문제는 삭제가 빈번히 일어난다는 것이다. 효과적이지 못하다
그래서 두번째로 구현 했을 때는</p>
<pre><code>fun solve2(){
     val l = LinkedList&lt;Int&gt;()
     var total = 0
     repeat(9){
         val h = readLine()!!.toInt()
         total += h
         l.add(h)
     }

     loop@for(i in 0 until l.size - 1){
         for(j in (i+1) until l.size){
             if(total - (l[i] + l[j]) == 100){
                 val v1 = l[i]
                 val v2 = l[2]
                 l.remove(v1)
                 l.remove(v2)
                 break@loop
             }
         }
     }
     l.sort()
     for(element in l){
         println(element)
     }
 }</code></pre><p>LinkedList를 사용하였고, loop@ break@loop를 통해 2중 for문의 중지를 제어하였다.
또한 list의 값을 삭제하고 다시 담는 것이 아니라, total 변수에서 값을 차감해본 후 100이 된다면 list에서 삭제함으로써 삭제가 필요한 경우에만 발생하도록 하였다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[앱만드는 일기3]]></title>
            <link>https://velog.io/@willow_ryu/%EC%95%B1%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B03</link>
            <guid>https://velog.io/@willow_ryu/%EC%95%B1%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B03</guid>
            <pubDate>Wed, 20 Oct 2021 10:29:27 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>일기1에서 만들던 앱에서 부족한 점을 보완해서 만들어 냈다</p>
<blockquote>
<p><strong>앱 링크</strong>
<a href="https://github.com/SSong-develop/RickMorty">https://github.com/SSong-develop/RickMorty</a></p>
</blockquote>
<blockquote>
<p><strong>이전 일기</strong>
<a href="https://velog.io/@willow_ryu/%EC%95%B1%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B01">https://velog.io/@willow_ryu/%EC%95%B1%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B01</a></p>
</blockquote>
<h2 id="만들면서-어려웠던-점">만들면서 어려웠던 점</h2>
<p>Flow와 함수형 언어의 특징을 이용해서 App의 상태에 따른 View를 보여주고자 했다.
그러기 위해 Repository단에서 Flow를 반환하고 그 값을 기존의 값에서 연산자를 통해서 받는 식으로 앱의 구조를 작성해야 했는데, 그 구조를 잡는 과정에서 애를 꽤나 많이 먹었다.
물론 작성하고 나니 작동이 잘되는 거 같아서 다행이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Programmers[소수찾기]]]></title>
            <link>https://velog.io/@willow_ryu/Programmers%EC%86%8C%EC%88%98%EC%B0%BE%EA%B8%B0</link>
            <guid>https://velog.io/@willow_ryu/Programmers%EC%86%8C%EC%88%98%EC%B0%BE%EA%B8%B0</guid>
            <pubDate>Sat, 02 Oct 2021 05:45:51 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<blockquote>
<p><a href="https://programmers.co.kr/learn/courses/30/lessons/42839">https://programmers.co.kr/learn/courses/30/lessons/42839</a></p>
</blockquote>
<h2 id="내-생각">내 생각</h2>
<p>완전탐색의 전형적인 유형이다.
전부 다 해보고 그 중 되는 것들의 갯수를 세어 결과를 반환하는 방법으로 코드를 작성했다.</p>
<h2 id="깨달은-점">깨달은 점</h2>
<p>BooleanArray를 사용한 checking 되는 방법은 잊지않고 상기시켜야 할 거 같다.
재귀 공부는 꾸준히...재귀 공부는 꾸준히...재귀 공부는 꾸준히...
또한 소수 판별을 할 때 Count 변수로 세면 시간초과가 뜬다. 나눠지는 수가 존재했을 때 false가 반환되는 함수 형태로 고쳐서 했더니 바로 문제를 해결할 수 있었다.</p>
<h2 id="코드">코드</h2>
<p>```
class Problem2 {
    private val numberSet = mutableSetOf<Int>()
    lateinit var tempNumbers: String
    lateinit var check: BooleanArray</p>
<pre><code>fun solve(numbers: String): Int {
    var answer = 0
    tempNumbers = numbers
    check = BooleanArray(tempNumbers.length)

    func(0, &quot;&quot;)
    answer = numberSet.size
    return answer
}

fun func(depth: Int, strNum: String) {
    if (depth == tempNumbers.length) {
        var counter = 0
        if (strNum == &quot;&quot;) return
        val number = strNum.toInt()
        if (number == 0) return
        if (number == 1) return

        if(isPrime(number)){
            numberSet.add(number)
        }
        return
    }

    for (i in tempNumbers.indices) {
        if (!check[i]) {
            check[i] = true
            func(depth + 1, strNum + tempNumbers[i])
            check[i] = false
            func(depth + 1, strNum)
        }
    }
}

fun isPrime(number : Int) : Boolean{
    for(i in 2 until number){
        if(number % i == 0) return false
    }
    if(number &lt;= 1) return false

    return true
}</code></pre><p>}</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[앱만드는 일기2]]></title>
            <link>https://velog.io/@willow_ryu/%EC%95%B1%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B02</link>
            <guid>https://velog.io/@willow_ryu/%EC%95%B1%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B02</guid>
            <pubDate>Thu, 30 Sep 2021 06:48:10 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>SOPT 28기 앱잼에서 만들었던 프로젝트인 &quot;DooRiBon&quot; 레포지토리가 어느 웹사이트에 등재된 거 같다.
조금 있으면 릴리즈 관련으로 일을 시작해야해서 프로젝트 확인 차 레포지토리를 보았는데 androidRepo.com 이라는 곳에서 방문한 사람이 존재했다.</p>
<blockquote>
<p><strong>유용한 라이브러리나 Repo들이 모여 있는 거 같은 사이트라 추천은 할 만한거 같다.</strong>
<a href="https://androidrepo.com/">https://androidrepo.com/</a></p>
</blockquote>
<h2 id="나의-느낌">나의 느낌</h2>
<p>사실 좀 깜짝 놀랐다. 해커톤 개념으로 만든 것이라 보안을 철저히 신경쓴 것도 아니였어서 이렇게 사람들이 보게 된다면 보다 코드를 잘 구성할 걸 이라는 생각도 했지만 한편으로 보안에 위배되는 건 아닌가 라는 생각을 하게되었다.
앞으로 개발할 때는 보다 철저히 보안에 신경써서 개발에 임해야겠다는 생각을 하게 됐다.</p>
<blockquote>
<p><strong>앱 링크</strong>
<a href="https://github.com/TeamDooRiBon/DooRi-Android">https://github.com/TeamDooRiBon/DooRi-Android</a></p>
</blockquote>
<h2 id="그래도-언제든-프로젝트를-보고-pr을-날려주는-것은-환영이다">그래도 언제든 프로젝트를 보고 PR을 날려주는 것은 환영이다.</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[앱만드는 일기1]]></title>
            <link>https://velog.io/@willow_ryu/%EC%95%B1%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B01</link>
            <guid>https://velog.io/@willow_ryu/%EC%95%B1%EB%A7%8C%EB%93%9C%EB%8A%94-%EC%9D%BC%EA%B8%B01</guid>
            <pubDate>Wed, 29 Sep 2021 14:02:39 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>RickAndMorty라는 애니메이션을 보고 캐릭터를 설명해주는 api가 있지 않을까 싶었는데 아니나 다를까 쉽게 찾을 수 있었다.
그래서 최근 만드는 프로젝트에서 Flow와 Serialization을 사용한 Retrofit을 사용할 일이 있는데 공부할 겸 만들어 보았다. </p>
<blockquote>
<p><strong>앱 링크</strong>
<a href="https://github.com/SSong-develop/RickAndMorty">https://github.com/SSong-develop/RickAndMorty</a></p>
</blockquote>
<h2 id="만들면서-어려웠던-점">만들면서 어려웠던 점</h2>
<p>이번 프로젝트는 각 브랜치마다 공부하는 기술을 적용해 다른 버전의 앱들을 만드는 걸 목표로 했기 때문에 만드는 모든게 공부다.
LiveData를 Flow로 고치는 데만 오늘 3시간 정도 썼고, 사용하는 법만 알게 된거지 내부 동작은 공부하지 않아서 이 또한 공부해야한다.
또한 UI의 상태에 따른 View의 상태가 변할 수 있도록 처리를 해줘야 하는데 그 부분도 조금 약한거 같아 보완해야할 거 같다.</p>
<h2 id="공부할거">공부할거</h2>
<ol>
<li>Flow 이론 및 사용법</li>
<li>UI 상태 관리</li>
<li>Detail 화면 만들지 않아서 만들어야 함 😂</li>
</ol>
<h2 id="options">Options</h2>
<p>링크를 타고 오신 분들은 언제든 코멘트를 남겨주신다면 감사하겠다!!</p>
<h1 id="come-and-pr"><strong>Come And PR!!!</strong></h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[개인 앱 View 최적화 ]]></title>
            <link>https://velog.io/@willow_ryu/%EA%B0%9C%EC%9D%B8-%EC%95%B1-View-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@willow_ryu/%EA%B0%9C%EC%9D%B8-%EC%95%B1-View-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Mon, 27 Sep 2021 11:12:38 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>개인적으로 <em>ToDoReminder</em> 라는 앱을 만들었는데, 여러 기술과 View를 커스텀하여 만든 첫번째앱이다 보니 많은 시간이 소요되고 있다.</p>
<blockquote>
<p><strong>앱 링크</strong>
<a href="https://github.com/SSong-develop/ToDoReminder">https://github.com/SSong-develop/ToDoReminder</a></p>
</blockquote>
<p>앱에 달력과 Spinner 그외 RecyclerView Item View까지 커스텀해서 들어가다 보니 작업하면 어지럽기 따름이다.
앱을 사용하는데는 큰 문제는 없는데, 문득 과연_ 내가 만든 커스텀뷰는 최적화를 잘 했는가???_ 에 대한 의문점이 생겼고 이를 분석해보았다.</p>
<h3 id="overdraw오버드로">OverDraw(오버드로)</h3>
<p>첫번째로 확인해본 것은 OverDraw다.
오버드로라는 것은 <strong>앱이 같은 프레임 내에서 두번 이상 같은 픽셀을 그릴 때 오버드로가 발생한다.</strong> 그러므로 최대한 이러한 작업이 생기는 구간을 없애주는 것이 중요하다.</p>
<blockquote>
<p><strong>공식문서</strong>
<a href="https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering?hl=ko#profile_rendering">https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering?hl=ko#profile_rendering</a>
보다 자세한 내용은 공식문서를 확인해주세요.</p>
</blockquote>
<p>오버드로 설정을 켜고서 본 결과는 충격적이였다.</p>
<div style="float:left">
     <img src = "https://images.velog.io/images/willow_ryu/post/c398ea90-07f0-4fad-9fb5-c38e08c92fc0/%EC%9E%90%EB%A3%8C1.jpg" width="40%">
</div>

<div style="float:left">
       <img src = "https://images.velog.io/images/willow_ryu/post/6f7acd54-ad2a-4cfb-84c2-d4cdcff6f27d/%EC%9E%90%EB%A3%8C2.jpg" width="40%">
</div>

<p>다시 색상을 봐도 어지럽다.
Coordinator Layout이 들어가는 부분은 4회 이상의 OverDraw가 발생했고, 그외에도 2회,1회가 발생하는 곳들이 존재했고, 그 부분은 거의 RecyclerView에서 일어났다.</p>
<h3 id="hwui">HWUI</h3>
<p>두번째로 HWUI 프로파일링을 사용했다.
이걸 사용하게 되면 화면들이 반응하는 과정을 볼 수 있는데, 이 막대기가 곧 성능을 나타내기도 한다.
이것 또한 굉장히 어지럽다.</p>
<div style="float:left">
     <img src = "https://images.velog.io/images/willow_ryu/post/b1c17814-2fce-48f7-ba83-31c7753eef13/%EC%9E%90%EB%A3%8C3.jpg" width="20%">
</div>
<div style="float:left">
       <img src = "https://images.velog.io/images/willow_ryu/post/9f4fb49b-06e1-4734-a825-50e1597fe3b0/%EC%9E%90%EB%A3%8C4.jpg" width="20%">

<p>  HWUI 프로파일링 막대기에서 나타내는 색상이 어떤 것을 의미하는 지는 위에 있는 공식문서를 확인하면 보다 자세하게 알 수 있다.</p>
<blockquote>
<p><strong>공식문서</strong>
<a href="https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering?hl=ko#profile_rendering">https://developer.android.com/topic/performance/rendering/inspect-gpu-rendering?hl=ko#profile_rendering</a>
보다 자세한 내용은 공식문서를 확인해주세요.</p>
</blockquote>
<p>뷰가 처음 켜질 때 연산되는 내용 중 Vsync 지연과 관련된 내용이 많았고, 이는 과한 작업량을 의미하기도 합니다.
어쩔 수 없이 처음 켜지면 연산을 많이 해야하긴 하지만 어떻게 하면 저 하늘을 뚫을 듯 한 막대기를 줄일 수 있을지 고민해봐야 할 거 같습니다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스윕 라인 알고리즘(Sweep Line Algorithm)]]></title>
            <link>https://velog.io/@willow_ryu/%EC%8A%A4%EC%9C%95-%EB%9D%BC%EC%9D%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98Sweep-Line-Algorithm</link>
            <guid>https://velog.io/@willow_ryu/%EC%8A%A4%EC%9C%95-%EB%9D%BC%EC%9D%B8-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98Sweep-Line-Algorithm</guid>
            <pubDate>Fri, 24 Sep 2021 08:06:17 GMT</pubDate>
            <description><![CDATA[<p>정렬을 사용하여 문제를 푸는 기법중 하나인 스윕 라인 알고리즘이다.</p>
<blockquote>
<p>정렬된 순서대로 처리되는 이벤트의 집합으로 문제를 모델링 하는 방법</p>
</blockquote>
<p>주로 O(N^2)의 시간복잡도를 갖는 방법으로 해결이 불가능하거나, DP를 사용하기에 메모이제이션 해야하는 데이터의 크기가 너무 클때 고려한다.</p>
<p>정렬 후 로직을 처리하므로 O(NlogN)의 시간 복잡도를 가진다.</p>
<h3 id="예제">예제</h3>
<blockquote>
<p> BOJ 2170 선긋기</p>
</blockquote>
<p><a href="https://www.acmicpc.net/problem/2170">https://www.acmicpc.net/problem/2170</a></p>
<h3 id="내-생각">내 생각</h3>
<p>선을 겹쳐서 긋는 것에 대해서는 차이가 없기 때문에 좌표 위치만 확인하면 된다.</p>
<p>선이 어디서 시작하는지가 중요하기 때문에 x좌표를 기준으로 정렬을 진행한다.</p>
<p>이후 처음에 0번째의 left , right를 x와 y좌표를 담아두고, 반복하면서 다음 좌표의 y좌표가 기존의 left 보다 작다면 이는 연결되어 있는 녀석이라고 판단한다.</p>
<p>이 경우가 아니라면 연결되어 있지 않은 선분이라 판단하고, 연결되어 있던 정보를 계산한 후 새로운 x,y 좌표를 담아 다시 선분을 시작한다.</p>
<h3 id="코드">코드</h3>
<pre><code>class BOJ2170 {
    fun solve(){
        val n = readLine()!!.toInt() // 선 그은 횟수 , 최대 1,000,000임
        val list = mutableListOf&lt;Pair&lt;Int,Int&gt;&gt;()

        var left = (-1e9).toInt()
        var right = (-1e9).toInt()
        var result = 0

        repeat(n){
            val (x,y) = readLine()!!.split(&#39; &#39;).map { it.toInt() }
            list.add(Pair(x,y))
        }

        list.sortBy { it.first }

        for(i in 0 until n){
            // 만약 , x좌표가 기존의 right보다 작다면 , 이는 합칠 수 있는 선분임
            if(list[i].first &lt; right){
                right = max(right,list[i].second) // 합침
            } else { // 합칠 수 없는 선분이라면 , 혹은 반복문의 첫번째 단계라면
                result += (right - left) // 그 전까지의 선분들을 전부 계산하고
                left = list[i].first // 새로운 왼쪽과
                right = list[i].second // 새로운 오른쪽으로 갱신후 다시 작업을 진행한다
            }
        }

        result += right - left
        println(result)
    }
}</code></pre><h3 id="깨달은-점">깨달은 점</h3>
<p>그리디 알고리즘을 하다보면 정렬한 후에 작업을 해야하는 경우가 있는데 정렬에 대해서 좀 더 공부가 필요하다고 생각했다.</p>
<p>시간복잡도에 보다 신경을 써야할 거 같다.</p>
<p>이와 관련된 문제는 쉽지 않다...😂</p>
]]></description>
        </item>
    </channel>
</rss>