ProGuard və R8 — Android tətbiqləri üçün obfuskasiya, minifikasiya və optimallaşdırma alətləri. 2002-ci ildə yaradılmış ProGuard uzun müddət Java kodunun qorunması üçün de-fakto standart olmuşdur. R8 — Google tərəfindən hazırlanmış və AGP 3.4-dən etibarən Android Gradle Plugin-ə inteqrasiya edilmiş onun varisidir. Hər iki alət APK ölçüsünü azaldır, ölü kodu silir və reverse engineering-i çətinləşdirir. Android Developers məlumatına görə, R8 müqayisə olunan obfuskasiya keyfiyyətində ProGuard-dan 2–3 dəfə sürətli qurma yerinə yetirir.
Əsas məqamlar
ProGuard — Java bayt kodunun obfuskasiyası, minifikasiyası, optimallaşdırılması və preverifikasiyası üçün açıq mənbə aləti (Apache 2.0). Erik Laforj tərəfindən 2002-ci ildə SourceForge layihəsi çərçivəsində hazırlanmışdır. ProGuard giriş kimi kompilyasiya olunmuş Java siniflərini (.class) və ya JAR arxivlərini qəbul edir və eyni formatda, lakin daha kiçik ölçüdə və adları dəyişdirilmiş elementlərlə işlənmiş siniflər çıxarır.
Uzun müddət ProGuard Android tətbiqlərini reverse engineering-dən qorumaq üçün yeganə standart idi. Google onun Android SDK-da istifadəsini rəsmi olaraq tövsiyə edir və SDK tools daxilində proguard-android-optimize.txt faylında standart konfiqurasiya təqdim edirdi. ProGuard Java kodunun bayt koduna kompilyasiyasından sonra və DEX-ə paketlənmədən əvvəl işə salınan ayrıca alət kimi çalışırdı.
ProGuard dörd ardıcıl fazadan ibarətdir: shrink (istifadə olunmayan siniflərin silinməsi), optimize (bayt kodunun optimallaşdırılması — inlining, ölü kodun silinməsi), obfuscate (sinif, metod və sahə adlarının qısa adlara dəyişdirilməsi), preverify (JVM-ə uyğunluğun yoxlanılması). Hər bir faza konfiqurasiya fayllarından ayrıca qaydalarla idarə olunur.
Obfuskasiya mərhələsində ProGuard orijinal adları obfuska olunmuş adlara uyğunlaşdıran mapping faylı (mapping.txt) yaradır. Bu fayl retrace vasitəsi ilə release qurmalarından crash-logların dekodlanması üçün kritik əhəmiyyət daşıyır. Mapping faylı olmadan stack trace a(), b(), c() hərflər dəstinə çevrilir və orijinal konteksti bərpa etmək mümkün olmur.
| ProGuard fazası | Məqsəd | Nəticə |
|---|---|---|
| Shrink | Çağrı qrafının təhlili və ölü kodun silinməsi | APK-da siniflərin sayının azaldılması |
| Optimize | Metodların inlining-i, istifadə olunmayan parametrlərin silinməsi | Kodun icrasının sürətləndirilməsi |
| Obfuscate | Sinif, sahə və metod adlarının dəyişdirilməsi | Reverse engineering-dən qorunma |
| Preverify | JVM üçün StackMap atributlarının əlavə edilməsi | Java 6+ ilə uyğunluq |
R8 — Google-dan növbəti nəsil obfuskasiya və minifikasiya aləti, ilk dəfə Android Studio 3.3-də (noyabr 2018) təqdim edilmiş və AGP 3.4-də (avqust 2019) standart halına gəlmişdir. ProGuard-dan fərqli olaraq, R8 Java bayt kodunu DEX formatına çevirən D8/R8 kompilyatorunun bir hissəsidir. R8 bütün fazaları — obfuskasiya, minifikasiya və optimallaşdırmanı — bir keçiddə, alətlər arasında aralıq faylları ötürmədən yerinə yetirir.
Google R8-ni iki məqsədlə hazırlamışdır: qurmanı sürətləndirmək (ProGuard xarici alət kimi çalışırdı) və müasir Android yığını ilə (Desugar, Core Library Desugaring, D8) problemsiz inteqrasiyanı təmin etmək. R8 Kotlin və Java dilində yazılmışdır və AOSP (Android Open Source Project) daxilində R8/Desugar deposunun bir hissəsidir.
R8-in mühüm üstünlüyü ProGuard rules ilə tam geriyə uyğunluqdur. Mövcud .pro faylları dəyişiklik olmadan işləyir. R8 hətta ProGuard-a xas olan -whyareyoukeeping, -printconfiguration və -printmapping direktivlərini dəstəkləyir. Bu o deməkdir ki, ProGuard-dan R8-yə keçid şəffaf şəkildə baş verir: AGP-ni yeniləmək kifayətdir.
// build.gradle.kts — R8-nin minifyEnabled vasitəsilə aktivləşdirilməsi
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
// Android SDK-dan əsas konfiqurasiya
getDefaultProguardFile("proguard-android-optimize.txt"),
// Layihənin fərdi qaydaları
"proguard-rules.pro"
)
}
}
}Kod release qurmasının standart konfiqurasiyasını nümayiş etdirir. isMinifyEnabled = true bayrağı obfuskasiya və optimallaşdırma üçün R8-ni aktivləşdirir. isShrinkResources = true əlavə olaraq istifadə olunmayan resursları silir. getDefaultProguardFile SDK-dan əsas qaydaları yükləyir, proguard-rules.pro isə layihəyə xas parametrləri ehtiva edir.
Obfuskasiya — mənbə kodunun insan tərəfindən təhlili çətin olan, lakin tam funksionallığı qoruyan formaya çevrilməsi prosesidir. Android kontekstində obfuskasiya siniflərin, metodların və sahələrin adlarının qısa, mənasız adlarla əvəz edilməsi deməkdir: com.example.app.auth.LoginManager a.a.a-ya, authenticateUser metodu a-ya, userToken sahəsi isə b-yə çevrilir.
Android APK faylları istənilən arxivləşdirici ilə (ZIP, 7z, WinRAR) açıla bilən arxivlərdir. Obfuskasiya olmadan təcavüzkar tətbiqin tam xəritəsini əldə edir: paket, sinif, metod və sahə adları. jadx və ya Bytecode Viewer kimi alətlər DEX fayllarından demək olar ki, orijinal Java kodunu saniyələr ərzində bərpa edir. Obfuskasiya kodu zərərsiz etmir, lakin giriş həddini əhəmiyyətli dərəcədə artırır: mənalı adlar əvəzinə oxucu a(), b(), c() görür.
Obfuskasiyanın tipik məqsədləri: kommersiya məntiqinin qorunması (alqoritmlər, hesablama düsturları), API açarlarının və tokenlərin oğurlanmasının çətinləşdirilməsi, reflection vasitəsilə siniflərin əvəz edilməsinin qarşısının alınması, APK-nın patçinq və modifikasiyasından qorunma (repackage attack). Praktikada tapşırıqların 70%-i məhz adların dəyişdirilməsi ilə həll olunur — buna görə də ProGuard / R8 işə salınır.
Aşağıda Retrofit, Gson və Parcelable ilə Android layihəsi üçün tipik proguard-rules.pro faylı verilmişdir. -keep qaydaları reflection vasitəsilə kitabxanaların işləməsi üçün zəruri olan sinif və metodları saxlayır. Bu qaydalar olmadan R8 kitabxananın string adı ilə müraciət etdiyi sinifləri siləcək və ya adlarını dəyişəcək.
# =====================
# Retrofit — interfeyslərin saxlanması
# =====================
-keep,allowobfuscation,allowshrinking interface retrofit2.** { *; }
-keepattributes Signature, Exceptions
# =====================
# Gson — JSON seriyalaşdırması
# =====================
-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 — release-dən logların silinməsi
# =====================
-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 data sinifləri — konstruktorların saxlanması
# =====================
-keepclassmembers class * {
@kotlin.Metadata <fields>;
}
# =====================
# Activity — giriş nöqtəsi
# =====================
-keep class * extends android.app.Activity {
@android.annotation.SuppressLint <methods>;
}.pro faylındakı hər bir direktiv konkret vəzifəni həll edir. -keep sinfin tamamilə silinməsinin və ya adının dəyişdirilməsinin qarşısını alır. -keepclassmembers yalnız sinif üzvlərini (sahələr və metodlar) qoruyur, lakin sinfin özünün istifadə olunmadıqda silinməsinə icazə verir. -assumenosideeffects R8-yə metod çağırışının yan təsirlərinin olmadığını və təhlükəsiz silinə biləcəyini göstərir. -keepattributes direktivi bayt kodunda metadata — annotasiyalar, imzalar, istisnalar saxlayır.
Retrofit üçün -keep,allowobfuscation,allowshrinking qaydası R8-yə interfeyslərin adlarını dəyişməyə icazə verir, lakin onları silməməyi tapşırır. Bu zəruridir, çünki Retrofit interfeyslərə dinamik proksi (java.lang.reflect.Proxy) vasitəsilə müraciət edir və silinmə runtime-da ClassNotFoundException-a səbəb olacaq. Eyni şəkildə, Gson @SerializedName annotasiyası olan sahələrə daxil olmaq üçün reflection-dan istifadə edir — -keepclassmembers olmadan sahələr istifadə olunmayan kimi silinəcək.
Minifikasiya (shrink) — yekun qurmadan istifadə olunmayan kod və resursların silinməsi prosesi. ProGuard və R8 giriş nöqtələrindən (Activity, Service, BroadcastReceiver) başlayaraq çağrı qrafını təhlil edir və çağrı zənciri ilə çatmaq mümkün olmayan sinif və metodları silir. ShrinkResources — res/ (layout, drawable, string, color) daxilində istifadə olunmayan resursları silən əlavə mərhələdir.
Minifikasiya kitabxanaları olan böyük layihələrdə ən böyük qazancı verir. Tipik mənzərə: layihə qoşulmuş kitabxananın (məsələn, Google Play Services) 10% kodundan istifadə edir. Minifikasiya olmadan kitabxananın bütün kodu APK-ya daxil olur. Minifikasiya ilə R8 kitabxana kodunun 70–90%-ni silir, yalnız faktiki istifadə olunan sinif və metodları qoyur. Bu, birbaşa APK ölçüsünə, yükləmə müddətinə və yaddaş istehlakına təsir edir.
ShrinkResources mexanizmi kod minifikasiyası ilə birlikdə işləyir. R8 hansı siniflərin istifadə olunduğunu müəyyən etdikdən sonra resurs shrinking koddan resurslara istinadları təhlil edir: R.layout.main, R.drawable.icon, getString(R.string.title). Birbaşa və ya dolayı istinadı olmayan bütün resurslar yekun APK və ya AAB-dan silinir. Bunun üçün resources.arsc resurs faylı və res/ qovluqlarından istifadə olunur.
Vacib nüans: resurslar R sinfindən yan keçərək getIdentifier() və ya Resources.getResourceName() vasitəsilə string adı ilə çağırıla bilər. Belə hallarda R8 birbaşa əlaqəni görmür və faktiki istifadə olunan resursu silə bilər. Belə resursları qorumaq üçün -keep class **.R$* { *; } direktivi mövcuddur — o, R sinfinin bütün identifikatorlarını saxlayır.
<!-- Nümunə: yalnız getIdentifier() vasitəsilə istifadə olunan resurs -->
<string name="dynamic_title_welcome">Xoş gəlmisiniz</string>
<string name="dynamic_title_share">Paylaş</string>
<!-- String ilə müraciət edən Kotlin kodu -->
<!-- val title = getString(resources.getIdentifier( -->
<!-- \"dynamic_title_${type}\", \"string\", packageName)) -->Bu halda R8 R sinfində dynamic_title_welcome-ə statik istinad görmür, çünki müraciət dinamik adla getIdentifier vasitəsilə baş verir. Belə resursları saxlamaq üçün proguard-rules.pro faylına -keepclassmembers class **.R$string { *; } direktivi əlavə edilməlidir — o, bütün R$string siniflərindən istənilən sahələrin silinməsini qadağan edir.
| Direktiv | Məqsəd | Nümunə |
|---|---|---|
| -keep | Sinifi və bütün üzvlərini saxlayır | -keep class com.example.api.** { *; } |
| -keepclassmembers | Yalnız sinif üzvlərini saxlayır | -keepclassmembers class * { @SerializedName <fields>; } |
| -keepattributes | Bayt kodunun metadata saxlayır | -keepattributes *Annotation*, Signature |
| -assumenosideeffects | Yan təsiri olmayan çağırışları silir | -assumenosideeffects class Log { d(...); } |
| -dontwarn | Xəbərdarlıqları yatırır | -dontwarn com.example.legacy.** |
R8 ProGuard-ın varisi olsa da, alətlər arasında arxitektura, performans və davranış baxımından prinsipial fərqlər mövcuddur. Google Android Gradle Plugin-də AGP 7.0-dan etibarən ProGuard dəstəyini rəsmi olaraq dayandırmışdır, lakin ProGuard R8-də mövcud olmayan xüsusi optimallaşdırma davranışı tələb olunan layihələrdə istifadə olunmağa davam edir.
| Xarakteristika | ProGuard | R8 |
|---|---|---|
| Yaradıcı | GuardSquare (Erik Laforj) | |
| Buraxılış ili | 2002 | 2018 (stabil 2019) |
| Arxitektura | 4 ayrı faza (shrink → optimize → obfuscate → preverify) | Bir keçid: shrink + optimize + obfuscate eyni vaxtda |
| AGP-yə inteqrasiya | Xarici alət, javac-dan sonra işə salınır | D8 DEX kompilyatoruna daxil edilmişdir |
| Qurma sürəti | 2–3 dəfə yavaş | Tək keçid və nativ inteqrasiya hesabına daha sürətli |
| Kotlin dəstəyi | Məhdud (inline, lambdas, coroutines ilə problemlər) | Tam: coroutines, inline-funksiyalar, data class |
| Mapping faylı | mapping.txt (retrace ilə uyğun) | mapping.txt (eyni format) |
| Optimallaşdırmanın fərdiləşdirilməsi | 60+ seçim -optimizationpasses, -optimizations | Məhdud: əksər optimallaşdırmalar standart olaraq aktivdir |
| Dəstək statusu | R8 ilə əvəz edilmişdir (AGP 7.0+ istifadə etmir) | Aktiv inkişaf, AOSP-nin bir hissəsi |
R8 ProGuard-dan daha aqressiv şəkildə ölü hesab etdiyi kodu silir. Bu, debug qurmasının işlədiyi, lakin release-in ClassNotFoundException və ya NoSuchMethodException ilə çökdüyü vəziyyətlərə gətirib çıxarır. Tipik hallar: sinif adı ilə reflection-dan istifadə edən kitabxanalar (Gson, Moshi, Retrofit, Room, Dagger); ServiceLoader və ya java.util.ServiceLoader çağırışları; dinamik proksilər (java.lang.reflect.Proxy); nativ metodlar (JNI). Həll yolu — reflection vasitəsilə çağırılan bütün siniflər üçün -keep əlavə etmək.
# Reflection-un tipik problemləri — R8 statik əlaqəni görmür
# Room — DAO və miqrasiyaları saxlayırıq
-keep class * extends androidx.room.RoomDatabase { *; }
-keep class *.DatabaseMigrations { *; }
# Dagger / Hilt — komponentləri saxlayırıq
-keep class * extends dagger.hilt.android.components.** { *; }
# JNI — nativ metodların adlarını dəyişmirik
-keepclasseswithmembernames class * {
native <methods>;
}
# Data Binding — Binding siniflərini saxlayırıq
-keep class *.databinding.** { *; }Əgər qaydaları əlavə etdikdən sonra da qurma çökürsə, proguard-rules.pro faylında -printconfiguration full-config.txt bayrağından istifadə edin. R8 hansı qaydaların tətbiq olunduğunu və hansı siniflərin saxlanıldığını göstərən tam konfiqurasiya faylı yaradacaq. Həmçinin -whyareyoukeeping class com.example.MyClass direktivi faydalıdır — o, R8-nin bu sinfi saxlamaq qərarının səbəbini göstərir.
ProGuard rules-un düzgün qurulması — runtime-da səhvlər olmadan obfuskasiyanın stabil işləməsinin açarıdır. Aşağıda yeni layihə və ya obfuskasiyanın səhvlərə səbəb olduğu layihə üçün addım-addım qurma prosesi verilmişdir.
Android SDK-nın standart faylını — proguard-android-optimize.txt-i qoşmaqla başlayın. O, əsas Android komponentləri üçün qaydaları ehtiva edir: Activity, Service, BroadcastReceiver, ContentProvider, View, Fragment. Bu fayl SDK qovluğunda yerləşir: $ANDROID_HOME/tools/proguard/proguard-android-optimize.txt. AGP istifadə edirsinizsə, getDefaultProguardFile onu avtomatik yükləyəcək.
Hər bir məşhur kitabxananın tövsiyə olunan ProGuard qaydaları var. Retrofit, OkHttp, Glide, Fresco, Coil, Room, Dagger/Hilt, Kotlin Coroutines — bunların hamısı xüsusi -keep qaydaları tələb edir. Adətən qaydalar AAR kitabxanasına daxil edilir və consumer guard rules vasitəsilə avtomatik qoşulur. Kitabxananın AAR daxilində proguard.txt faylı təqdim edib-etmədiyini yoxlayın — bu, qaydaların artıq nəzərə alındığının göstəricisidir.
Nəşrdən əvvəl release qurmasını mütləq real cihazda və ya emulyatorda test edin. Obfuskasiya problemləri yalnız runtime-da üzə çıxır. Yoxlayın: avtorizasiya (daxil olma/qeydiyyat), şəbəkədən məlumat yüklənməsi, ekranlar arasında naviqasiya, kamera və qalereya, push bildirişləri, Deeplinks, WebView. Release qurmasındakı hər bir crash mapping faylı ilə retrace vasitəsilə dekodlanmalı və çatışmayan -keep qaydaları əlavə edilməlidir.
Mapping faylı build/outputs/mapping/release/mapping.txt ünvanında yaradılır. Bu fayl saxlamaq üçün məcburidir: onsuz Google Play Console-dan crash-logları dekodlamaq mümkün deyil. Mapping.txt faylını versiya idarəetmə sisteminə daxil edin və ya CI-artefaktları kimi yükləyin. Google Play Console uploading mapping.txt aktiv edilmiş AAB yüklənərkən mapping faylını avtomatik qəbul edir.
Aşağıda hər bir qayda qrupu üçün şərhlərlə birlikdə proguard-rules.pro faylında obfuskasiyanın qurulmasının tam workflow-u verilmişdir.
# ===========================================
# proguard-rules.pro — tam nümunə
# ===========================================
# --- Ümumi parametrlər ---
-keepattributes *Annotation*, Signature, Exceptions, InnerClasses, EnclosingMethod
-dontpreverify
# --- Android komponentləri ---
-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 {}
# --- Seriyalaşdırma ---
-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();
}
# --- Yalnız R8: məcburi saxlama ---
# (ProGuard bu direktivi nəzərə almır)
-keep,allowobfuscation class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}Qurmadan sonra kompilasiyanı yerinə yetirin: ./gradlew assembleRelease. build/outputs/mapping/release/ qovluğunda faylların yarandığını yoxlayın: mapping.txt (orijinal və obfuska olunmuş adların uyğunluğu), seeds.txt (-keep qaydaları ilə saxlanılan siniflər), usage.txt (minifikasiya zamanı silinmiş siniflər). Obfuskasiyadan sonra APK ölçüsü qoşulmuş kitabxanaların sayından asılı olaraq 20–50% azalmalıdır.
Tez-tez verilən suallar
R8 — Google tərəfindən hazırlanmış ProGuard-ın varisidir. R8 obfuskasiya, minifikasiya və optimallaşdırmanı bir keçiddə yerinə yetirir, ProGuard-dan 2–3 dəfə sürətli işləyir və birbaşa Android Gradle Plugin-ə inteqrasiya olunub. ProGuard dörd ayrı fazadan istifadə edir və xarici işə salınma tələb edir. AGP 7.0-dan etibarən ProGuard istifadə olunmur — standart olaraq R8 işləyir.
Bəli, R8 eyni ProGuard rules (.pro faylları) istifadə edir. -keep, -keepclassmembers, -keepattributes, -assumenosideeffects direktivləri eyni şəkildə işləyir. Əsas qaydalar Android SDK-dan proguard-android-optimize.txt faylında gəlir, kitabxanalara (Retrofit, Room, Gson) xas olanlar isə layihənin proguard-rules.pro faylına əlavə edilir. Bu qaydalar olmadan R8 reflection vasitəsilə işləyən kitabxanalar üçün zəruri olan sinifləri silə bilər.
R8 AGP 3.4-dən etibarən Android Gradle Plugin-də standart olaraq aktivdir. Minifikasiyanı aktivləşdirmək üçün build.gradle.kts faylının release buildType blokunda isMinifyEnabled = true təyin edin. Əlavə isShrinkResources = true bayrağı istifadə olunmayan resursların silinməsini aktivləşdirir. gradle.properties faylında android.enableR8=false vasitəsilə R8-ni məcburi olaraq söndürmək olar, lakin bu tövsiyə edilmir — R8 daha sürətli və stabil daha etibarlıdır.
Obfuskasiya — siniflərin, metodların və sahələrin adlarının qısa mənasız adlarla (a, b, c) əvəz edilməsidir. com.example.app.auth.LoginManager sinfi a.a.a-ya, authenticateUser metodu a-ya çevrilir. Bu, tətbiqin reverse engineering-ni çətinləşdirir, lakin icra məntiqinə təsir etmir. ProGuard və R8 yalnız -keep qaydaları ilə qorunmayan elementlərin adlarını dəyişir. Mapping faylı crash-logların dekodlanması üçün orijinal və obfuska olunmuş adların uyğunluğunu saxlayır.
Stack trace-in dekodlanması üçün retrace utilitesi (ProGuard/R8 SDK-nın tərkib hissəsi) istifadə olunur. Komanda: retrace mapping.txt crash-stacktrace.txt. Mapping faylı build/outputs/mapping/release/mapping.txt ünvanında yerləşir. Google Play Console da AAB nəşr edilərkən mapping.txt yüklənməsini dəstəkləyir — crash-loglar konsolda avtomatik dekodlanır. Mapping faylı olmadan stack trace yalnız a.b.c() kimi obfuska olunmuş adları ehtiva edəcək, bu da debug üçün faydasızdır.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun