ProGuard/R8: обфускація та захист Android додатків

Автор: IT Sectr Опубліковано: 2026-02-14 Час читання: 8 хв

ProGuard та R8 — інструменти обфускації, мініфікації та оптимізації для Android-додатків. ProGuard, створений у 2002 році, довгий час був стандартом де-факто для захисту Java-коду. R8 — його наступник, розроблений Google та вбудований в Android Gradle Plugin починаючи з AGP 3.4. Обидва інструменти зменшують розмір APK, видаляють мертвий код та ускладнюють reverse engineering. За даними Android Developers, R8 виконує збірку в 2–3 рази швидше за ProGuard при порівнянній якості обфускації.

Головне

  • ProGuard — інструмент обфускації та оптимізації Java-байткоду, стандарт для Android з 2000-х років
  • R8 — наступник ProGuard від Google, вбудований в AGP, виконує обфускацію, мініфікацію та оптимізацію за один прохід
  • Обфускація перейменовує класи та методи в короткі імена, ускладнюючи reverse engineering додатка
  • Мініфікація видаляє невикористовувані класи, методи та поля, зменшуючи розмір кінцевого APK/AAB
  • ProGuard rules (.pro файли) керують тим, які частини коду зберігаються, а які обфускуються або видаляються

Що таке ProGuard?

ProGuard — це інструмент з відкритим кодом (Apache 2.0) для обфускації, мініфікації, оптимізації та преверифікації Java-байткоду. Розроблений Еріком Лафоржем у 2002 році в рамках проекту SourceForge. ProGuard приймає на вхід скомпільовані Java-класи (.class) або JAR-архіви та видає оброблені класи того ж формату, але меншого розміру та з перейменованими елементами.

Довгий час ProGuard був єдиним стандартом для захисту Android-додатків від reverse engineering. Google офіційно рекомендувала його використання в Android SDK та постачала конфігурацію за замовчуванням у файлі proguard-android-optimize.txt всередині SDK tools. ProGuard працював як окремий інструмент, що запускався після компіляції Java-коду в байткод та перед пакуванням у DEX.

Архітектура ProGuard

ProGuard складається з чотирьох послідовних фаз: shrink (видалення невикористовуваних класів), optimize (оптимізація байткоду — інлайнінг, видалення мертвого коду), obfuscate (перейменування класів, методів та полів у короткі імена), preverify (перевірка сумісності з JVM). Кожна фаза керується окремими правилами з конфігураційних файлів.

На етапі обфускації ProGuard генерує mapping-файл (mapping.txt), який відображає вихідні імена на обфусковані. Цей файл критично важливий для декодування crash-логів з релізних збірок через утиліту retrace. Без mapping-файлу stack trace перетворюється на набір літер a(), b(), c() без можливості відновлення вихідного контексту.

Фаза ProGuardПризначенняРезультат
ShrinkАналіз графа викликів та видалення мертвого кодуЗменшення кількості класів в APK
OptimizeІнлайнінг методів, видалення невикористовуваних параметрівПрискорення виконання коду
ObfuscateПерейменування класів, полів та методівЗахист від reverse engineering
PreverifyДодавання StackMap-атрибутів для JVMСумісність з Java 6+

Що таке R8?

R8 — це інструмент обфускації та мініфікації наступного покоління від Google, вперше представлений в Android Studio 3.3 (листопад 2018) і став стандартним в AGP 3.4 (серпень 2019). На відміну від ProGuard, R8 є частиною компілятора D8/R8, який перетворює Java-байткод у DEX-формат. R8 виконує всі фази — обфускацію, мініфікацію та оптимізацію — за один прохід, без передачі проміжних файлів між інструментами.

Google розробила R8 з двома цілями: прискорити збірку (ProGuard працював як зовнішній інструмент) та забезпечити безшовну інтеграцію з сучасним Android-стеком (Desugar, Core Library Desugaring, D8). R8 написаний на Kotlin та Java і є частиною репозиторію R8/Desugar на AOSP (Android Open Source Project).

