Google Play에서 다운로드 App Store에서 다운로드

Unity/IT 관련 자료

Unity Android 빌드 최적화를 위한 Proguard 자동화 최종 가이드: 의존성 분석부터 안정적인 Minify 적용까지

정보처리마법사 2025. 8. 15. 16:52
반응형

Unity Android 빌드 최적화를 위한 Proguard 자동화 최종 가이드: 의존성 분석부터 안정적인 Minify 적용까지

Unity Proguard 규칙 최적화 가이드: Minify라는 이름의 야수 길들이기 

 

Unity 개발자라면 누구나 한 번쯤은 겪어봤을 딜레마가 있습니다. 안드로이드 빌드 설정에서 Minify 옵션을 활성화하는 순간입니다. APK 크기를 줄이고, 코드를 난독화하여 보안을 강화하며, 전반적인 성능을 개선할 수 있다는 약속은 매력적입니다. 하지만 이 약속 뒤에는 종종 ClassNotFoundException이나 NoSuchMethodError와 같은 암호 같은 빌드 실패 메시지, 혹은 더 교활하게 런타임에만 발생하는 원인 불명의 충돌이 도사리고 있습니다.1

이러한 문제에 직면한 개발자들은 종종 Minify 옵션을 비활성화하고 최적화의 이점을 포기하거나, 시행착오를 거듭하며 인터넷에서 찾은 불완전한 Proguard 규칙 조각들을 proguard-user.pro 파일에 채워 넣는 불안정한 해결책에 의존하게 됩니다. 이는 근본적인 해결책이 아닌, 문제 발생 후 대처하는 반응적인 접근 방식에 불과합니다.

본 기술 보고서는 이러한 악순환을 끊고, Unity 개발자가 Minify 기능을 자신감 있게 활용할 수 있도록 돕는 것을 목표로 합니다. 문제의 본질은 Minify 기능 자체의 결함이 아니라, 정적 코드 분석 도구(Proguard/R8)와 동적 코드 패턴이 만연한 Unity 프로젝트 및 서드파티 SDK 환경 사이의 예측 가능한 불일치에 있습니다. 따라서 본 보고서는 반응적인 디버깅에서 벗어나, 프로젝트의 의존성을 기반으로 체계적이고 선제적으로 Proguard 규칙을 생성하는 전문적인 워크플로우를 제시합니다.

핵심 논지는 다음과 같습니다. 프로젝트의 핵심 의존성 파일인 packages-lock.json, mainTemplate.gradle, AndroidManifest.xml을 법의학적 수준으로 정밀하게 분석함으로써, 개발자는 프로젝트의 포괄적인 "의존성 맵(Dependency Map)"을 구축할 수 있습니다. 그리고 이 맵을 활용하여, 첫 번째 Minify 빌드를 시도하기 전에 이미 거의 완벽에 가까운 proguard-user.pro 파일을 생성할 수 있습니다. 이 가이드를 통해 개발자는 더 이상 Minify를 두려움의 대상이 아닌, 앱의 품질을 한 단계 끌어올리는 강력한 도구로 활용할 수 있게 될 것입니다.

 

섹션 1: 코드 축소의 원리와 충돌의 해부학

 

Proguard/R8으로 인한 실패의 근본 원인을 이해하는 것은 효과적인 해결책을 수립하기 위한 첫걸음입니다. 이 섹션에서는 코드 축소 도구의 작동 방식을 심층적으로 분석하고, 왜 Unity 프로젝트 환경에서 런타임 충돌이 빈번하게 발생하는지에 대한 이론적 토대를 마련합니다.

 

1.1. Proguard/R8의 세 가지 핵심 임무

 

최신 안드로이드 빌드 시스템에 통합된 Proguard와 그 후속 도구인 R8은 단순히 코드를 제거하는 것을 넘어, 앱의 품질을 다각도로 향상시키는 세 가지 주요 기능을 수행합니다.5

 

1.1.1. 축소 (Shrinking / Tree Shaking)

 

축소는 Proguard/R8의 가장 기본적인 기능으로, 정적 코드 분석을 통해 앱의 최종 빌드에서 실제로 사용되지 않는 코드를 식별하고 제거하는 과정입니다. 이 과정은 '트리 쉐이킹(Tree Shaking)'이라는 비유로 설명될 수 있습니다. 빌드 시스템은 앱의 진입점(Entry Point)부터 시작하여 코드의 호출 그래프를 따라가며 도달 가능한 모든 클래스와 메서드를 표시합니다. 안드로이드 앱의 경우, AndroidManifest.xml에 선언된 Activity, Service, BroadcastReceiver 등이 주요 진입점이 됩니다. 이 분석 과정이 끝나면, 표시되지 않은, 즉 어떤 진입점에서도 도달할 수 없는 코드는 '죽은 코드(Dead Code)'로 간주되어 최종 DEX 파일에서 제거됩니다.7 예를 들어,

main 메서드에서 greeting 메서드를 호출하지만 unused 메서드는 어디에서도 호출하지 않는 코드가 있다면, R8은 main에서 시작하여 greeting까지 추적한 후, 도달하지 못한 unused 메서드를 제거합니다.7 이 과정을 통해 앱의 크기가 현저히 줄어들고, 이는 더 빠른 다운로드 시간과 더 적은 기기 저장 공간 점유로 이어집니다.

 

1.1.2. 최적화 (Optimization)

 

최적화는 코드 축소를 넘어, 남아있는 코드의 바이트코드 자체를 더 효율적으로 재작성하는 단계입니다. R8은 다양한 최적화 기법을 적용하여 런타임 성능을 개선하고 앱 크기를 추가로 줄입니다. 예를 들어, 특정 조건에서 항상 실행되지 않는 코드를 제거하거나(Dead Code Elimination), 작은 메서드의 본문을 호출 위치에 직접 삽입하여 메서드 호출 오버헤드를 없애고(Inlining), 사용되지 않는 메서드 매개변수를 제거하며, 구조가 유사한 클래스를 하나로 병합하는 등의 작업을 수행합니다.7 이러한 최적화는 최종 사용자에게 더 빠른 앱 시작 시간과 부드러운 런타임 성능을 제공하는 데 기여합니다.6

 

1.1.3. 난독화 (Obfuscation / Identifier Renaming)

 

