Обфускация кода мобильного приложения (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.** { *; }.
Серializованные 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 дня. Стоимость рассчитывается индивидуально.