Важлива перевага R8 — повна зворотна сумісність з ProGuard rules. Існуючі .pro-файли працюють без змін. R8 навіть підтримує специфічні ProGuard-директиви, включаючи -whyareyoukeeping, -printconfiguration та -printmapping. Це означає, що перехід з ProGuard на R8 відбувається прозоро: достатньо оновити AGP.

kotlin
// build.gradle.kts — включення R8 через minifyEnabled
android {
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            isShrinkResources = true

            proguardFiles(
                // Базова конфігурація з Android SDK
                getDefaultProguardFile("proguard-android-optimize.txt"),
                // Користувацькі правила проекту
                "proguard-rules.pro"
            )
        }
    }
}

Код демонструє стандартну конфігурацію release-збірки. Флаг isMinifyEnabled = true активує R8 для обфускації та оптимізації. isShrinkResources = true додатково видаляє невикористовувані ресурси. getDefaultProguardFile завантажує базові правила з SDK, а proguard-rules.pro містить специфічні для проекту налаштування.

Обфускація коду в Android

Обфускація — це процес перетворення вихідного коду у форму, яку складно аналізувати людині, але яка зберігає повну функціональність. У контексті Android обфускація означає перейменування класів, методів та полів у короткі, позбавлені сенсу імена: com.example.app.auth.LoginManager перетворюється на a.a.a, метод authenticateUser — на a, поле userToken — на b.

Навіщо потрібна обфускація

APK-файли Android — це архіви, які можна відкрити будь-яким архіватором (ZIP, 7z, WinRAR). Без обфускації зловмисник отримує повну карту додатка: назви пакетів, класів, методів та полів. Інструменти на кшталт jadx або Bytecode Viewer відновлюють майже вихідний Java-код з DEX-файлів за секунди. Обфускація не робить код невразливим, але суттєво підвищує поріг входу: замість осмислених імен читач бачить a(), b(), c().

Типові цілі обфускації: захист комерційної логіки (алгоритми, формули розрахунку), ускладнення крадіжки API-ключів та токенів, запобігання підміні класів через reflection, захист від патчінгу та модифікації APK (repackage attack). На практиці 70% завдань вирішує саме перейменування — заради цього і запускають ProGuard / R8.

Приклад ProGuard правил

Нижче наведено типовий файл proguard-rules.pro для Android-проекту з Retrofit, Gson та Parcelable. Правила -keep зберігають класи та методи, необхідні для роботи бібліотек через reflection. Без цих правил R8 видалить або перейменує класи, до яких бібліотека звертається по імені рядка.

pro
# =====================
# Retrofit — збереження інтерфейсів
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions

# =====================
# Gson — серіалізація JSON
# =====================
-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName <fields>;
}
-keep class *.serialization.** {
    <fields>;
}

# =====================
# Parcelable — Creator
# =====================
-keepclassmembers class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

# =====================
# Logging — видалення логів з релізу
# =====================
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(String, int);
    public static int v(...);
    public static int d(...);
    public static int i(...);
    public static int w(...);
    public static int e(...);
}

# =====================
# Дата-класи Kotlin — зберігаємо конструктори
# =====================
-keepclassmembers class * {
    @kotlin.Metadata <fields>;
}

# =====================
# Activity — точка входу
# =====================
-keep class * extends android.app.Activity {
    @android.annotation.SuppressLint <methods>;
}

Кожна директива в .pro файлі вирішує конкретне завдання. -keep запобігає видаленню або перейменуванню класу цілком. -keepclassmembers захищає лише члени класу (поля та методи), але дозволяє видалити сам клас, якщо він не використовується. -assumenosideeffects вказує R8, що виклик методу не має побічних ефектів і може бути безпечно видалений. Директива -keepattributes зберігає метадані в байткоді — анотації, сигнатури, винятки.

Правило -keep,allowobfuscation,allowshrinking для Retrofit дозволяє R8 перейменовувати інтерфейси, але не видаляти їх. Це необхідно, оскільки Retrofit звертається до інтерфейсів через динамічний проксі (java.lang.reflect.Proxy), і видалення призведе до ClassNotFoundException в runtime. Аналогічно, Gson використовує reflection для доступу до полів з анотацією @SerializedName — без -keepclassmembers поля будуть видалені як невикористовувані.

Мініфікація та ShrinkResources