난독화는 클래스, 메서드, 필드와 같은 식별자의 이름을 a, b, c와 같이 짧고 의미 없는 이름으로 변경하는 과정입니다.5 이 작업은 두 가지 중요한 목적을 가집니다. 첫째, 식별자 이름이 짧아지면서 DEX 파일 내의 문자열 풀(String Pool) 크기가 감소하여 APK 크기가 추가로 줄어듭니다. 둘째, 코드의 논리를 파악하기 어렵게 만들어 리버스 엔지니어링을 통한 앱의 내부 로직 분석이나 해킹 시도를 현저히 어렵게 만듭니다. 이는 앱의 지적 재산을 보호하는 중요한 보안 계층 역할을 합니다.

 

1.2. 정적 분석의 맹점: 런타임 재앙의 근본 원인

 

Proguard/R8의 강력한 기능에도 불구하고, 이들이 수행하는 정적 분석에는 근본적인 한계가 존재합니다. 바로 컴파일 시점에는 파악할 수 없는, 런타임에 동적으로 결정되는 코드 호출 패턴을 인지하지 못한다는 점입니다. 이 '맹점'이 바로 Minify 활성화 시 발생하는 대부분의 런타임 충돌의 근원입니다.

이 현상을 이해하는 가장 좋은 방법은 Proguard/R8과 Unity 프로젝트 사이의 "언어 장벽"으로 생각하는 것입니다. Proguard/R8은 정적인 컴파일 타임의 자바 바이트코드 분석이라는 언어를 사용합니다. 반면, 서드파티 네이티브 SDK가 포함된 Unity 프로젝트는 동적이며, C#, C++, 자바 등 여러 언어가 혼재하고, 런타임에 의존하는 방언을 사용합니다. Proguard 규칙(-keep)은 이 두 세계 사이의 간극을 메우는 "번역 사전"과 같습니다. 이 사전이 없다면, 둘 사이의 소통은 런타임 예외라는 파국으로 치닫게 됩니다. 따라서 개발자의 역할은 버그를 수정하는 것이 아니라, Proguard에게 컴파일 시점에는 보이지 않는 런타임의 연결고리를 알려주는 "정적 분석 감독관"이 되는 것입니다.

 

1.2.1. 리플렉션 (Reflection)

 

리플렉션은 런타임에 클래스, 메서드, 필드 등을 문자열 이름을 통해 동적으로 조회하고 호출하는 강력한 기능입니다. JSON 직렬화/역직렬화 라이브러리(예: Gson, Newtonsoft.Json)나 의존성 주입 프레임워크에서 널리 사용됩니다. 예를 들어, {"name":"player1", "score":100}라는 JSON 문자열을 PlayerData 클래스 객체로 변환할 때, 라이브러리는 리플렉션을 사용하여 "name"이라는 문자열에 해당하는 PlayerData.name 필드를 찾아 값을 할당합니다.7

문제는 Proguard/R8이 컴파일 시점에 코드를 분석할 때, "com.example.MyModel"이라는 문자열이 com.example.MyModel 클래스를 사용한다는 것을 알 수 없다는 점입니다. 정적 분석기는 코드상에 명시적인 new MyModel()이나 MyModel.someMethod() 호출이 없으면 해당 클래스가 사용되지 않는다고 판단하고 제거해버립니다. 그 결과, 런타임에 리플렉션을 통해 해당 클래스를 찾으려 할 때 ClassNotFoundException이 발생하며 앱이 충돌합니다.1

 

1.2.2. 자바 네이티브 인터페이스 (JNI)

 

Unity는 IL2CPP 또는 Mono를 통해 C# 코드를 네이티브 코드로 컴파일하고, 이는 안드로이드 플랫폼의 자바/코틀린 코드와 상호작용하기 위해 JNI를 사용합니다. 즉, C# 코드에서 안드로이드 네이티브 라이브러리의 자바 메서드를 호출하거나, 반대로 자바 코드에서 C#의 특정 메서드를 호출할 수 있습니다.

이 과정 역시 Proguard의 정적 분석 맹점에 해당합니다. 만약 myNativeCallback()이라는 자바 메서드가 오직 C++(IL2CPP) 코드에서만 호출된다면, Proguard의 자바 중심 분석에서는 이 메서드에 대한 어떤 호출도 찾을 수 없습니다. 따라서 이 메서드는 '사용되지 않는 코드'로 분류되어 제거됩니다. 이후 런타임에 C++ 코드가 JNI를 통해 이 메서드를 호출하려고 시도하면, 이미 제거된 메서드를 찾을 수 없으므로 NoSuchMethodError가 발생하게 됩니다.9

 

1.2.3. 동적 리소스 로딩

 

안드로이드에서는 resources.getIdentifier()와 같은 메서드를 사용하여 런타임에 동적으로 생성된 문자열 이름으로 리소스를 조회할 수 있습니다. 예를 들어, String iconName = "icon_" + level;과 같이 문자열을 조합한 후, 이 이름에 해당하는 드로어블 리소스를 찾는 경우가 있습니다.10 Proguard/R8은 이러한 동적인 문자열 조합 로직을 따라갈 수 없기 때문에, 정적 분석 과정에서 해당 리소스들이 코드에서 직접 참조되지 않는다고 판단하여 제거할 수 있습니다. 이는 런타임에 리소스를 찾지 못하는 문제로 이어질 수 있습니다.

 

1.2.4. 서드파티 SDK의 복잡성

 

이 문제는 서드파티 SDK를 사용할 때 더욱 복잡해집니다. 광고, 인앱 결제, 분석, 소셜 로그인 등을 위한 대부분의 SDK는 내부적으로 리플렉션과 JNI를 광범위하게 사용하며, 종종 소스 코드가 공개되지 않은 상태로 제공됩니다. Unity 프로젝트에 이러한 SDK를 통합하면, 개발자는 자신이 작성하지 않은 코드에서 발생하는 정적 분석의 맹점까지 책임져야 하는 상황에 놓입니다.1 SDK 제작사가 권장 Proguard 규칙을 제공하는 경우가 많지만, 그렇지 않거나 여러 SDK가 복합적으로 얽혀있는 경우, 개발자는 직접 문제의 원인을 파악하고 규칙을 작성해야만 합니다.

 

섹션 2: 의존성 기반 규칙 생성을 위한 법의학적 접근법

 

Minify로 인한 문제를 해결하는 열쇠는 프로젝트의 의존성을 체계적으로 분석하여 Proguard/R8에게 필요한 정보를 선제적으로 제공하는 데 있습니다. 이 섹션에서는 packages-lock.json, mainTemplate.gradle, AndroidManifest.xml이라는 세 가지 핵심 파일을 분석하여 프로젝트의 완전한 "의존성 맵"을 구축하고, 이를 바탕으로 정확한 Proguard 규칙을 추론하는 구체적인 워크플로우를 제시합니다.

이 세 파일은 Unity-안드로이드 프로젝트에서 의존성이 주입되는 각기 다른 계층을 대표합니다. packages-lock.json은 Unity 패키지 매니저(UPM)를 통해 관리되는 "Unity 네이티브" 세계를 보여줍니다. mainTemplate.gradle은 Unity 외부 의존성 관리 도구(EDM4U 등)를 통해 연결되는 "안드로이드 네이티브" 빌드 세계를 드러냅니다. 마지막으로 AndroidManifest.xml은 최종적으로 안드로이드 OS와 상호작용하는 "OS 인터페이스" 계층의 선언을 담고 있습니다. 따라서 포괄적인 분석을 위해서는 이 세 가지 계층을 모두 감사하는 "계층적 분석(Layered Analysis)" 모델이 필수적입니다. 이 접근법을 통해 어떤 경로로 주입된 의존성이든 놓치지 않고 추적할 수 있습니다.

 

2.1. packages-lock.json을 통한 UPM 의존성 해독



2.1.1. 락 파일(Lock File)의 해부학

 

Packages/packages-lock.json 파일은 프로젝트에 설치된 모든 UPM(Unity Package Manager) 패키지의 정확한 버전 정보를 기록하는 결정적인 문서입니다.11 이 파일은 프로젝트가 직접 의존하는 패키지(Direct Dependencies)뿐만 아니라, 그 패키지들이 다시 의존하는 다른 패키지들(Transitive Dependencies)까지 모두 명시합니다.

packages-lock.json의 가장 중요한 역할은 '결정론적 빌드(Deterministic Builds)'를 보장하는 것입니다. 즉, 다른 개발 환경이나 빌드 서버에서도 항상 동일한 버전의 패키지들이 설치되도록 하여 "내 컴퓨터에서는 됐는데..."와 같은 문제를 방지합니다.12

 

2.1.2. 분석 워크플로우

 

packages-lock.json 파일을 열면 JSON 형식으로 패키지 목록이 나열되어 있습니다. 각 항목을 검토하여 프로젝트에 포함된 라이브러리를 식별할 수 있습니다.

  • 직접 의존성 식별: 파일의 최상위 레벨에 있는 "com.unity.purchasing", "com.unity.services.core"와 같은 항목들은 프로젝트가 직접적으로 필요로 하는 패키지입니다.
  • 전이 의존성 식별: 각 패키지 항목 내의 "dependencies" 블록은 해당 패키지가 의존하는 다른 패키지들을 보여줍니다. 이는 매우 중요한 단서가 될 수 있습니다. 예를 들어, Unity 서비스 관련 패키지를 설치했을 때, 그 패키지의 dependencies 블록 안에 "com.unity.nuget.newtonsoft-json": "2.0.0"와 같은 항목이 포함될 수 있습니다.11 이는 개발자가 직접 설치하지 않았더라도 Newtonsoft.Json 라이브러리가 프로젝트에 암묵적으로 포함되었음을 의미합니다.

 

2.1.3. 사례 연구: Newtonsoft.Json

 

  • 문제점: packages-lock.json 분석을 통해 "com.unity.nuget.newtonsoft-json" 패키지가 포함된 것을 확인했다고 가정해 봅시다. 앞서 설명했듯이, JSON 직렬화 라이브러리는 리플렉션을 사용하여 JSON 데이터의 키와 C# 클래스의 필드/프로퍼티 이름을 매핑합니다. 만약 다음과 같은 데이터 모델 클래스가 있다면:
    C#
    public class PlayerData
    {
        public string playerName;
        public int score;
    }

    Minify가 활성화되면 playerName은 a로, score는 b로 난독화될 수 있습니다. 런타임에 JSON 파서는 여전히 "playerName"과 "score"라는 키를 찾으려 하지만, 클래스에는 더 이상 해당 이름의 필드가 없으므로 데이터를 올바르게 객체에 채울 수 없게 됩니다.1
  • 해결책: 이 문제를 해결하기 위해, packages-lock.json에서 식별한 정보를 바탕으로 다음과 같은 Proguard 규칙을 proguard-user.pro 파일에 추가해야 합니다.
    코드 스니펫
    # Rules for Newtonsoft.Json Serialization
    # Reason: Prevents obfuscation of data model classes and their fields,
    # which are accessed via reflection during JSON deserialization.
    -keep class com.yourgame.models.** { *; }

    이 규칙은 Proguard에게 com.yourgame.models 네임스페이스 아래에 있는 모든 클래스와 그 멤버(필드, 메서드 등)의 이름을 그대로 유지하도록 지시합니다. ** 와일드카드는 하위 네임스페이스까지 모두 포함하며, { *; }는 모든 멤버를 대상으로 함을 의미합니다. 이를 통해 리플렉션 기반의 직렬화/역직렬화가 문제없이 작동하도록 보장할 수 있습니다.

 

2.2. mainTemplate.gradle에서 네이티브 라이브러리 노출



2.2.1. 템플릿 활성화

 

Unity는 내부적으로 Gradle을 사용하여 안드로이드 프로젝트를 빌드합니다. 이 빌드 프로세스를 직접 제어하기 위해 커스텀 Gradle 템플릿을 사용할 수 있습니다. Player Settings > Publishing Settings로 이동하여 Build 섹션의 Custom Main Gradle Template 체크박스를 활성화하면 Assets/Plugins/Android/mainTemplate.gradle 파일이 생성됩니다.15 이 파일은 Unity가 생성하는 기본 Gradle 스크립트에 개발자가 원하는 설정을 추가할 수 있는 통로 역할을 합니다.

 

2.2.2. 분석 워크플로우

 

생성된 mainTemplate.gradle 파일을 열고 dependencies {... } 블록을 찾습니다. 이 블록은 프로젝트가 의존하는 네이티브 안드로이드 라이브러리(AAR 또는 JAR 파일) 목록을 정의하는 곳입니다.16 이 의존성들은 주로

.unitypackage 형태로 제공되는 에셋이나, Unity의 External Dependency Manager for Unity (EDM4U)와 같은 도구에 의해 자동으로 추가됩니다.19

implementation '...' 또는 api '...'로 시작하는 줄들을 주의 깊게 살펴보아야 합니다.

 

