Wolna aplikacja to główny powód, dla którego użytkownicy usuwają programy. Milisekundy opóźnienia przy uruchamianiu lub przewijaniu listy zmniejszają retencję o dziesiątki procent. Wydajność (performance) to nie tylko szybkość, ale także stabilność: brak ANR, crashy i wycieki pamięci. W tym artykule omówimy wszystkie aspekty wydajności: od zarządzania pamięcią (GC, ARC) po profilowanie narzędziami. Więcej w oficjalnym przewodniku Android Performance.
Najważniejsze
Wydajność aplikacji jest bezpośrednio związana z jank — zauważalnym opóźnieniem między działaniem użytkownika a reakcją interfejsu. Główne przyczyny: blokowanie głównego wątku (ciężkie operacje na wątku UI), częste przerysowywanie układu (overdraw), wycieki pamięci (częsty GC), nieoptymalne algorytmy (O(n²) na dużych danych). Frame Rate (FPS) — liczba klatek na sekundę. Do komfortowego doświadczenia potrzebne są stabilne 60 FPS (Android) lub 120 FPS (iPhone Pro, iPad Pro). VSync synchronizuje renderowanie z częstotliwością odświeżania ekranu.
Jank występuje, gdy renderowanie pojedynczej klatki przekracza 16,6 ms (dla 60 FPS) lub 8,3 ms (dla 120 FPS). Profilowanie GPU (Profile GPU Rendering na Androidzie, Core Animation na iOS) pokazuje, które etapy renderowania zajmują najwięcej czasu. Główne etapy: Layout (rozmieszczenie elementów), Draw (rysowanie), Display (przekazanie do bufora klatek). Najczęstszym problemem jest inflacja układu w XML, szczególnie przy użyciu złożonych zagnieżdżonych ConstraintLayout.
Time-to-Interactive (TTI) — czas, w którym aplikacja staje się w pełni gotowa do interakcji. TTI obejmuje Cold Start, ładowanie danych i inicjalizację bibliotek. Google zaleca TTI poniżej 5 sekund, Apple — poniżej 2 sekund dla głównych ekranów. Lazy Loading — technika opóźnionego ładowania treści i bibliotek, kluczowa dla poprawy TTI. W IT Sectr domyślnie stosujemy leniwą inicjalizację we wszystkich projektach.
ANR i Crash to główni wrogowie wydajności aplikacji mobilnych. ANR (Application Not Responding) — okno dialogowe na Androidzie, które pojawia się, gdy główny wątek jest zablokowany przez ponad 5 sekund. Przyczyny: synchroniczne żądania sieciowe na wątku UI, praca z bazą danych bez coroutine, dekodowanie dużego bitmapy bez downsamplingu, deadlock na głównym wątku. Stos wywołań ANR jest zapisywany w /data/anr/traces.txt i pozwala określić dokładne miejsce blokady.
Crash — nieoczekiwane zakończenie aplikacji. Na Androidzie — to Exception (Java/Kotlin) lub Signal (kod natywny). Na iOS — NSException lub sygnał (EXC_BAD_ACCESS — dostęp do zwolnionej pamięci). Narzędzia do raportowania crashy: Firebase Crashlytics, Sentry, BugSnag. Zbierają stacktrace, dane urządzenia i kroki reprodukcji. Stack Overflow — przepełnienie stosu wywołań przez nieskończoną rekurencję. OutOfMemoryError — gdy sterta (heap) jest pełna.
StrictMode — narzędzie Android do wykrywania naruszeń bezpieczeństwa wątków. Pozwala ustawić reguły: ThreadPolicy (zakaz dysku/sieci na głównym wątku), VmPolicy (wykrywanie wycieków Activity, SQLite, CloseGuard). StrictMode powinien być włączony tylko w kompilacjach debug — w wydaniu nie powinien działać. Na iOS odpowiednikiem jest Main Thread Checker (Xcode), który automatycznie wykrywa wywołania UIKit poza głównym wątkiem.
Wyciek pamięci (Memory Leak) — sytuacja, w której obiekt pozostaje w pamięci, mimo że aplikacja już go nie używa. To bezpośrednio obniża wydajność aplikacji. Na Androidzie GC (Garbage Collection) nie może zebrać obiektu, jeśli istnieje do niego silne odniesienie. Typowe przyczyny: statyczne odniesienia do Activity, nieanulowane callbacki/obserwatorzy, klasy wewnętrzne z niejawnym odniesieniem do klasy zewnętrznej, Handler z nieoczyszczonymi wiadomościami. LeakCanary — biblioteka do automatycznego wykrywania wycieków.
ARC (Automatic Reference Counting) — model zarządzania pamięcią w iOS. Każdy obiekt ma licznik odniesień (retain count). Gdy licznik osiągnie zero, pamięć jest zwalniana. Retain Cycle występuje, gdy dwa obiekty utrzymują silne odniesienia do siebie nawzajem (A → B i B → A). ARC nigdy nie wyzeruje liczników. Rozwiązanie: słabe odniesienia (weak) lub bez właściciela (unowned). Weak automatycznie zeruje się (staje się nil) przy zwalnianiu obiektu. Unowned nie zeruje się, ale gwarantuje, że obiekt żyje.
GC (Garbage Collection) działa na Androidzie (Java/Kotlin). GC okresowo wstrzymuje wykonanie (pauza Stop-the-World) w celu znalezienia i zwolnienia nieosiągalnych obiektów. Wyzwalacz GC: gdy sterta (heap) wypełni się do określonego procentu. ARC działa na iOS (Swift/Objective-C) i nie ma pauz — liczniki są aktualizowane atomowo przy każdym przypisaniu. ARC jest bardziej przewidywalny, ale może gromadzić nadmierne operacje retain/release przy wysokiej częstotliwości przypisań.
Słabe odniesienie (Weak Reference) i silne odniesienie (Strong Reference) — typ odniesienia określa, czy GC/ARC może zwolnić obiekt. Strong Reference — obiekt nie zostanie zebrany, dopóki istnieje to odniesienie. Weak Reference — GC/ARC może zebrać obiekt; słabe odniesienie staje się nil (w Swift/Java WeakReference). Unowned Reference (Swift) — nie zeruje się przy zwalnianiu; dostęp do niego po śmierci obiektu powoduje crash. Na Androidzie do słabych odniesień używa się java.lang.ref.WeakReference.
Przykład wykrywania wycieku na Androidzie przez LeakCanary:
// Утечка: анонимный класс держит ссылку на Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
// Используем `this@MainActivity`, сохраняя ссылку на Activity
Log.d("TAG", "Handler received message")
}
}
handler.sendEmptyMessageDelayed(0, 60000)
}
}
// Исправление: статический Handler + WeakReference
class SafeHandler(activity: MainActivity) : Handler() {
private val weakActivity =
WeakReference(activity)
override fun handleMessage(msg: Message) {
weakActivity.get() ?: return
Log.d("TAG", "Handler received message")
}
}
Profilowanie to proces pomiaru wydajności aplikacji: CPU, pamięć, sieć, zużycie energii. Bez profilowania optymalizacja na ślepo jest bezużyteczna — nie dowiesz się, która część kodu faktycznie spowalnia.
| Narzędzie | Platforma | Mierzy | Kiedy używać |
|---|---|---|---|
| Instruments (Time Profiler) | iOS | CPU, wywołania funkcji, czas wykonania | Optymalizacja algorytmów, szukanie wąskich gardeł |
| Instruments (Allocations) | iOS | Pamięć, liczba obiektów, retain counts | Szukanie wycieków i nadmiernego zużycia pamięci |
| Instruments (Leaks) | iOS | Retain cycles, wycieki pamięci | Regularne sprawdzanie przed wydaniem |
| Android Profiler (CPU) | Android | Użycie CPU, aktywność wątków, trace | Szukanie blokad głównego wątku |
| Android Profiler (Memory) | Android | Zrzut sterty, śledzenie alokacji | Szukanie wycieków, analiza obiektów |
| Android Profiler (Network) | Android | Ruch, prędkość, czasy żądań | Optymalizacja wywołań sieciowych |
| LeakCanary | Android | Automatyczne wykrywanie wycieków pamięci | Na wszystkich etapach rozwoju |
| StrictMode | Android | Dysk/sieć na głównym wątku, wycieki | Kompilacja debug |
| Traceview / Systrace | Android | Śledzenie metod, zdarzenia systemowe | Dogłębna analiza opóźnień |
Instruments (Xcode) — najpotężniejsze narzędzie dla iOS. Time Profiler pokazuje, które funkcje zużywają najwięcej CPU. Allocations śledzi tworzenie i zwalnianie obiektów. Leaks automatycznie znajduje retain cycles. Kroki profilowania: (1) uruchom Instruments; (2) wybierz szablon (Time Profiler dla CPU); (3) wykonaj problematyczny scenariusz; (4) przeanalizuj stos wywołań — najszersza kolumna to najgorętsza funkcja.
Android Profiler jest wbudowany w Android Studio (View → Tool Windows → Profiler). CPU Profiler pokazuje obciążenie każdego wątku. Memory Profiler — zrzut sterty i śledzenie alokacji. Network Profiler — wszystkie żądania HTTP z czasami. Energy Profiler — zużycie energii: WakeLock, Location, Network. Do szczegółowego śledzenia używa się Systrace (Android 10+) lub Perfetto — śledzenie systemowe z dokładnością do mikrosekund.
Uruchamianie aplikacji to jeden z kluczowych wskaźników wydajności. Dzieli się na trzy typy: Cold Start — aplikacja uruchamia się od zera: proces jest tworzony, Application.onCreate (Android) / AppDelegate.applicationDidFinishLaunching (iOS), ładowanie klas, inicjalizacja bibliotek. Warm Start — proces istnieje, ale Activity/ViewController jest zniszczony (np. przy obrocie ekranu lub powrocie z pamięci). Hot Start — Activity/ViewController jest w pamięci, aplikacja jest po prostu wyświetlana (przełączanie z innej aplikacji).
Cold Start to najważniejsza metryka. Na Androidzie obejmuje: (1) launch Activity — ładowanie XML, inicjalizacja View; (2) pierwsza klatka — czas do pierwszego renderowania. Google zaleca: launch Activity < 200 ms, pierwsza klatka < 500 ms, TTI < 5 sekund. Optymalizacja Cold Start: zmniejsz Application.onCreate (coroutine do leniwej inicjalizacji), użyj SplashScreen API (Android 12+), odłóż inicjalizację bibliotek (WorkManager, DI), usuń zbędne ContentProviders.
Na iOS Cold Start obejmuje: ładowanie pliku binarnego Mach-O, dyld (dynamiczny linker), inicjalizacja środowiska Objective-C, delegat aplikacji, pierwszy kontroler. Chrome Custom Tabs (Android) i Universal Links (iOS) — technologie szybkiego otwierania zewnętrznej treści w aplikacji bez pełnego Cold Start. Zaleca się testowanie Cold Start na prawdziwych urządzeniach średniej klasy.
Rozmiar aplikacji to czynnik wydajności instalacji i aktualizacji. Wpływa na konwersję: każde 10 MB zmniejsza konwersję o 1%. Google Play zaleca rozmiar APK poniżej 150 MB; App Store — poniżej 200 MB (sieci komórkowe — 100 MB). Główne metody optymalizacji: kompresja obrazów (WebP zamiast PNG oszczędza 25-35%), wektoryzacja (VectorDrawable na Androidzie, SF Symbols na iOS), usuwanie nieużywanego kodu (R8/ProGuard), usuwanie nieużywanych zasobów (lint → unused resources).
App Bundle (Android) — format publikacji, w którym Google Play generuje zoptymalizowany APK dla każdego urządzenia. App Bundle zmniejsza rozmiar pobierania o 20-40%. Dynamic Delivery — moduły pobierane na żądanie (on-demand feature modules). Na iOS odpowiednikiem są On-Demand Resources (ODR): zasoby pobierane po pierwszym uruchomieniu (poziomy gier, wideo).
Lazy Loading — technika, w której moduły i biblioteki nie są ładowane przy starcie, ale ładowane w razie potrzeby. Split APK (Android) i App Slicing (iOS) — podział aplikacji na gniazda architektoniczne: arm64-v8a, x86_64. Optymalizacja rozmiaru aplikacji — ciągły proces: analizuj skład APK (Analyze APK w Android Studio), usuwaj zduplikowane ikony, używaj SVG zamiast wielu gęstości PNG. W IT Sectr włączamy sprawdzanie rozmiaru kompilacji do CI/CD dla każdego MR.
Często zadawane pytania
ANR (Application Not Responding) — okno dialogowe, które pojawia się na Androidzie, jeśli główny wątek jest zablokowany przez ponad 5 sekund. Aby uniknąć ANR, przenieś wszystkie ciężkie operacje (sieć, baza danych, przetwarzanie plików) do wątków tła. Odpowiednik na iOS to frozen UI, gdy aplikacja przestaje reagować na dotyk.
Wyciek pamięci — gdy obiekt nie może zostać zwolniony, ponieważ nadal istnieją do niego odniesienia. Retain Cycle — sytuacja w iOS/Objective-C, gdzie dwa obiekty odnoszą się do siebie nawzajem (A → B → A) i ARC nie może zwolnić żadnego. Rozwiązanie: odniesienia weak/unowned i terminowe czyszczenie callbacków.
Dla iOS: Instruments (Time Profiler, Allocations, Leaks). Dla Android: Android Profiler (CPU, Memory, Network), LeakCanary (wycieki pamięci), StrictMode (naruszenia wątków). Zaleca się łączenie profilowania na etapie rozwoju i integracji.
Cold Start — aplikacja uruchamia się od zera: proces jest tworzony, klasy są ładowane, Application.onCreate jest wykonywany. Warm Start — proces istnieje, ale Activity/ViewController jest odtwarzany. Hot Start — Activity/ViewController jest już w pamięci, po prostu wyświetlany. Cold Start jest najwolniejszy (1-5 sekund) i krytyczny dla doświadczenia użytkownika.
Główne metody: usuń nieużywane zasoby i kod (użyj R8/ProGuard), wektoryzuj obrazy (VectorDrawable, SF Symbols), kompresuj PNG/WebP (Android), używaj App Bundle zamiast APK, usuwaj zbędne biblioteki, używaj Lazy Loading dla modułów. Optymalizacja rozmiaru może zmniejszyć APK o 40-60%.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.