|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "ProGuard" |
| 4 | +date: 2019-09-23 |
| 5 | +categories: ["Budowanie"] |
| 6 | +image: build/proguard |
| 7 | +github: build/tree/master/proguard |
| 8 | +description: "Budowanie" |
| 9 | +keywords: "budowanie, kompilacja, konfiguracja, build, proguard, apk, properties, configuration, shrink, code, resource, keep, obfuscate, optimize, android, programowanie, programming" |
| 10 | +--- |
| 11 | + |
| 12 | +## Wstęp |
| 13 | +Podczas procesu budowania projektu warto zadbać o optymalizację rozmiaru pliku wyjściowego. W tym celu `Android Gradle plugin` wykorzystuje kompilator `R8` wraz z zasadami `ProGuard`. Pozwala on na zmniejszanie rozmiaru poprzez wycinanie nieużywanego kodu i zasobów oraz zaciemnianie kodu dodatkowo utrudniającego dekompilacje. Użycie kompilatora `R8` powinno być nieodłącznym elementem budowania wersji produkcyjnej. |
| 14 | + |
| 15 | +## Pliki |
| 16 | +Definiowanie zasad w plikach `ProGuard` pozwala na konfigurację i nadpisanie domyślnego zachowania kompilatora `R8`. `Android Studio` dla każdego modułu automatycznie tworzy pusty plik `proguard-rules.pro` w którym definiowane są zasady dla danego modułu natomiast `Android Gradle plugin` generuje `proguard-android-optimize.txt` zawierający zasady optymalizacji użyteczne dla projektów Android takie jak np. zachowanie adnotacji. Jeśli zewnętrzna biblioteka zawiera własne zasady `ProGuard` opisane w pliku `proguard.txt` i lokalizacji `META-INF/proguard` są one również addytywnie aplikowane dla całego projektu i nie mogą zostać usunięte co może w sposób niepożądany znacząco zmienić oczekiwaną konfiguracje. Ponadto po pozytywnym zakończeniu budowania projektu w podkatalogu wariantu budowania może zostać wygenerowany plik `aapt_rules.txt` zawierający zasady `keep` dla klas opisanych w manifeście, plikach layout i innych zasobach oznaczonych jako potencjalne punkty wejścia. |
| 17 | + |
| 18 | +## Konfiguracja |
| 19 | +Podstawowa konfiguracja pliku `build.gradle` definuje zastosowanie lub pominięcie procesu zmniejszania, zaciemniania i optymalizacji zarówno dla kodu jak i zasobów za pomocą wpisów `minifyEnabled`, `shrinkResources` oraz `proguardFiles` dla ścieżki plików. Domyślnie są one jednak wyłączone ponieważ wydłużają czas kompilacji i mogą powodować występowanie błędów w przypadku niepełnej konfiguracji. |
| 20 | + |
| 21 | +{% highlight gradle %} |
| 22 | +android { |
| 23 | + |
| 24 | + buildTypes { |
| 25 | + release { |
| 26 | + // Enable code shrinking, obfuscation, optimization |
| 27 | + minifyEnabled true |
| 28 | + |
| 29 | + // Enable resource shrinking, requires minifyEnabled to be applied |
| 30 | + shrinkResources true |
| 31 | + |
| 32 | + // Specify ProGuard rules files |
| 33 | + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
| 34 | + } |
| 35 | + debug { |
| 36 | + // don't need to enable minifyEnabled and shrinkResources for debug purpose |
| 37 | + } |
| 38 | + } |
| 39 | + |
| 40 | + flavorDimensions "version" |
| 41 | + productFlavors { |
| 42 | + free { |
| 43 | + //... |
| 44 | + dimension "version" |
| 45 | + proguardFile 'free-rules.pro' //add extra rules for flavors |
| 46 | + } |
| 47 | + paid { |
| 48 | + //... |
| 49 | + dimension "version" |
| 50 | + } |
| 51 | + } |
| 52 | +} |
| 53 | +{% endhighlight %} |
| 54 | + |
| 55 | +## Zmniejszanie kodu |
| 56 | +Zmniejszanie kodu (`code shrinking`) wykrywa i bezpiecznie usuwa nieużywane klasy, pola i metody z aplikacji oraz zewnętrznych zależności. W wyniku tego procesu plik `APK` zawiera tylko rzeczywiście używany kod co w niektórych sytuacjach może znacznie przyczynić się do redukcji rozmiaru. Cały proces jest jednak dość kosztowny i wydłuża czas kompilacji spowodowany analizą wszystkich `punktów wejścia` tzn. klas które mogą zostać użyte przez platformę do uruchomienia aktywności czy usługi. Na ich podstawie tworzony jest `graf` wszystkich metod, zmiennych i innych klas do których aplikacja może uzyskać dostęp w czasie działania. Kod niezawierający się w grafie jest traktowany jako nieosiągalny i może zostać usunięty z aplikacji. Przykładowo aplikacja wykorzystuje tylko kilka metod oraz klas z zewnętrznej biblioteki w związku z czym pozostała jej nieużywana przez aplikacje część może zostać zignorowana i niedołączona do plików `DEX`. Punkty wejścia są określane na podstawie automatycznie generowanego pliku `aapt_rules.txt` oraz ręcznie opisanych zasad `keep` w plikach `ProGuard`. Przeważnie `R8` usuwa tylko rzeczywiście nieużywany kod jednakże w przypadku użycia mechanizmu `refleksji` czy wywołań `JNI` (`Java Native Interface`) może nie być w stanie poprawnie zbudować grafu. W takim przypadku należy dodać odpowiednie zasady `keep` do pliku `ProGuard` lub oznaczyć fragmenty kodu adnotacją `@Keep`. |
| 57 | + |
| 58 | +{% highlight pro %} |
| 59 | +-keep class CustomClass |
| 60 | +-keep class * implements CustomInterface |
| 61 | +-keep public class models.** { *; } |
| 62 | +{% endhighlight %} |
| 63 | + |
| 64 | +## Zmniejszanie zasobów |
| 65 | +Podczas analizy dokonywanej przez kompilator poza budowaniem grafu oraz operacjami zmniejszania, zaciemniania i optymalizacji kodu następuje także identyfikacja i oznaczanie zasobów pod kątem wykorzystania w osiągalnym kodzie. Następnie w procesie zmniejszania zasobów (`resource shrinking`) te nieużywane zostają usunięte. Zasada ta nie dotyczy jednak zasobów alternatywnych np. inna gęstość czy język. Jeśli zasoby o tej samej nazwie, typie i kwalifikatorze występują w wielu lokalizacjach wówczas tylko jeden zostaje przekazany zgodnie z priorytetem (`build type > build flavor > main > library`). W przypadku budowania różnych wariantów aplikacji mogą występować różnice w rzeczywistym użyciu zasobów. W takiej sytuacji należy określić własne zasady zachowania zasobów definiowanych w pliku `xml` w folderze `raw`. |
| 66 | + |
| 67 | +{% highlight xml %} |
| 68 | +<resources |
| 69 | + xmlns:tools="http://schemas.android.com/tools" |
| 70 | + tools:keep="@layout/used1, @drawable/used2" |
| 71 | + tools:discard="@menu/unused1" /> |
| 72 | +{% endhighlight %} |
| 73 | + |
| 74 | +## Zaciemnianie |
| 75 | +Celem zaciemniania (`obfuscation`) kodu jest redukcja rozmiaru aplikacji oraz utrudnienie procesu dekompilacji. Realizowane jest to poprzez skrócenie nazw klas, metod i pól. Konfiguracja może być definiowana w zasadach `ProGuard`. |
| 76 | + |
| 77 | +{% highlight pro %} |
| 78 | +-keepattributes SourceFile, LineNumberTable |
| 79 | +-keepattributes *Annotation* |
| 80 | +-keeppackagenames package.name |
| 81 | +{% endhighlight %} |
| 82 | + |
| 83 | +## Optymalizacja |
| 84 | +W trakcie procesu redukcji kodu kompilator `R8` dokonuje także analizy na głębszym poziomie w celu zastosowania optymalizacji. Jeśli kod nigdy nie osiągnie pewnego warunku wówczas cała przypisana mu gałąź może zostać usunięta. Ponadto kod może zostać także przepisany do krótszej formy, np. metoda wywoływana w jednym miejscu może zostać usunięta i jej ciało przeniesione w miejsce wywołania. Dodatkowe ustawienia optymalizacji deklarowane są w pliku `gradle.properties`. |
0 commit comments