2.2.3. 사례 연구: Google AdMob

 

  • 문제점: 개발자가 프로젝트에 Google Mobile Ads Unity 패키지를 추가했습니다. 이 패키지에 포함된 EDM4U는 빌드 과정에서 mainTemplate.gradle 파일에 다음과 같은 의존성을 자동으로 추가합니다: implementation 'com.google.android.gms:play-services-ads:...'. 만약 적절한 Proguard 규칙이 없다면, 앱이 시작될 때 AdMob SDK가 초기화에 실패하며 java.lang.ClassNotFoundException: Didn't find class "com.google.android.gms.ads.MobileAdsInitProvider"와 같은 오류를 발생시키며 충돌할 수 있습니다.3 이는 R8이 SDK 초기화에 필수적인
    MobileAdsInitProvider 클래스를 사용되지 않는 코드로 판단하여 제거했기 때문입니다.
  • 해결책: mainTemplate.gradle에서 com.google.android.gms:play-services-ads 의존성을 발견하는 즉시, 개발자는 해당 SDK에 대한 Proguard 규칙이 필요하다는 것을 인지해야 합니다. 가장 신뢰할 수 있는 정보원은 SDK의 공식 문서입니다. Google AdMob의 공식 문서나 신뢰할 수 있는 소스에서는 다음과 같은 규칙을 제공합니다 21:
    코드 스니펫
    # Rules for Google AdMob SDK
    # Reason: Preserves SDK classes required for initialization, ad loading,
    # and mediation functionality.
    -keep public class com.google.android.gms.ads.** {
        public *;
    }
    -keep class com.google.ads.mediation.admob.AdMobAdapter {
        *;
    }
    -keep class com.google.ads.mediation.AdUrlAdapter {
        *;
    }
    -keep public class com.google.ads.** {
        public *;
    }

    이 규칙들은 AdMob SDK의 공개 API, 미디에이션(Mediation)에 사용되는 핵심 어댑터 클래스, 그리고 구버전 클래스들이 제거되거나 이름이 변경되는 것을 방지하여 SDK가 런타임에 동적으로 필요한 클래스를 찾고 인스턴스화할 수 있도록 보장합니다.

 

2.3. AndroidManifest.xml에서 SDK 흔적 교차 검증



2.3.1. 매니페스트의 역할

 

Assets/Plugins/Android/AndroidManifest.xml 파일은 앱의 핵심 구성 요소와 권한, 하드웨어 요구 사항 등을 안드로이드 운영체제에 알리는 선언적인 파일입니다.24 Gradle 파일만큼 구체적인 라이브러리 버전 정보를 담고 있지는 않지만, 프로젝트에 어떤 SDK들이 통합되어 있는지에 대한 고수준의 "지도" 역할을 하며, 다른 파일에서 얻은 정보를 교차 검증하는 데 매우 유용합니다.

 

2.3.2. 분석 워크플로우

 

매니페스트 파일을 검사하여 특정 SDK의 존재를 암시하는 '흔적'을 찾을 수 있습니다.

  • <meta-data> 태그: 많은 SDK들이 API 키나 애플리케이션 ID와 같은 설정 값을 <meta-data> 태그를 통해 받습니다. 예를 들어, android:name="com.google.android.gms.ads.APPLICATION_ID" 태그를 발견했다면 Google AdMob SDK가 프로젝트에 포함되어 있다는 확실한 증거입니다.25 마찬가지로
    android:name="com.facebook.sdk.ApplicationId"는 Facebook SDK의 존재를 나타냅니다.26
  • <activity>, <service>, <provider> 태그: SDK들은 종종 자체적인 Activity(예: 로그인 화면), Service(예: 백그라운드 푸시 메시지 처리), Provider 등을 필요로 합니다. com.facebook.FacebookActivity나 com.google.android.gms.ads.AdActivity와 같은 이름의 컴포넌트 선언은 해당 SDK가 사용되고 있음을 명확히 보여줍니다.24

 

2.3.3. 사례 연구: Facebook SDK 삼각측량

 

여러 파일의 정보를 종합하여 의존성을 확신하는 과정을 "삼각측량"에 비유할 수 있습니다.

  • 단서 1 (.unitypackage 또는 에셋 폴더): 개발자는 Facebook SDK를 .unitypackage 파일을 통해 Assets 폴더에 직접 임포트했을 수 있습니다.
  • 단서 2 (mainTemplate.gradle): EDM4U와 같은 도구가 빌드 과정에서 implementation 'com.facebook.android:facebook-android-sdk:...' 의존성을 Gradle 파일에 추가합니다.26
  • 단서 3 (AndroidManifest.xml): 매니페스트 파일에 <meta-data android:name="com.facebook.sdk.ApplicationId"... />와 <activity android:name="com.facebook.FacebookActivity"... />와 같은 명백한 흔적이 남습니다.26

이 세 가지 파일 모두에서 Facebook SDK의 존재를 뒷받침하는 증거를 발견했다면, 의심의 여지 없이 해당 SDK가 프로젝트의 일부임을 확신할 수 있습니다. 이 확신을 바탕으로 Facebook SDK의 공식 Proguard 규칙을 찾아 proguard-user.pro 파일에 자신 있게 추가할 수 있습니다.27

 

섹션 3: 마스터 proguard-user.pro 파일 작성 및 유지보수

 

의존성 분석을 통해 필요한 Proguard 규칙들을 파악했다면, 다음 단계는 이를 체계적이고 유지보수하기 쉬운 단일 파일로 통합하는 것입니다. 이 섹션에서는 분석 결과를 실제 구현으로 옮기는 과정, 즉 견고하고 문서화가 잘 된 마스터 proguard-user.pro 파일을 만드는 모범 사례를 다룹니다.

 

3.1. 통합 및 구성

 

가장 좋은 방법은 Assets/Plugins/Android/ 경로에 proguard-user.pro라는 단일 파일을 생성하고 모든 커스텀 규칙을 이곳에서 관리하는 것입니다. 여러 파일에 규칙이 흩어져 있으면 추적이 어렵고 중복이나 충돌이 발생할 수 있습니다.

규칙을 통합할 때는 논리적인 그룹으로 묶는 것이 중요합니다. 단순히 인터넷에서 찾은 규칙들을 순서 없이 붙여넣는 대신, 각 규칙이 어떤 SDK나 라이브러리에 해당하는지를 기준으로 섹션을 나눕니다. 예를 들어, 서로 다른 두 SDK가 공통적으로 okhttp 라이브러리에 의존하고 각각 관련 규칙을 요구한다면, 이 규칙들을 한곳에 모아 "Shared Dependencies - OkHttp"와 같은 섹션으로 관리하는 것이 효율적입니다.

 

