ProGuard та R8 — інструменти обфускації, мініфікації та оптимізації для Android-додатків. ProGuard, створений у 2002 році, довгий час був стандартом де-факто для захисту Java-коду. R8 — його наступник, розроблений Google та вбудований в Android Gradle Plugin починаючи з AGP 3.4. Обидва інструменти зменшують розмір APK, видаляють мертвий код та ускладнюють reverse engineering. За даними Android Developers, R8 виконує збірку в 2–3 рази швидше за 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 складається з чотирьох послідовних фаз: 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 — це інструмент обфускації та мініфікації наступного покоління від 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.
// 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 обфускація означає перейменування класів, методів та полів у короткі, позбавлені сенсу імена: 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-rules.pro для Android-проекту з Retrofit, Gson та Parcelable. Правила -keep зберігають класи та методи, необхідні для роботи бібліотек через reflection. Без цих правил R8 видалить або перейменує класи, до яких бібліотека звертається по імені рядка.
# =====================
# 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 поля будуть видалені як невикористовувані.
Мініфікація (shrinking) — процес видалення невикористовуваного коду та ресурсів з кінцевої збірки. ProGuard та R8 аналізують граф викликів, починаючи з точок входу (Activity, Service, BroadcastReceiver), і видаляють класи та методи, до яких не можна дійти по ланцюжку викликів. ShrinkResources — додатковий етап, який видаляє невикористовувані ресурси з res/ (layout, drawable, string, color).
Мініфікація дає найбільший виграш у великих проектах з бібліотеками. Типова картина: проект використовує 10% коду з підключеної бібліотеки (наприклад, Google Play Services). Без мініфікації весь код бібліотеки потрапляє в APK. З мініфікацією R8 видаляє 70–90% коду бібліотек, залишаючи лише реально використовувані класи та методи. Це безпосередньо впливає на розмір APK, час завантаження та споживання пам'яті.
Механізм 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-класу.
<!-- Приклад: ресурс, який використовується тільки через 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 є наступником ProGuard, між інструментами є принципові відмінності в архітектурі, продуктивності та поведінці. Google офіційно припинила підтримку ProGuard в Android Gradle Plugin починаючи з AGP 7.0, однак ProGuard продовжує використовуватися в проектах, де потрібна специфічна поведінка оптимізації, недоступна в R8.
| Характеристика | ProGuard | R8 |
|---|---|---|
| Розробник | GuardSquare (Ерік Лафорж) | |
| Рік виходу | 2002 | 2018 (стабільний в 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 агресивніше ProGuard видаляє код, який вважає мертвим. Це призводить до ситуацій, коли збірка в debug працює, а release падає з ClassNotFoundException або NoSuchMethodException. Типові випадки: бібліотеки, що використовують reflection по імені класу (Gson, Moshi, Retrofit, Room, Dagger); виклики ServiceLoader або java.util.ServiceLoader; динамічні проксі (java.lang.reflect.Proxy); нативні методи (JNI). Рішення — додати -keep для всіх класів, які викликаються через reflection.
# Типові проблеми 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 — ключ до стабільної роботи обфускації без багів в runtime. Нижче наведено покроковий процес налаштування для нового проекту або для проекту, в якому обфускація викликає помилки.
Почніть з підключення стандартного файлу Android SDK — proguard-android-optimize.txt. Він містить правила для базових компонентів Android: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Цей файл знаходиться в папці SDK: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. Якщо ви використовуєте AGP, getDefaultProguardFile завантажить його автоматично.
Кожна популярна бібліотека має рекомендовані ProGuard rules. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — всі вони вимагають специфічних -keep правил. Зазвичай правила включені в AAR-бібліотеку і підключаються автоматично через consumer guard rules. Перевірте, що бібліотека надає файл proguard.txt всередині AAR — це ознака того, що правила вже враховані.
Перед публікацією обов'язково протестуйте релізну збірку на реальному пристрої або емуляторі. Проблеми обфускації проявляються тільки в runtime. Перевірте: авторизацію (логін/реєстрацію), завантаження даних з мережі, навігацію між екранами, камеру та галерею, push-сповіщення, Deeplinks, WebView. Кожен краш в релізній збірці потрібно декодувати через retrace з mapping-файлом і додавати недостатні -keep правила.
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 з коментарями для кожної групи правил.
# ===========================================
# 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, розроблений Google. R8 виконує обфускацію, мініфікацію та оптимізацію за один прохід, працює в 2–3 рази швидше ProGuard та інтегрований безпосередньо в Android Gradle Plugin. ProGuard використовує чотири окремі фази та вимагає зовнішнього запуску. Починаючи з AGP 7.0 ProGuard не використовується — за замовчуванням працює 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 Gradle Plugin починаючи з AGP 3.4. Для активації мініфікації встановіть isMinifyEnabled = true в блоці release buildType файлу build.gradle.kts. Додатковий флаг isShrinkResources = true включає видалення невикористовуваних ресурсів. В gradle.properties можна примусово відключити R8 через android.enableR8=false, але це не рекомендується — R8 швидше та стабільніше.
Обфускація — перейменування класів, методів та полів у короткі безглузді імена (a, b, c). Клас com.example.app.auth.LoginManager перетворюється на a.a.a, метод authenticateUser — на a. Це ускладнює reverse engineering додатка, але не впливає на логіку виконання. ProGuard та R8 перейменовують тільки ті елементи, які не захищені правилами -keep. Mapping-файл зберігає відповідність вихідних та обфускованих імен для декодування 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(), що бесполезно для налагодження.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також