Wstęp
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ąc dekompilacje. Użycie kompilatora R8
powinno być nieodłącznym elementem budowania wersji produkcyjnej.
Pliki
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 tworzone 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.
Konfiguracja
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.
Zmniejszanie kodu
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 część może zostać zignorowana i niedołączona do plików DEX
. Punkty wejścia określane są 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-rules.pro
lub oznaczyć fragmenty kodu adnotacją @Keep
.
Zmniejszanie zasobów
Podczas analizy dokonywanej przez kompilator R8
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 mogą zostać 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
.
Optymalizacja
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 być także przepisany do krótszej formy, np. metoda wywoływana w jednym miejscu jest usunięta, a jej ciało przeniesione w miejsce wywołania. Domyślne ustawienia optymalizacji zawierają są w pliku proguard-android-optimize.txt
.
Zaciemnianie
Zaciemniania (obfuscation
) jest częścią procesu zmniejszania kodu. Redukuje rozmiar aplikacji oraz utrudnienia dekompilację. Polega m.in. na skracaniu nazw klas, metod i pól.
Inżynieria wsteczne
Inżynieria wsteczna (reverse engineering
) jest procesem badania programu w celu ustalenie sposobu działania. Polega na dekompilacji
kodu oraz poddawaniu go statycznej i dynamicznej analizie
. W przypadku aplikacji dla systemu Android
zalecane jest wykorzystanie różnych narzędzi umożliwiających częściową lub całkowitą dekompilacje. Android Studio
posiada wbudowane narzędzie analizy APK
, które dostarcza wielu informacji nt pliku wyjściowego w tym także pozwala na przeprowadzenie analizy zdekompilowanych zasobów oraz kodu. Ponadto wykorzystanie dex2jar
wraz z jd-gui
umożliwia konwersję pliku APK
do JAR
oraz przeglądanie zawartości plików źródłowych (klas). Pomimo zastosowania zaciemniania kodu wraz z zasadami ProGuard
możliwe jest jego przekształcenie do czytelnego zapisu dzięki czemu może on stać się zrozumiały i ujawniać informacje wrażliwe takie jak autorskie algorytmy czy klucze. Dobrze zaprojektowany i zaprogramowany kod nawet w przypadku ujawnienia jego zawartości powinien minimalizować ryzyko utraty bezpieczeństwa i wycieku informacji. Zespół deweloperski jest zobowiązany zidentyfikować wrażliwe obszary aplikacji których znajomość osób trzecich może naruszać bezpieczeństwo
i ujawnienie know-how
oraz podjąć dodatkowe środki ostrożności. Przykładowo część kodu może być zależna od informacji ze zdalnego serwera. Alternatywnym sposobem na zaciemnienie kodu jest użycie DexGuard
, który jest narzędziem zaprojektowanym dla aplikacji Android
. Oferuje on znacznie większą ochronę przed statyczną i dynamiczną analize, aplikuje wiele warstw szyfrowania i zaciemniania oraz przetwarza nie tylko kod bajtowy lecz wszystkie komponenty aplikacji w tym zasoby.