3.2. 문서화의 힘: 미래의 자신을 위한 주석

 

Proguard 규칙 파일에서 주석은 선택이 아닌 필수입니다. Proguard 설정 파일은 # 문자를 사용하여 주석을 작성할 수 있습니다.28 잘 작성된 주석은 시간이 지난 후에도, 또는 다른 팀원이 파일을 보더라도 각 규칙이 왜 필요한지를 즉시 이해할 수 있게 해줍니다. 이는

proguard-user.pro 파일을 단순한 설정 파일이 아닌, 프로젝트 아키텍처의 일부를 기록하는 살아있는 문서로 만듭니다.29

모든 규칙 블록 앞에는 다음 세 가지 정보를 포함하는 주석을 다는 것을 강력히 권장합니다.

  1. 대상 (Target): 이 규칙이 어떤 SDK나 라이브러리를 위한 것인지 명시합니다.
  2. 출처 (Source): 규칙을 어디서 얻었는지 기록합니다. (예: 공식 문서, packages-lock.json 분석 결과)
  3. 이유 (Reason): 이 규칙이 없다면 어떤 문제가 발생하는지 간략하게 설명합니다.

다음은 이러한 모범 사례를 적용한 주석의 예시입니다.

 

코드 스니펫



#####################################################################
# Google AdMob SDK Rules
# Source: Official Google Mobile Ads Unity documentation
# Reason: Prevents crashes on SDK initialization and preserves
#         classes required for mediation adapters.
#####################################################################
-keep class com.google.android.gms.ads.** { public *; }
-keep class com.google.ads.mediation.admob.AdMobAdapter { *; }

#####################################################################
# Newtonsoft.Json Serialization Rules
# Source: Deduced from presence of 'com.unity.nuget.newtonsoft-json'
#         in packages-lock.json.
# Reason: Preserves data model class and field names for
#         reflection-based deserialization.
#####################################################################
-keep class com.mygame.data.models.** { *; }

 

3.3. 포괄적인 proguard-user.pro 템플릿

 

개발자들이 자신의 프로젝트에 즉시 적용할 수 있도록, 다음은 다양한 상황을 고려하여 작성된 포괄적인 proguard-user.pro 템플릿입니다. 이 템플릿은 필수 규칙과 주석 처리된 선택적 규칙들로 구성되어 있어, 프로젝트의 필요에 맞게 수정하여 사용할 수 있습니다.

 

코드 스니펫



# Unity Android Proguard Rules Template
# Version 1.0
#
# This file provides a comprehensive starting point for Unity projects
# using Minify (Proguard/R8). Uncomment the sections relevant to your project.
# Best practice is to add comments explaining the source and reason for each rule.

#####################################################################
# 1. Unity Engine Essential Rules
# Source: Unity Community Best Practices
# Reason: These rules are generally required for the Unity Engine
#         to function correctly with minification. They preserve
#         classes and methods called from the native (C++) side.
#####################################################################
-keep class com.unity3d.player.UnityPlayerActivity { *; }
-keep class com.unity3d.player.UnityPlayer { *; }
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
}

# Preserve native method names that are called from C#
-keepclasseswithmembernames class * {
    native <methods>;
}

# Preserve classes that are instantiated from native code
-keep class * extends java.lang.Object {
    public <init>();
}

#####################################################################
# 2. Common Android & Java Rules
# Source: Default Android Proguard configurations
# Reason: Preserves common patterns like serialization and annotations
#         that are often broken by code shrinking.
#####################################################################
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# Preserve annotations
-keepattributes *Annotation*

#####################################################################
# 3. SDK-Specific Rules (Uncomment as needed)
#####################################################################

# --- Google AdMob ---
# Source: Official Google Documentation
# Reason: Prevents crashes on SDK init and ad loading.
# -keep public class com.google.android.gms.ads.** { public *; }
# -keep public class com.google.ads.** { public *; }
# -keep class com.google.ads.mediation.admob.AdMobAdapter { *; }
# -keep class com.google.ads.mediation.AdUrlAdapter { *; }

# --- Unity In-App Purchasing (IAP) ---
# Source: Unity/Partner Documentation (e.g., RevenueCat, ONE Store)
# Reason: Ensures IAP services can initialize and process purchases.
# Note: Some IAP SDKs like Vivox now embed rules in their AARs.[30]
# Check your specific IAP provider's documentation.
# Example for RevenueCat [31]:
# -keep class com.revenuecat.** { *; }
# Example for ONE Store [32]:
# -keep class com.gaa.sdk.iap.** { *; }

# --- Firebase (Common) ---
# Source: Firebase Documentation & Community Examples
# Reason: Firebase SDKs heavily use reflection and dynamic instantiation.
# These are general rules; specific products might need more.
# -keepnames class com.google.android.gms.common.** { *; }
# -keepnames class com.google.firebase.** { *; }
# -keep class com.google.firebase.provider.FirebaseInitProvider
# -dontwarn com.google.firebase.**
# -keepattributes Signature
# -keepattributes *Annotation*
#
# For Firebase Firestore/Database (if you use data models) [33, 34]:
# -keep class com.yourgame.models.** { *; }

# --- Facebook SDK ---
# Source: Official Facebook SDK Proguard files [27]
# Reason: Required for login, sharing, and analytics to function.
# -keep class com.facebook.** { *; }
# -keepattributes Signature

# --- Newtonsoft.Json ---
# Source: Deduced from UPM dependency
# Reason: Preserves data models for reflection-based serialization.
# -keep class com.yourgame.models.** { *; }

#####################################################################
# 4. Debugging Attributes
# Source: Proguard/R8 Documentation
# Reason: Retains file names and line numbers in stack traces for
#         easier debugging of minified builds. Remove for final release
#         if maximum obfuscation is desired.
#####################################################################
-keepattributes SourceFile,LineNumberTable


 

의존성-규칙 매핑 매트릭스

 

다음 표는 본 보고서에서 제시한 분석 워크플로우를 요약한 빠른 참조 가이드입니다. 개발자는 이 표를 사용하여 자신의 프로젝트에서 발견한 의존성을 바탕으로 필요한 Proguard 규칙을 신속하게 찾아 적용할 수 있습니다.

