Обфускація коду мобільного додатку (ProGuard/R8 для Android)
R8 — компілятор та мініфікатор, який з AGP 3.4 замінив ProGuard у Android-сборках. Він робить три речі: видаляє невикористаний код (tree shaking), переносить класи та методи в однолітерні ідентифікатори (obfuscation), оптимізує байткод. ProGuard-правила при цьому залишаються — R8 їх читає, синтаксис сумісний.
Включається в build.gradle:
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
proguard-android-optimize.txt — базовий файл Google з агресивними оптимізаціями. proguard-rules.pro — користувацькі правила проекту.
Де ломається після включення обфускації
Включили isMinifyEnabled = true — додаток крашується в release. Це стандартна ситуація, і розібратися в ній без розуміння механізму складно.
Класи, використовувані через рефлексію. JSON-бібліотеки (Gson, Moshi без codegen), які створюють об'єкти через Class.forName() або читають поля через рефлексію — не знають про обфускацію на compile-time. R8 переносить UserResponse.userId в a.a — Gson ище поле userId, не знаходить, мовчки повертає null. Краху немає, дані просто не десеріалізувалися. Рішення: @Keep аннотації або правило -keepclassmembers class com.example.data.** { *; }. З Gson краще перейти на kotlinx.serialization з KSP — там кодогенерація не залежить від рефлексії.
Retrofit-інтерфейси. Retrofit через рефлексію читає аннотації методів (@GET, @POST). Інтерфейси API потрібно зберігати: -keep interface com.example.api.** { *; }.
Серіалізовані Parcelable та Serializable класи. Якщо об'єкт передається через Intent.putExtra() або зберігається в Bundle — імена полів повинні збігатися. R8 їх переносить. Правило: -keepclassmembers class * implements android.os.Parcelable { *; }.
Firebase Crashlytics та стектрейси. Обфускація робить краш-репорти нечитаємими: a.b.c замість реальних імен. Рішення — mapping-файл. Crashlytics автоматично підхоплює app/build/outputs/mapping/release/mapping.txt при використанні com.google.firebase.crashlytics Gradle-плагіну. Маппінг потрібно зберігати — без нього старі краші не деобфускуватимуться.
Native-бібліотеки та JNI. Методи, викликувані з C++ через JNI, повинні зберігати точні імена: -keepclasseswithmembernames class * { native <methods>; }.
Як перевірити правила
-printusage build/outputs/usage.txt — список видаленого коду. –printseeds build/outputs/seeds.txt — що збережено. Аналіз цих файлів після сборки показує, не видалив ли R8 щось важливе.
apkanalyzer (входить в Android SDK): apkanalyzer dex packages app-release.apk — список класів у фінальному APK. Якщо потрібного класу немає — він вирізаний.
Тестування release-сборки через Firebase App Distribution або внутрішній трек Google Play обов'язково до публікації. Debug-сборка з isMinifyEnabled = false не покаже проблем обфускації.
Бібліотеки з вбудованими правилами. Більшість Jetpack-бібліотек та популярних SDK включають consumer-rules.pro в AAR — R8 застосовує їх автоматично. Але сторонні та legacy-бібліотеки часто не мають вбудованих правил.
Настройка обфускації з аудитом існуючих правил та тестуванням release-сборки: 1–3 дні. Ціна розраховується індивідуально.