Мініфікація (shrinking) — процес видалення невикористовуваного коду та ресурсів з кінцевої збірки. ProGuard та R8 аналізують граф викликів, починаючи з точок входу (Activity, Service, BroadcastReceiver), і видаляють класи та методи, до яких не можна дійти по ланцюжку викликів. ShrinkResources — додатковий етап, який видаляє невикористовувані ресурси з res/ (layout, drawable, string, color).

Мініфікація дає найбільший виграш у великих проектах з бібліотеками. Типова картина: проект використовує 10% коду з підключеної бібліотеки (наприклад, Google Play Services). Без мініфікації весь код бібліотеки потрапляє в APK. З мініфікацією R8 видаляє 70–90% коду бібліотек, залишаючи лише реально використовувані класи та методи. Це безпосередньо впливає на розмір APK, час завантаження та споживання пам'яті.

ShrinkResources в дії

Механізм ShrinkResources працює в парі з мініфікацією коду. Після того як R8 визначив, які класи використовуються, ресурсний shrinking аналізує посилання на ресурси з коду: R.layout.main, R.drawable.icon, getString(R.string.title). Всі ресурси, на які немає прямого або непрямого посилання, видаляються з фінального APK або AAB. Для цього використовується ресурсний файл resources.arsc та папки res/.

Важливий нюанс: ресурси можуть бути викликані через getIdentifier() або Resources.getResourceName() по рядковому імені, минаючи R-клас. У таких випадках R8 не бачить прямого зв'язку і може видалити ресурс, який фактично використовується. Для захисту таких ресурсів існує директива -keep class **.R$* { *; } — вона зберігає всі ідентифікатори R-класу.

xml
<!-- Приклад: ресурс, який використовується тільки через getIdentifier() -->
<string name="dynamic_title_welcome">Ласкаво просимо</string>
<string name="dynamic_title_share">Поділитися</string>

<!-- Kotlin-код, який звертається по рядку -->
<!-- val title = getString(resources.getIdentifier( -->
<!--     \"dynamic_title_${type}\", \"string\", packageName)) -->

У цьому випадку R8 не бачить статичного посилання на dynamic_title_welcome в R-класі, тому що звернення відбувається через getIdentifier з динамічним іменем. Щоб зберегти такі ресурси, потрібно додати в proguard-rules.pro директиву -keepclassmembers class **.R$string { *; } — вона забороняє видалення будь-яких полів з усіх R$string класів.

ДирективаПризначенняПриклад
-keepЗберігає клас і всі його члени-keep class com.example.api.** { *; }
-keepclassmembersЗберігає тільки члени класу-keepclassmembers class * { @SerializedName <fields>; }
-keepattributesЗберігає метадані байткоду-keepattributes *Annotation*, Signature
-assumenosideeffectsВидаляє виклики без побічних ефектів-assumenosideeffects class Log { d(...); }
-dontwarnПригнічує попередження-dontwarn com.example.legacy.**

R8 vs ProGuard: ключові відмінності

Незважаючи на те що R8 є наступником ProGuard, між інструментами є принципові відмінності в архітектурі, продуктивності та поведінці. Google офіційно припинила підтримку ProGuard в Android Gradle Plugin починаючи з AGP 7.0, однак ProGuard продовжує використовуватися в проектах, де потрібна специфічна поведінка оптимізації, недоступна в R8.

Порівняльна таблиця

ХарактеристикаProGuardR8
РозробникGuardSquare (Ерік Лафорж)Google
Рік виходу20022018 (стабільний в 2019)
Архітектура4 окремі фази (shrink → optimize → obfuscate → preverify)Один прохід: shrink + optimize + obfuscate одночасно
Інтеграція в AGPЗовнішній інструмент, запускається після javacВбудований в D8 компілятор DEX
Швидкість збіркиПовільніше в 2–3 разиШвидше за рахунок одного проходу та нативної інтеграції
Підтримка KotlinОбмежена (проблеми з inline, lambdas, coroutines)Повна: coroutines, inline-функції, data class
Mapping-файлmapping.txt (сумісний з retrace)mapping.txt (той же формат)
Кастомізація оптимізації60+ опцій -optimizationpasses, -optimizationsОбмежена: більшість оптимізацій включені за замовчуванням
Статус підтримкиЗамінений на R8 (AGP 7.0+ не використовує)Активна розробка, частина AOSP

Коли R8 може зламати збірку

R8 агресивніше ProGuard видаляє код, який вважає мертвим. Це призводить до ситуацій, коли збірка в debug працює, а release падає з ClassNotFoundException або NoSuchMethodException. Типові випадки: бібліотеки, що використовують reflection по імені класу (Gson, Moshi, Retrofit, Room, Dagger); виклики ServiceLoader або java.util.ServiceLoader; динамічні проксі (java.lang.reflect.Proxy); нативні методи (JNI). Рішення — додати -keep для всіх класів, які викликаються через reflection.

pro
# Типові проблеми reflection — R8 не бачить статичного зв'язку

# Room — зберігаємо DAO та міграції
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }

# Dagger / Hilt — зберігаємо компоненти
-keep class * extends dagger.hilt.android.components.** { *; }

# JNI — не перейменовуємо нативні методи
-keepclasseswithmembernames class * {
    native <methods>;
}

# Data Binding — зберігаємо Binding-класи
-keep class *.databinding.** { *; }

Якщо після додавання правил збірка все ще падає, використовуйте флаг -printconfiguration full-config.txt в proguard-rules.pro. R8 згенерує повний файл конфігурації, який показує, які правила застосовані і які класи зберігаються. Також корисна директива -whyareyoukeeping class com.example.MyClass — вона виводить причину, з якої R8 вирішив зберегти даний клас.

Налаштування ProGuard rules

Правильне налаштування ProGuard rules — ключ до стабільної роботи обфускації без багів в runtime. Нижче наведено покроковий процес налаштування для нового проекту або для проекту, в якому обфускація викликає помилки.

Крок 1: Базова конфігурація

Почніть з підключення стандартного файлу Android SDK — proguard-android-optimize.txt. Він містить правила для базових компонентів Android: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Цей файл знаходиться в папці SDK: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. Якщо ви використовуєте AGP, getDefaultProguardFile завантажить його автоматично.

Крок 2: Бібліотеки

Кожна популярна бібліотека має рекомендовані ProGuard rules. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — всі вони вимагають специфічних -keep правил. Зазвичай правила включені в AAR-бібліотеку і підключаються автоматично через consumer guard rules. Перевірте, що бібліотека надає файл proguard.txt всередині AAR — це ознака того, що правила вже враховані.

Крок 3: Тестування релізної збірки

Перед публікацією обов'язково протестуйте релізну збірку на реальному пристрої або емуляторі. Проблеми обфускації проявляються тільки в runtime. Перевірте: авторизацію (логін/реєстрацію), завантаження даних з мережі, навігацію між екранами, камеру та галерею, push-сповіщення, Deeplinks, WebView. Кожен краш в релізній збірці потрібно декодувати через retrace з mapping-файлом і додавати недостатні -keep правила.

Крок 4: Mapping-файл та CI

Mapping-файл генерується в build/outputs/mapping/release/mapping.txt. Цей файл обов'язковий для збереження: без нього неможливо декодувати crash-логи з Google Play Console. Включіть mapping.txt в систему контролю версій або завантажуйте його в CI-артефакти. Google Play Console приймає mapping-файл автоматично при завантаженні AAB з включеним uploading mapping.txt.

Нижче наведено повний workflow налаштування обфускації у файлі proguard-rules.pro з коментарями для кожної групи правил.

pro
# ===========================================
# proguard-rules.pro — повний приклад
# ===========================================

# --- Загальні налаштування ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify

# --- Android компоненти ---
-keep public class * extends android.app.Activity
-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.app.Fragment
-keep public class * extends androidx.fragment.app.Fragment
-keep public class * extends android.view.View

# --- OkHttp / Retrofit ---
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class retrofit2.** { *; }
-keepattributes Exceptions

# --- Gson / Moshi ---
-keepclassmembers class * {
    @com.google.gson.annotations.SerializedName <fields>;
}
-keep class com.google.gson.** { *; }

# --- Firebase ---
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }

# --- Kotlin Coroutines ---
-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {}
-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {}