의존성 출처 파일 식별된 의존성/패키지 필수 Proguard 규칙(예시) 규칙이 없을 시 발생하는 일반적인 오류
packages-lock.json com.unity.nuget.newtonsoft-json -keep class com.yourgame.models.** { *; } JSON 역직렬화 실패, 데이터 모델 필드가 null로 채워짐.
mainTemplate.gradle com.google.android.gms:play-services-ads -keep public class com.google.android.gms.ads.** { public *; } ClassNotFoundException (예: MobileAdsInitProvider), SDK 초기화 실패, 광고 로드 안 됨.
mainTemplate.gradle com.facebook.android:facebook-android-sdk -keep class com.facebook.** { *; } ClassNotFoundException (예: FacebookActivity), 로그인/공유 기능 작동 불능.
mainTemplate.gradle com.google.firebase:firebase-analytics -keepnames class com.google.firebase.** { *; } NoSuchMethodError, 이벤트 로깅 실패, Firebase 서비스 초기화 오류.
mainTemplate.gradle com.revenuecat.purchases:purchasing -keep class com.revenuecat.** { *; } 인앱 결제 초기화 실패, 구매 처리 불가.
AndroidManifest.xml <meta-data android:name="com.google.android.gms.ads.APPLICATION_ID"/> (위의 play-services-ads 규칙과 동일) AdMob SDK가 존재함을 교차 확인하는 강력한 증거.
AndroidManifest.xml <activity android:name="com.facebook.FacebookActivity"/> (위의 facebook-android-sdk 규칙과 동일) Facebook SDK가 존재함을 교차 확인하는 강력한 증거.

 

섹션 4: 최종 관문: 전문적인 검증 및 디버깅 워크플로우

 

선제적인 의존성 분석과 체계적인 Proguard 규칙 작성을 통해 대부분의 문제를 예방할 수 있지만, 최종 검증 단계는 절대로 생략할 수 없습니다. 이 섹션에서는 Minify가 활성화된 빌드의 안정성을 보장하고, 만에 하나 발생할 수 있는 런타임 문제를 신속하게 해결하기 위한 전문적인 검증 및 디버깅 워크플로우를 설명합니다.

 

4.1. 빌드 성공의 함정

 

가장 먼저 명심해야 할 점은, Minify가 활성화된 상태에서 안드로이드 빌드가 성공적으로 완료되었다는 사실이 앱의 런타임 안정성을 보장하지 않는다는 것입니다.9 Proguard/R8으로 인해 발생하는 가장 위험한 오류들은 컴파일 시점에 발견되지 않습니다. 이들은 특정 사용자 시나리오(예: 광고 시청, 인앱 결제 시도, 특정 UI 상호작용)에서만 트리거되는 런타임 충돌의 형태로 나타납니다. 따라서,

Minify가 적용된 릴리스 빌드에 대한 전용 QA(Quality Assurance) 테스트는 출시 프로세스에서 절대 건너뛸 수 없는 필수적인 과정입니다.

 

4.2. 런타임의 창, adb logcat 마스터하기

 

안드로이드 디버그 브릿지(ADB)는 안드로이드 기기와 통신하기 위한 강력한 커맨드 라인 도구입니다. 그중 logcat 명령어는 기기에서 발생하는 시스템 및 애플리케이션 로그를 실시간으로 스트리밍하여 보여주므로, 런타임 충돌의 원인을 파악하는 데 가장 중요한 도구입니다.37

디버깅 워크플로우는 다음과 같습니다.

  1. 기기 연결 확인: 터미널 또는 명령 프롬프트에서 adb devices를 실행하여 테스트 기기가 정상적으로 연결되었는지 확인합니다.
  2. 로그 캡처 시작: adb logcat 명령어를 실행하여 모든 로그를 실시간으로 확인합니다.
  3. 충돌 재현: 테스트 기기에서 앱을 실행하고 충돌이 발생하는 시나리오를 재현합니다.
  4. 로그 분석: 앱이 충돌하는 순간, logcat 출력에 관련 정보가 기록됩니다. 하지만 로그의 양이 방대하여 핵심 정보를 찾기 어려울 수 있으므로, 효과적인 필터링이 필수적입니다.

다음은 노이즈를 제거하고 충돌 원인을 신속하게 찾는 데 유용한 logcat 필터링 명령어입니다.

  • 오류 레벨(Error Level) 필터링: 심각한 오류만 확인하려면 adb logcat *:E를 사용합니다. E는 Error를 의미하며, W(Warning), I(Info), D(Debug) 등으로 조정할 수 있습니다.37
  • 특정 태그(Tag) 필터링: Unity 엔진에서 출력하는 로그만 보려면 adb logcat Unity:I *:S를 사용합니다. 이는 Unity 태그의 로그는 Info 레벨 이상으로 보여주고, 나머지(*) 모든 태그는 Silent(S)로 처리하여 숨깁니다.
  • grep을 이용한 키워드 검색: logcat의 출력을 파이프(|)로 grep(Linux/macOS) 또는 findstr(Windows)에 연결하여 특정 키워드가 포함된 라인만 필터링하는 것이 가장 강력한 방법입니다. 치명적인 예외를 찾기 위해 다음 명령어를 사용할 수 있습니다.38
    Shell
    # Linux / macOS
    adb logcat | grep "FATAL EXCEPTION"
    adb logcat | grep "ClassNotFoundException"
    adb logcat | grep "NoSuchMethodError"

    # Windows
    adb logcat | findstr "FATAL EXCEPTION"

    이러한 필터링을 통해 수천 줄의 로그 속에서 충돌의 직접적인 원인이 되는 스택 트레이스(Stack Trace)를 정확히 찾아낼 수 있습니다.41

 

4.3. retrace로 난독화된 진실 해독하기

 

Minify가 적용된 빌드에서 충돌이 발생하면, logcat에서 찾은 스택 트레이스는 난독화되어 있어 거의 해독이 불가능합니다. 클래스 이름은 a.b.c로, 메서드 이름은 d()와 같이 표시되어 실제 코드의 어느 부분에서 문제가 발생했는지 알 수 없습니다.10

이 문제를 해결하기 위해, 안드로이드 빌드 시스템은 mapping.txt라는 파일을 생성합니다. 이 파일은 원본 식별자 이름과 난독화된 이름 사이의 매핑 정보를 담고 있는 '해독 키'입니다. 이 키와 retrace라는 도구를 사용하면, 난독화된 스택 트레이스를 다시 사람이 읽을 수 있는 형태로 복원할 수 있습니다.

이 과정은 실시간 디버깅이 아닌, 충돌 발생 후 증거를 수집하여 원인을 재구성하는 "법의학적 분석"에 가깝습니다. 표준적인 개발 디버깅(브레이크포인트 설정 등)은 난독화된 코드에 효과적으로 적용하기 어렵습니다. 대신, 빌드의 산출물(mapping.txt)과 런타임의 기록(logcat 출력)을 다루는 데 익숙해져야 합니다. 이러한 아티팩트 기반의 디버깅은 프로페셔널 안드로이드 개발에 필수적인 별개의 기술입니다.

 

