<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>day_park_94.log</title>
        <link>https://velog.io/</link>
        <description>#Mobile #Android #iOS #Flutter #개발문화</description>
        <lastBuildDate>Wed, 01 Jul 2026 13:29:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>day_park_94.log</title>
            <url>https://velog.velcdn.com/images/day_park_94/profile/da950047-def5-4134-8dbc-ead359049330/image.svg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. day_park_94.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/day_park_94" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[OS 트러블 슈팅] Android 15~16 WebView Cold Start 크래시 분석 및 대응]]></title>
            <link>https://velog.io/@day_park_94/OS-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-Android-1516-WebView-Cold-Start-%ED%81%AC%EB%9E%98%EC%8B%9C-%EB%B6%84%EC%84%9D-%EB%B0%8F-%EB%8C%80%EC%9D%91</link>
            <guid>https://velog.io/@day_park_94/OS-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-Android-1516-WebView-Cold-Start-%ED%81%AC%EB%9E%98%EC%8B%9C-%EB%B6%84%EC%84%9D-%EB%B0%8F-%EB%8C%80%EC%9D%91</guid>
            <pubDate>Wed, 01 Jul 2026 13:29:22 GMT</pubDate>
            <description><![CDATA[<p>안녕하세요. 최근 Android 16 업데이트 기기(특히 삼성 One UI 8.0 기반 기기)에서 <strong>Cold Start 시 WebView 초기화 단계에서 앱이 즉시 종료(FC)</strong>되는 크래시 이슈를 마주했습니다. </p>
<p>Play Console Vitals 상에서 크래시 지수가 <strong>평소 대비 약 150,000% 급등</strong>할 정도로 심각했던 사안이었는데요. 앱 자체의 코드 변경이 없었던 상황에서 어떤 가설을 세우고 원인을 추적했는지, 그리고 제조사 기술 문의와 앱 레벨에서의 방어 코드 작성 과정을 공유합니다.</p>
<p><img src="https://velog.velcdn.com/images/day_park_94/post/0d817882-e7ed-4a04-aea9-3a639645caeb/image.png" alt=""></p>
<hr>
<h2 id="1-문제-현상">1. 문제 현상</h2>
<p>Android 16 업데이트 기기에서 프로세스가 새로 시작되는 Cold Start 시점에 앱이 즉시 종료되는 현상이 감지되었습니다.</p>
<ul>
<li><strong>크래시 로그:</strong> <code>android.app.ActivityThread.performLaunchActivity</code> 단계에서 <code>InflateException</code> 및 <code>NotFoundException</code> 발생</li>
<li><strong>발생 시점:</strong> 레이아웃 XML에 선언된 커스텀 WebView가 인플레이션(Inflation)되어 즉시 초기화될 때 발생</li>
<li><strong>특이 사항:</strong> &#39;영향을 받는 사용자&#39;와 &#39;이벤트&#39;가 1:1로 매칭되며, 대개 OS 업데이트 후 최초 1회성으로 집중 발생</li>
</ul>
<hr>
<h2 id="2-가설-수립-및-정밀-분석">2. 가설 수립 및 정밀 분석</h2>
<p>처음 로그를 파악했을 때 <code>ResourcesImpl</code> 리다이렉션 키워드가 눈에 띄었습니다. 앱 코드는 동일한 상태에서 OS 버전이나 기기 환경에 따라 현상이 갈렸기 때문에, 내부 로직 결함보다는 <strong>OS 프레임워크 및 리소스 매핑의 가설</strong>을 세우고 접근했습니다.</p>
<h3 id="🔍-진짜-원인-android-16의-리소스-참조-위반-정책-강화">🔍 진짜 원인: Android 16의 리소스 참조 위반 정책 강화</h3>
<p>구글 및 제조사 기술 문의를 통해 확인한 정확한 메커니즘은 다음과 같았습니다.</p>
<ol>
<li><strong>GMS(Google Mobile Services) 업데이트</strong>가 런타임에 일어날 때, 앱이 갱신된 리소스 경로를 즉각 반영하지 못하고 과거의 경로를 참조하는 일시적인 위반(<code>Violation</code>) 현상이 발생합니다.</li>
<li>Android 15 이전까지는 이런 Race Condition이 발생해도 유연하게 넘어갔으나, <strong>Android 16부터는 리소스 참조 위반 정책이 강화</strong>되었습니다.</li>
<li>결과적으로 <code>ResourcesImpl</code> 리다이렉션 실패 시 예외 처리 없이 프레임워크 단에서 <strong>앱 프로세스를 즉시 크래시(FC)</strong> 처리해 버리는 것이 문제의 본질이었습니다.</li>
</ol>
<hr>
<h2 id="3-단계별-대처-과정">3. 단계별 대처 과정</h2>
<p>문제 포착부터 제조사 소통, 내부 데이터 검증까지의 대처 흐름입니다.</p>
<ul>
<li><strong>[Phase 1] 이슈 최초 포착 및 리포트 (2025.09 ~ 12)</strong>
Android 16 베타 사용자 유입 시점에 문제를 먼저 감지하여 <strong>Google Issue Tracker에 최초 리포트를 등록</strong>하고 장기 모니터링에 착수했습니다.</li>
<li><strong>[Phase 2] 기술 문의 및 정밀 분석 (2026.01 ~ 02)</strong>
정식 업데이트 기기가 늘어나며 크래시가 급증하자, <strong>삼성 개발자 지원팀에 1차 기술 문의</strong>를 진행했습니다. 단순 앱 오류가 아님을 설명하기 위해 과거 관련 QA 리포트(<code>P251010-03077</code>) 분석 데이터를 근거로 제시했습니다.</li>
<li><strong>[Phase 3] 로그 제출 및 이슈 공식화 (2026.03)</strong>
직접 채득한 <code>dumpstate</code> 로그를 함께 제출하여 기술 문의를 구체화했고, 플랫폼 단의 리소스 매핑 결함이라는 최종 피드백을 확인받았습니다.</li>
<li><strong>[Phase 4] 효과 검증 (2026.03 ~ 현재)</strong>
제조사의 수정 패치(One UI 8.5 이상)가 반영된 기기들을 위주로 <strong>Play Console 데이터를 모니터링하여 크래시가 해소된 것을 확인</strong>했습니다.</li>
</ul>
<hr>
<h2 id="4-해결-방법-및-방어-코드">4. 해결 방법 및 방어 코드</h2>
<p>제조사 펌웨어 업데이트를 통해 근본적으로 해소되는 문제이지만, 사용자의 OS 업데이트 지연이나 파편화 환경을 고려하여 <strong>앱 레벨에서 안전하게 구동될 수 있도록 방어 코드</strong>를 추가했습니다.</p>
<h3 id="application-레벨-방어-코드">Application 레벨 방어 코드</h3>
<p>Application 클래스의 <code>onCreate()</code> 시점에 GMS 패키지의 런타임 의존성을 강제로 확인하도록 유도하는 방식입니다. 이렇게 하면 GMS 업데이트가 발생하더라도 앱 프로세스가 올바른 리소스 컨텍스트를 즉시 갱신하거나 안정적으로 재시작할 수 있는 환경을 만들어 줍니다.</p>
<pre><code class="language-kotlin">@SuppressLint(&quot;CheckResult&quot;)
override fun onCreate() {
    super.onCreate()
    initGmsPackageDependency() // GMS 의존성 컨텍스트 갱신 유도
    // ..기존 초기화 로직
}

/**
 * Android 16 대응 GMS 패키지 의존성 강제 등록
 * 런타임 리소스 참조 위반(Violation)으로 인한 크래시 방지용 방어 코드
 */
private fun initGmsPackageDependency() {
    // Android 16(SDK 36) 환경에서만 작동하도록 제한
    if (Build.VERSION.SDK_INT != Build.VERSION_CODES.V) { 
        DevLog.d(&quot;[Application] GMS 의존성 등록 불필요 - SDK: ${Build.VERSION.SDK_INT}&quot;)
        return
    }

    try {
        // GMS 패키지 명을 기반으로 컨텍스트를 강제 로드하여 컨텍스트 갱신 유도
        createPackageContext(
            &quot;com.google.android.gms&quot;, // GMS_PACKAGE_NAME
            CONTEXT_IGNORE_SECURITY or CONTEXT_INCLUDE_CODE
        )
        DevLog.d(&quot;[Application] GMS 패키지 의존성 등록 성공&quot;)
    } catch (e: Throwable) {
        DevLog.d(&quot;[Application] GMS 패키지 의존성 등록 실패: ${e.message}&quot;)
    }
}
</code></pre>
<p>마치며
새로운 안드로이드 플랫폼이 출시될 때마다 강화되는 리소스/보안 정책은 종종 예측하기 어려운 사이드 이펙트를 만들어내곤 합니다.</p>
<p>이번 이슈는 원인 불명의 크래시 앞에서 정확한 로그(dumpstate) 분석과 기술 문의 과정을 통해 플랫폼 단의 변화를 빠르게 캐치하고, 앱 레벨에서 유연하게 대응할 수 있는 대응책을 구축해 냈다는 점에서 의미 있는 경험이었습니다. targetSdk 36 전환이나 Android 16 환경을 대응하고 계신 개발자분들께 작은 도움이 되었으면 좋겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[싱글톤 패턴(Singletone Pattern)은 언제나 옳다?]]></title>
            <link>https://velog.io/@day_park_94/%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4Singletone-Pattern%EC%9D%80-%EC%96%B8%EC%A0%9C%EB%82%98-%EC%98%B3%EB%8B%A4</link>
            <guid>https://velog.io/@day_park_94/%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4Singletone-Pattern%EC%9D%80-%EC%96%B8%EC%A0%9C%EB%82%98-%EC%98%B3%EB%8B%A4</guid>
            <pubDate>Fri, 19 May 2023 06:53:02 GMT</pubDate>
            <description><![CDATA[<h2 id="1-개요">1. 개요</h2>
<blockquote>
<p><strong>A:</strong> &quot;싱글톤 패턴은 무엇인가요?&quot;
<strong>B:</strong> &quot;객체를 단 하나의 인스턴스로만 생성하여, 프로세스가 돌아가는 동안 전역에서 접근할 수 있도록 하는 디자인 패턴입니다.&quot;
<strong>A:</strong> &quot;그렇다면 이 패턴의 위험성이나 단점에 대해 말씀해주세요.&quot;
<strong>B:</strong> &quot;...&quot;</p>
</blockquote>
<p>개발자라면 누구나 싱글톤 패턴을 다뤄봤거나 알고 있을 것입니다. </p>
<p>하지만 <strong>&quot;싱글톤 패턴의 위험성을 충분히 고민하고 쓰고 계시나요?&quot;</strong>라는 질문에는 선뜻 답하지 못하는 경우가 많습니다. 편리함 속에 가려진 싱글톤의 이면과, 올바른 구현 방식에 대한 고민을 기록으로 남겨둡니다.</p>
<hr>
<h2 id="2-싱글톤-패턴이란">2. 싱글톤 패턴이란?</h2>
<p>전 세계 개발자들의 교과서인 <em>&#39;Head First: Design Pattern&#39;</em>에서는 싱글톤을 다음과 같이 정의합니다.</p>
<blockquote>
<p><strong>싱글톤 패턴(Singleton Pattern)</strong>은 해당 클래스의 인스턴스가 하나만 만들어지고, 어디서든 그 인스턴스에 접근할 수 있도록 하기 위한 패턴이다.</p>
</blockquote>
<h3 id="왜-사용할까-장점">왜 사용할까? (장점)</h3>
<ul>
<li><strong>생성 코스트 절감:</strong> 객체를 생성할 때마다 힙(Heap) 영역에 리소스를 할당해야 합니다. 인스턴스 생성 비용이 크거나 공유해야 하는 리소스라면, 데이터(Data) 영역에 단 한 번만 할당하여 성능상 이점을 얻을 수 있습니다.</li>
<li><strong>공유 리소스 관리 및 접근:</strong> 데이터베이스 연결 pool, 로깅, 전역 설정 정보 등 앱 전반에서 일관된 상태를 유지해야 하는 시스템을 효율적으로 관리할 수 있습니다.</li>
</ul>
<hr>
<h2 id="3-싱글톤-패턴의-단점과-위험성">3. 싱글톤 패턴의 단점과 위험성</h2>
<p>사용하기엔 참 편리하지만, 오남용 시 객체지향 아키텍처를 망치기도 합니다.</p>
<ol>
<li><strong>높은 결합도와 OCP 위배:</strong> 다른 클래스들이 이 전역 객체를 직접 참조하게 되면서 클래스 간 결합도가 극도로 높아집니다. 이는 시스템 확장을 어렵게 만들어 <strong>개방-폐쇄 원칙(OCP, Open-Closed Principle)</strong>을 위배하는 원인이 됩니다.</li>
<li><strong>테스트의 어려움:</strong> 전역 상태를 공유하기 때문에 독립적인 단위 테스트(Unit Test)를 수행하기 어렵습니다. 매 테스트마다 상태를 초기화해 주어야 하는 번거로움이 생깁니다.</li>
<li><strong>멀티스레드 환경에서의 원자성 붕괴:</strong> 동기화 처리가 제대로 되지 않으면, 인스턴스가 단 하나만 존재해야 한다는 대전제가 깨지고 <strong>n개의 인스턴스가 생성되는 치명적인 결함</strong>이 발생할 수 있습니다.</li>
</ol>
<hr>
<h2 id="4-kotlin에서의-싱글톤-구현과-한계-분석">4. Kotlin에서의 싱글톤 구현과 한계 분석</h2>
<p>멀티스레드 환경(Thread-Safe)을 고려하며 발전해 온 싱글톤 구현 기법들의 장단점을 짚어보겠습니다.</p>
<h3 id="①-무늬만-싱글톤-잘못된-예시">① 무늬만 싱글톤 (잘못된 예시)</h3>
<p><code>companion object</code> 내에서 호출할 때마다 새로운 객체를 반환한다면 싱글톤의 정의가 무너집니다.</p>
<pre><code class="language-kotlin">class FakeSingleton private constructor() {
    companion object {
        // 호출할 때마다 새로운 객체를 인스턴스화함
        fun getInstance(): FakeSingleton = FakeSingleton()
    }
}</code></pre>
<h3 id="②-synchronized-thread-safe-하나-성능-저하">② Synchronized (Thread-Safe 하나 성능 저하)</h3>
<p>@Synchronized 키워드나 Double-Checked Locking을 사용하여 스레드 안전성을 보장하는 방식입니다.</p>
<pre><code class="language-Kotlin">class SynchronizedSingleton private constructor() {
    companion object {
        @Volatile
        private var instance: SynchronizedSingleton? = null

        @Synchronized
        fun getInstance(): SynchronizedSingleton {
            if (instance == null) {
                instance = SynchronizedSingleton()
            }
            return instance!!
        }
    }
}</code></pre>
<p>단점: Synchronized 블록은 동기화 락을 획득하는 과정에서 많은 비용(Cost)을 소모하므로 성능 저하의 주원인이 됩니다.</p>
<h3 id="③-kotlin-by-lazy-기법-위임-활용">③ Kotlin by lazy 기법 (위임 활용)</h3>
<p>호출되는 시점에 초기화(Lazy Initialization)되며, 코틀린 내부적으로 스레드 동기화(Thread-safe)를 기본 보장합니다.</p>
<pre><code class="language-Kotlin">class LazySingleton private constructor() {
    companion object {
        val instance: LazySingleton by lazy { LazySingleton() }
    }
}</code></pre>
<p>특징: 기본적으로 LazyThreadSafetyMode.SYNCHRONIZED로 동작하므로 멀티스레드 환경에서도 안전합니다. (다만 모드를 명시적으로 변경 시 원자성이 깨질 수 있습니다.)</p>
<h3 id="④-bill-pugh-solution-initialization-on-demand-holder-기법">④ Bill Pugh Solution (Initialization-on-demand holder 기법)</h3>
<p>JVM의 클래스 로딩 시점을 이용한 전통적이고 가장 우수한 기법입니다. JVM이 클래스를 초기화하는 과정에서 원자성을 보장하는 특성을 활용합니다.</p>
<pre><code class="language-Kotlin">class HolderSingleton private constructor() {
    companion object {
        val instance: HolderSingleton
            get() = Holder.INSTANCE
    }

    private object Holder {
        val INSTANCE = HolderSingleton()
    }
}</code></pre>
<p>장점: HolderSingleton 클래스가 로드될 때는 Holder가 모르는 상태였다가, instance가 최초로 호출되는 시점에만 내부 Holder 클래스가 로드되며 인스턴스가 생성됩니다. 구조적으로 Thread-safe 하며 성능 저하가 없습니다.</p>
<h2 id="5-결론-어떻게-써야-할까">5. 결론: 어떻게 써야 할까?</h2>
<p>가장 좋은 방법은 코틀린이 언어 차원에서 제공하는 object 키워드를 사용하거나, 수동 싱글톤보다는 Hilt, Koin 같은 DI(의존성 주입) 프레임워크를 활용하여 객체의 생명주기를 컨테이너가 싱글톤으로 관리하도록 위임하는 것입니다.</p>
<p>객체를 싱글톤으로 설계하기 전, &quot;이 객체가 정말 전역 상태를 가져야만 하는가?&quot;를 스스로에게 꼭 질문해 보아야 합니다. 구조에 대한 깊은 고민 없는 싱글톤의 무분별한 사용은 결국 거대한 기술 부채로 돌아온다는 점을 명심해야겠습니다.</p>
]]></description>
        </item>
    </channel>
</rss>