# --- Серіалізація ---
-keepclassmembers class * implements java.io.Serializable {
    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();
}

# --- R8 тільки: примусове збереження ---
# (ProGuard ігнорує цю директиву)
-keep,allowobfuscation class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

Після налаштування виконайте збірку: ./gradlew assembleRelease. Перевірте, що в build/outputs/mapping/release/ з'явилися файли: mapping.txt (відповідність вихідних та обфускованих імен), seeds.txt (класи, збережені правилами -keep), usage.txt (класи, видалені при мініфікації). Розмір APK після обфускації повинен зменшитися на 20–50% в залежності від кількості підключених бібліотек.

Часто задавані питання

Чим R8 відрізняється від ProGuard?

R8 — наступник ProGuard, розроблений Google. R8 виконує обфускацію, мініфікацію та оптимізацію за один прохід, працює в 2–3 рази швидше ProGuard та інтегрований безпосередньо в Android Gradle Plugin. ProGuard використовує чотири окремі фази та вимагає зовнішнього запуску. Починаючи з AGP 7.0 ProGuard не використовується — за замовчуванням працює R8.

Чи потрібно писати ProGuard rules при використанні R8?

Так, R8 використовує ті ж ProGuard rules (.pro файли). Директиви -keep, -keepclassmembers, -keepattributes, -assumenosideeffects працюють ідентично. Базові правила йдуть в proguard-android-optimize.txt з Android SDK, а специфічні для бібліотек (Retrofit, Room, Gson) додаються в proguard-rules.pro проекту. Без цих правил R8 може видалити класи, необхідні для роботи бібліотек через reflection.

Як включити R8 в Android проекті?

R8 включений за замовчуванням в Android Gradle Plugin починаючи з AGP 3.4. Для активації мініфікації встановіть isMinifyEnabled = true в блоці release buildType файлу build.gradle.kts. Додатковий флаг isShrinkResources = true включає видалення невикористовуваних ресурсів. В gradle.properties можна примусово відключити R8 через android.enableR8=false, але це не рекомендується — R8 швидше та стабільніше.

Що таке обфускація коду в Android?

Обфускація — перейменування класів, методів та полів у короткі безглузді імена (a, b, c). Клас com.example.app.auth.LoginManager перетворюється на a.a.a, метод authenticateUser — на a. Це ускладнює reverse engineering додатка, але не впливає на логіку виконання. ProGuard та R8 перейменовують тільки ті елементи, які не захищені правилами -keep. Mapping-файл зберігає відповідність вихідних та обфускованих імен для декодування crash-логів.

Як налагодити crash-лог з обфускованого додатка?

Для декодування stack trace використовується утиліта retrace (входить до складу ProGuard/R8 SDK). Команда: retrace mapping.txt crash-stacktrace.txt. Mapping-файл знаходиться в build/outputs/mapping/release/mapping.txt. Google Play Console також підтримує завантаження mapping.txt при публікації AAB — crash-логи автоматично декодуються в консолі. Без mapping-файлу stack trace буде містити тільки обфусковані імена a.b.c(), що бесполезно для налагодження.

Підсумки

  • ProGuard — класичний інструмент обфускації та оптимізації Java-байткоду, що складається з чотирьох послідовних фаз
  • R8 — сучасний наступник від Google, вбудований в AGP, що виконує всі фази за один прохід з продуктивністю в 2–3 рази вище
  • Обфускація перейменовує класи, методи та поля в короткі імена, ускладнюючи reverse engineering та захищаючи комерційну логіку додатка
  • Мініфікація видаляє невикористовуваний код та ресурси, зменшуючи розмір APK на 20–50% в типових проектах
  • ProGuard rules (.pro файли) керують поведінкою обфускації — директиви -keep, -keepclassmembers, -assumenosideeffects задають, які елементи зберігаються, видаляються або перейменовуються
  • Mapping-файл (mapping.txt) — критично важливий артефакт збірки для декодування crash-логів з релізних збірок через retrace
  • Тестування релізної збірки на реальному пристрої обов'язкове — проблеми обфускації проявляються тільки в runtime та вимагають додавання недостатніх -keep правил

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також