4.3.1. retrace 워크플로우

 

  1. mapping.txt 파일 찾기: Unity로 안드로이드 빌드를 수행하면 mapping.txt 파일이 생성됩니다. 이 파일은 일반적으로 프로젝트의 Temp/gradleOut/unityLibrary/build/outputs/mapping/release/ 디렉터리 내에 위치합니다.
  2. mapping.txt 파일 보관: 매우 중요합니다. 앱의 새로운 버전을 빌드하여 출시할 때마다 해당 버전의 mapping.txt 파일을 반드시 안전한 곳에 보관해야 합니다. 이 파일은 빌드할 때마다 덮어쓰여지므로, 특정 버전에서 발생한 사용자 충돌 보고서를 해독하려면 해당 버전을 빌드할 때 생성된 정확한 mapping.txt 파일이 필요합니다.43 버전 관리 시스템에 함께 커밋하거나, 빌드 번호와 함께 아카이빙하는 것이 좋습니다.
  3. retrace 도구 사용:
  • retrace 도구는 안드로이드 SDK 디렉터리 내에 있습니다. 최신 SDK에서는 cmdline-tools/latest/bin/에, 구버전에서는 tools/proguard/bin/에 위치할 수 있습니다.
  • logcat에서 복사한 난독화된 스택 트레이스를 텍스트 파일(예: obfuscated_stack_trace.txt)로 저장합니다.
  • 터미널에서 다음 명령어를 실행하여 스택 트레이스를 해독합니다.
    Shell
    # retrace.bat for Windows, retrace.sh for Linux/macOS
    # <path_to_sdk>는 실제 SDK 경로로 대체해야 합니다.
    <path_to_sdk>/cmdline-tools/latest/bin/retrace.sh -verbose mapping.txt obfuscated_stack_trace.txt

  1. 결과 분석: retrace 도구는 난독화된 스택 트레이스를 원본 클래스 및 메서드 이름, 그리고 소스 파일의 정확한 라인 번호까지 포함된, 사람이 읽을 수 있는 형태로 변환하여 출력합니다. 이 정보를 통해 개발자는 어떤 클래스의 어떤 메서드가 누락되었는지 정확히 파악하고, 해당 클래스나 메서드를 보존하는 -keep 규칙을 proguard-user.pro 파일에 추가하여 문제를 근본적으로 해결할 수 있습니다.

이러한 체계적인 사후 분석 워크플로우를 갖춤으로써, 개발자는 Minify로 인해 발생하는 가장 복잡하고 어려운 런타임 충돌 문제까지도 자신감 있게 대처할 수 있습니다.

 

결론: Proguard에 대한 자신감 확보

 

본 기술 보고서는 Unity 안드로이드 빌드에서 Minify 기능을 사용할 때 발생하는 문제들을 해결하기 위한 체계적이고 선제적인 접근법을 제시했습니다. 더 이상 빌드 후 발생하는 예측 불가능한 충돌에 반응적으로 대처하는 대신, 프로젝트의 핵심 의존성 파일들을 분석하여 필요한 Proguard 규칙을 미리 도출하는 전문적인 워크플로우를 수립할 수 있습니다.

packages-lock.json, mainTemplate.gradle, AndroidManifest.xml에 대한 계층적 분석은 프로젝트에 포함된 모든 UPM 패키지, 네이티브 안드로이드 라이브러리, 그리고 서드파티 SDK의 존재를 명확히 밝혀내는 법의학적 조사와 같습니다. 이 분석을 통해 얻은 "의존성 맵"을 바탕으로, 개발자는 주석이 잘 달린 체계적인 proguard-user.pro 파일을 작성하여 정적 분석 도구인 Proguard/R8이 놓칠 수 있는 동적 코드 호출(리플렉션, JNI 등)을 미리 알려줄 수 있습니다.

이러한 접근법은 다음과 같은 핵심적인 이점을 제공합니다.

  • 안정성: 시행착오를 최소화하고 런타임 충돌의 가능성을 현저히 낮춥니다.
  • 효율성: 문제 발생 후 원인을 찾는 데 소요되는 막대한 디버깅 시간을 절약합니다.
  • 유지보수성: 잘 문서화된 proguard-user.pro 파일은 프로젝트가 진화하고 새로운 의존성이 추가될 때에도 쉽게 관리하고 확장할 수 있도록 돕습니다.

궁극적으로 이 워크플로우를 채택함으로써 Unity 개발자는 Minify 옵션이 제공하는 본연의 가치, 즉 더 작은 APK 크기, 난독화를 통한 보안 강화, 그리고 최적화를 통한 성능 향상을 온전히 누릴 수 있습니다. 더 이상 Minify 체크박스를 두려움의 대상으로 여기지 않고, 더 최적화되고, 안정적이며, 안전한 애플리케이션을 만드는 강력하고 신뢰할 수 있는 도구로 자신감 있게 활용하게 될 것입니다.

참고 자료

  1. Reducing App Size: Proguard, R8, App Bundles & Resource Shrinking - Carrion.dev, 8월 15, 2025에 액세스, https://carrion.dev/en/posts/reducing-app-size/
  2. Android Proguard - ClassNotFoundException - Stack Overflow, 8월 15, 2025에 액세스, https://stackoverflow.com/questions/28563803/android-proguard-classnotfoundexception
  3. Didn't find class "com.google.android.gms.ads.MobileAdsInitProvider", 8월 15, 2025에 액세스, https://community.monogame.net/t/didnt-find-class-com-google-android-gms-ads-mobileadsinitprovider/13999
  4. Do you experience the following crash after changing ProGuard to R8? : r/androiddev, 8월 15, 2025에 액세스, https://www.reddit.com/r/androiddev/comments/bhp4bd/do_you_experience_the_following_crash_after/
  5. [Android] R8, Proguard 난독화 - Code Columnist - 티스토리, 8월 15, 2025에 액세스, https://yjhdevelopdiary.tistory.com/70
  6. 앱 최적화 사용 설정 | App quality - Android Developers, 8월 15, 2025에 액세스, https://developer.android.com/topic/performance/app-optimization/enable-app-optimization?hl=ko
  7. Shrinking your app with R8. Posted by Søren Gjesse, Software… | by Android Developers - Medium, 8월 15, 2025에 액세스, https://medium.com/androiddevelopers/shrinking-your-app-with-r8-909efac25de4
  8. Mastering ProGuard Rules in Android: A Comprehensive Guide | by Dharmesh Basapati, 8월 15, 2025에 액세스, https://dharmeshbasapati.medium.com/mastering-proguard-rules-in-android-a-comprehensive-guide-7404fa4e6ff2
  9. Practical ProGuard rules examples | by Wojtek Kaliciński | Android Developers - Medium, 8월 15, 2025에 액세스, https://medium.com/androiddevelopers/practical-proguard-rules-examples-5640a3907dc9
  10. A guide to R8 and code shrinking in Android - LogRocket Blog, 8월 15, 2025에 액세스, https://blog.logrocket.com/r8-code-shrinking-android-guide/
  11. FamilyTree code/Packages/packages-lock.json · master - GitLab, 8월 15, 2025에 액세스, https://csgitlab.reading.ac.uk/vq013253/individual-project/-/blob/master/FamilyTree%20code/Packages/packages-lock.json?ref_type=heads
  12. Dependency and resolution - Unity - Manual, 8월 15, 2025에 액세스, https://docs.unity3d.com/6000.1/Documentation/Manual/upm-dependencies.html
  13. The Complete Guide to package-lock.json | Pavesoft - Medium, 8월 15, 2025에 액세스, https://medium.com/pavesoft/package-lock-json-the-complete-guide-2ae40175ebdd
  14. Will proguard obfuscation affect my json data? - Stack Overflow, 8월 15, 2025에 액세스, https://stackoverflow.com/questions/78411279/will-proguard-obfuscation-affect-my-json-data
  15. Gradle dependencies on Unity - android - Stack Overflow, 8월 15, 2025에 액세스, https://stackoverflow.com/questions/35728875/gradle-dependencies-on-unity
  16. CAS-Unity-Sample/Assets/Plugins/Android/mainTemplate.gradle at master - GitHub, 8월 15, 2025에 액세스, https://github.com/cleveradssolutions/CAS-Unity-Sample/blob/master/Assets/Plugins/Android/mainTemplate.gradle
  17. surface-duo-sdk-unity-samples/duo-screenhelper/Assets/Plugins/Android/mainTemplate.gradle at main - GitHub, 8월 15, 2025에 액세스, https://github.com/microsoft/surface-duo-sdk-unity-samples/blob/master/duo-screenhelper/Assets/Plugins/Android/mainTemplate.gradle
  18. For Unity 2022.2 and newer - Strivr Developer Site, 8월 15, 2025에 액세스, https://developer.strivr.com/docs/sdk/latest/unity/dependencies-unity-2022.html
  19. Configure the Android Platform | Embrace Documentation, 8월 15, 2025에 액세스, https://embrace.io/docs/unity/integration/configure-embrace-android/
  20. Unity Firebase Analytics with custom gradle file as dependency management (instead of Google Play Services Resolver), 8월 15, 2025에 액세스, https://groups.google.com/g/firebase-talk/c/c7wrpkTsiM0
  21. Global Settings | Unity - Google for Developers, 8월 15, 2025에 액세스, https://developers.google.com/admob/unity/global-settings
  22. Proguard and AdMob mediation - Google Ads Developer Blog, 8월 15, 2025에 액세스, http://ads-developers.googleblog.com/2015/10/proguard-and-admob-mediation.html
  23. proguard-rules.pro - googlearchive/android-ads - GitHub, 8월 15, 2025에 액세스, https://github.com/googlearchive/android-ads/blob/master/admanager/BannerExample/app/proguard-rules.pro
  24. App manifest overview | App architecture - Android Developers, 8월 15, 2025에 액세스, https://developer.android.com/guide/topics/manifest/manifest-intro
  25. Get Started | Android - Google for Developers, 8월 15, 2025에 액세스, https://developers.google.com/admob/android/quick-start
  26. Getting Started - Facebook SDK for Android - Meta for Developers, 8월 15, 2025에 액세스, https://developers.facebook.com/docs/android/getting-started/
  27. facebook-android-sdk/facebook-login/proguard-rules.pro at main - GitHub, 8월 15, 2025에 액세스, https://github.com/facebook/facebook-android-sdk/blob/main/facebook-login/proguard-rules.pro
  28. How do you comment out a rule in Proguard.cfg? - Stack Overflow, 8월 15, 2025에 액세스, https://stackoverflow.com/questions/10543174/how-do-you-comment-out-a-rule-in-proguard-cfg
  29. The Silent Security Hero of Android Development: Mastering ProGuard Files - Stackademic, 8월 15, 2025에 액세스, https://blog.stackademic.com/the-slient-security-hero-of-android-development-mastering-proguard-files-c1e9c6480382
  30. minifyEnabled causing app to crash on release mode - Stack Overflow, 8월 15, 2025에 액세스, https://stackoverflow.com/questions/41900434/minifyenabled-causing-app-to-crash-on-release-mode
  31. How do I debug an Android app that crashes only in Release Mode - Stack Overflow, 8월 15, 2025에 액세스, https://stackoverflow.com/questions/51843796/how-do-i-debug-an-android-app-that-crashes-only-in-release-mode
  32. Logcat command-line tool | Android Studio, 8월 15, 2025에 액세스, https://developer.android.com/tools/logcat
  33. Mastering ADB logcat: Options, Filters & Advanced Debugging Techniques - Medium, 8월 15, 2025에 액세스, https://medium.com/@begunova/mastering-adb-logcat-options-filters-advanced-debugging-techniques-10331a73532f
  34. How to debug random Android crashes? - Unity3D - Reddit, 8월 15, 2025에 액세스, https://www.reddit.com/r/Unity3D/comments/a7yirv/how_to_debug_random_android_crashes/
  35. ADB logcat: Options, Filters & More - GitHub, 8월 15, 2025에 액세스, https://github.com/lana-20/adb-logcat-options-filters
  36. logcat does not show exception when a test application crashes with filter, also debugger has no way to easily catch fatal exceptions [37003543] - Issue Tracker, 8월 15, 2025에 액세스, https://issuetracker.google.com/issues/37003543
  37. [FEATURE] Support exclusion filters in ADB logs [37007142] - Issue Tracker - Google, 8월 15, 2025에 액세스, https://issuetracker.google.com/issues/37007142
  38. ProGuard | Android Developers, 8월 15, 2025에 액세스, https://minimum-viable-product.github.io/marshmallow-docs/tools/help/proguard.html

반응형
Google Play에서 다운로드 App Store에서 다운로드