Instrukcje

Priorytetowe traktowanie wydajności pamięci: najważniejsze kroki w przypadku Androida 17

10 minut czytania

Wydajność aplikacji jest często utożsamiana z płynnym interfejsem i krótkim czasem uruchamiania, ale pamięć jest cichym fundamentem, na którym opierają się te widoczne wskaźniki. Nie jest tajemnicą, że pamięć urządzenia jest obecnie ważniejsza niż kiedykolwiek wcześniej. W Androidzie 17 wprowadziliśmy optymalizacje pamięci, a także udostępniamy narzędzia i interfejsy API, które pomogą Ci spełnić bardziej rygorystyczne wymagania dotyczące pamięci, które wejdą w życie jeszcze w tym roku.

Aby zapewnić stabilność urządzenia, od Androida 17 system będzie egzekwować limity pamięci aplikacji na podstawie całkowitej pamięci RAM urządzenia. Jeśli aplikacja przekroczy te limity, Android zakończy proces bez powiązanego śladu stosu.

Oprócz tych wymuszonych zakończeń nieoptymalne wykorzystanie pamięci nieuchronnie pogarsza wrażenia użytkowników. Gdy aplikacja zbliża się do limitów pamięci sterty, często uruchamia odśmiecanie pamięci, co powoduje zauważalne zacinanie się interfejsu. Gdy w urządzeniu zabraknie dostępnej pamięci, system próbuje odzyskać strony, co powoduje obciążenie procesora, opóźnienia interfejsu i szybkie zużycie baterii. Jeśli niedobór pamięci jest zbyt duży, może to powodować zdarzenia LMK (Low Memory Killer), które nagle kończą procesy w tle i sprawiają, że aplikacje powoli uruchamiają się „na zimno” i tracą stan użytkownika.

Aby tworzyć aplikacje o wysokiej wydajności i unikać ich wymuszonego zamykania, zalecamy stosowanie tych strategii optymalizacji pamięci:

  1. Maksymalizowanie optymalizacji kodu bajtowego za pomocą R8
  2. Optymalizacja wczytywania obrazów
  3. Wykrywanie i usuwanie wycieków pamięci za pomocą Androida Studio
  4. Przycinanie pamięci, gdy aplikacja przestaje być widoczna
  5. Zaawansowana obserwacja pamięci za pomocą ProfilingManager

Skrócona wersja tego posta na blogu jest też dostępna w formie filmu. Zapraszamy do oglądania!

Limity pamięci aplikacji w Androidzie 17

W Androidzie 17 wprowadzamy limity pamięci aplikacji, aby zapobiec sytuacji, w której „jeden nieuczciwy podmiot” zniszczy wielozadaniowość i stabilność całego urządzenia użytkownika.

Oto zestawienie powodów tej zmiany architektury:

  • Zapobieganie kaskadowemu zamykaniu: gdy aplikacja staje się zbyt duża lub wycieka z niej pamięć, gdy jest w stanie uprzywilejowanym (np. działa jako usługa na pierwszym planie), jest początkowo chroniona przed działaniem funkcji Low Memory Killer (LMK) systemu. Gdy ta jedna aplikacja rośnie bez kontroli i gromadzi pamięć RAM, LMK musi to skompensować, zamykając dziesiątki mniejszych, dobrze działających aplikacji w pamięci podręcznej i zadań w tle, aby odzyskać miejsce dla aplikacji zużywającej dużo pamięci.
  • Zachowanie wielozadaniowości i stanu użytkownika: gdy system musi usunąć aplikacje z pamięci podręcznej, aby pomieścić jeden proces wycieku, wielozadaniowość jest znacznie ograniczona. Użytkownicy wracający do wcześniej buforowanych aplikacji napotykają powolne uruchamianie „na zimno” zamiast niemal natychmiastowego wznawiania „na ciepło”. Ta nieefektywność powoduje większe obciążenie procesora i przyspiesza wyczerpywanie się baterii. Może też zniszczyć kontekst użytkownika w ostatnio używanych aplikacjach, np. pozycje przewijania, stosy nawigacji i postępy w grze.

Aby sprawdzić, czy sesja aplikacji została objęta tymi ograniczeniami w terenie, możesz wywołać funkcję getDescription() w ramach ApplicationExitInfo. Jeśli system zastosował limit, powód zakończenia jest zgłaszany jako REASON_OTHER, a ciąg znaków opisu będzie zawierać „MemoryLimiter:AnonSwap”. Możesz też korzystać z profilowania opartego na wyzwalaczach za pomocą TRIGGER_TYPE_ANOMALY, aby automatycznie zapisywać zrzuty sterty po osiągnięciu limitu pamięci. Ponadto zespół Androida aktywnie pracuje nad udostępnieniem deweloperom w Konsoli Google Play większej liczby wskaźników pamięci w terenie.

Rozszerzyliśmy też dokumentację dotyczącą limitów pamięci, dodając do niej polecenia debugowania lokalnego, które umożliwiają symulowanie ograniczeń pamięci w środowisku lokalnym i sprawdzanie działania aplikacji w przypadku egzekwowania dowolnego limitu pamięci. 

Maksymalizowanie optymalizacji kodu bajtowego za pomocą R8

Bardzo skutecznym sposobem na zmniejszenie wykorzystania pamięci przez aplikację jest włączenie optymalizatora R8. Zmniejszając nazwy klas, metod i pól oraz usuwając nieużywany kod i zasoby, R8 znacznie zmniejsza wykorzystanie pamięci przez aplikację, minimalizując ilość kodu rezydentnego wymaganego podczas wykonywania. 

R8 minimalizuje kod rezydentny, zmniejszając wykorzystanie pamięci i ryzyko zakończenia działania przez LMK. Dzięki temu uruchomienia częściowo z pamięci będą występować częściej niż powolne uruchomienia „na zimno”. Dodatkowo uproszczony kod bajtowy zmniejsza obciążenie procesora głównego wątku, co bezpośrednio obniża częstotliwość błędów ANR i zapewnia płynniejsze działanie aplikacji. Na przykład bank cyfrowy Monzo włączył pełną optymalizację R8 i odnotował spadek częstotliwości błędów ANR o 35%, wzrost liczby uruchomień „na zimno” o 30% i zmniejszenie ogólnego rozmiaru aplikacji o 9%.

pic1-IO26_113_TSV-monzo-casestudy.jpg
Bank cyfrowy Monzo włączył pełną optymalizację R8 i zwiększył wskaźniki wydajności nawet o 35%.

Aby prawidłowo skonfigurować R8 w pliku build.gradle:

  • Ustaw isShrinkResources = trueisMinifyEnabled = true.
  • Używaj proguard-android-optimize.txt zamiast starszego proguard-android.txt, które w rzeczywistości uniemożliwia optymalizacje i nie jest już obsługiwane we wtyczce Androida do obsługi Gradle w wersji 9.
  • Usuń urządzenie android.enableR8.fullMode = false z konta gradle.properties.

Jeśli w bazie kodu używasz odbicia, dodaj reguły zachowywania, aby zapobiec optymalizacji tych części kodu przez R8. Aby uzyskać maksymalną optymalizację, ogranicz zakres reguł przechowywania. 

Aby uzyskać maksymalną optymalizację, w pliku reguł zachowywania stosuj te sprawdzone metody.

  • Usuń opcje globalne, takie jak -dontoptimize, -dontshrink-dontobfuscate, które uniemożliwiają R8 optymalizację całej bazy kodu.
  • Usuń reguły zachowywania, które uniemożliwiają optymalizację komponentów Androida, takich jak aktywność, usługi, widoki czy odbiorniki transmisji.
  • Doprecyzuj ogólne reguły zachowywania dla całego pakietu, aby kierować je tylko na określone klasy lub metody. 

Więcej sprawdzonych metod znajdziesz w dokumentacji reguł przechowywania.

Sprawdzone metody dotyczące biblioteki Developer R8

Jeśli jesteś deweloperem biblioteki, umieść w pliku consumer-rules file tylko reguły, których potrzebują Twoi klienci, a wewnętrzne reguły ochrony biblioteki przechowuj w pliku proguard-rules.pro. Więcej informacji o optymalizacji bibliotek znajdziesz w artykule Optymalizacja dla autorów bibliotek.

Analizator konfiguracji R8

Aby sprawdzić optymalizację R8, użyj analizatora konfiguracji. Analizator konfiguracji pokazuje bieżący stan optymalizacji z  wynikami zaciemniania, optymalizacji i zmniejszania rozmiaru. Za pomocą analizatora konfiguracji możesz też sprawdzić, ile klas, metod lub pól jest wykluczonych z optymalizacji przez każdą regułę zachowywania. Ulepsz te ogólne reguły przechowywania na poziomie pakietu, aby uzyskać maksymalną optymalizację. 

Za pomocą analizatora konfiguracji możesz też wykrywać reguły przechowywania, które obejmują inne reguły przechowywania, zbędne reguły przechowywania i nieużywane reguły przechowywania.

pic2-r8-config-analyzer.png
Analizator konfiguracji pokazuje bieżący stan optymalizacji z wynikami zaciemniania, optymalizacji i zmniejszania rozmiaru.

Umiejętności agenta R8

Możesz też użyć umiejętności R8 Agent w połączeniu z agentem Android Studio lub innymi narzędziami AI, aby rozwiązywać problemy z nieprawidłową konfiguracją i dopracowywać reguły, co pozwoli Ci zwiększyć wydajność aplikacji. (Statystyki dotyczące umiejętności opartych na AI będą wymagać weryfikacji technicznej)

Optymalizacja wczytywania obrazów

Bitmapy są zwykle największymi obiektami w pamięci aplikacji. Stanowią one ostatni etap procesu wczytywania obrazu, w którym skompresowane pliki, takie jak JPEG lub PNG, są dekodowane do postaci surowych danych pikseli w celu wyświetlenia. Oznacza to, że mały skompresowany obraz o rozmiarze 100 KB może zajmować kilka megabajtów pamięci RAM, ponieważ zużycie pamięci zależy od wymiarów obrazu w pikselach i głębi kolorów. Operacje na mapach bitowych często znajdują się na ścieżce krytycznej do rysowania klatek, więc nieoptymalne obrazy powodują znaczne zwiększenie zużycia pamięci i zacinanie się interfejsu.

W przypadku projektów opartych na Kotlinie Google zaleca korzystanie z bibliotek wczytywania obrazów Coil, zwłaszcza podczas tworzenia aplikacji z użyciem Jetpack Compose, a w przypadku aplikacji opartych na Javie – z biblioteki Glide.

Zastosuj te 5 sprawdzonych metod

  1. Próbkowanie w dół obrazów: jeśli wczytujesz mapy bitowe ręcznie, unikaj wczytywania dużego obrazu do małej miniatury. Użyj inSampleSize, aby wczytać mniejszą wersję. Glide i Coil domyślnie zmniejszają próbkowanie obrazów. Możesz skonfigurować tę strategię zmniejszania próbkowania za pomocą odpowiednio DownsampleStrategyImageLoader.
  2. Przycinanie:  unikaj umieszczania dopełnienia bezpośrednio w pliku obrazu w celu uzyskania efektu letterboxingu (np. tworzenia przezroczystej ramki, aby zwiększyć wymiary obrazu). Zamiast wbudowywać te obramowania, użyj elementu InsetDrawable lub zastosuj dopełnienie bezpośrednio w obiekcie View lub funkcji kompozycyjnej zawierającej mapę bitową.
  3. Konfiguracja: zrównoważ pamięć i jakość, wybierając odpowiedni format pikseli. Używaj formatu RGB_565, gdy przezroczystość nie jest potrzebna. Zajmuje on połowę pamięci domyślnego formatu ARGB_8888. W Glide możesz to skonfigurować za pomocą funkcji DecodeFormat, a w Coil – za pomocą właściwości bitmapConfig.
  4. Nadaj priorytet obiektom rysowalnym wektorowo: w przypadku podstawowych komponentów geometrycznych używaj klasy ShapeDrawable jako lekkiej alternatywy dla dekodowania zrasteryzowanych bitmap. Definiując te komponenty raz za pomocą kodu XML, zapewniasz ich bezproblemowe skalowanie we wszystkich gęstościach wyświetlania, skutecznie eliminując nadmierne zużycie pamięci przez zasoby.
  5. Ponowne użycie: jeśli aplikacja zarządza mapami bitowymi ręcznie, aby zminimalizować fragmentację pamięci, gdy mapa bitowa nie jest już potrzebna, aplikacja powinna wywołać bitmap.recycle() i natychmiast odrzucić odwołanie Bitmap. Jeśli używasz biblioteki do wczytywania obrazów, takiej jak Glide lub Coil, zwróć bitmapę do puli zarządzanej przez bibliotekę. Dzięki zapewnieniu istniejącego bufora na potrzeby pamięci w przyszłości pula skutecznie unika obciążenia związanego z nowymi przydziałami.

Więcej informacji znajdziesz w dokumentacji na temat optymalizacji wydajności obrazów.

Narzędzia Android Studio

Możesz też wyeliminować nadmiarowe mapy bitowe za pomocą Androida Studio Narwhal 4. Oto 5 prostych kroków, które pomogą Ci je znaleźć:

  1. Otwórz kartę Profiler w Android Studio.
  2. Kliknij Heap Dump (lub „Analyze Memory Usage”) i naciśnij przycisk nagrywania, aby zrobić migawkę bieżącego stanu pamięci aplikacji.
  3. Przejrzyj wyniki analizy i poszukaj żółtego trójkąta ostrzegawczego ⚠️, którego Android Studio używa do oznaczania zduplikowanych bitmap przechowywanych wielokrotnie. Możesz też przejść do nagłówka profilera, kliknąć „Filtruj według:” i wybrać ustawienie „Zduplikowane mapy bitowe”.
  4. Kliknij dowolny oznaczony wpis, aby otworzyć panel Podgląd mapy bitowej, który pozwoli Ci zobaczyć, który obraz jest powielany.
  5. Użyj tego wizualnego potwierdzenia, aby odnaleźć w kodzie zbędną logikę wczytywania i wdrożyć lepszą strategię buforowania.
pic3-IO26_113_TSV -dup-bitmaps-cropped.jpg
Podczas korzystania z profilera Androida Studio szukaj w zrzutach sterty żółtego trójkąta ostrzegawczego ⚠️.

Wykrywanie i usuwanie wycieków pamięci za pomocą Androida Studio

Wycieki pamięci na Androidzie występują, gdy kod zachowuje odniesienie do obiektu długo po zakończeniu jego cyklu życia. Uniemożliwia to odzyskanie pamięci przez moduł odśmiecania (GC), co ostatecznie prowadzi do spowolnienia działania lub błędu braku pamięci (OutOfMemoryError).

Android Studio Panda 3 zawiera dedykowane zadanie profilowania LeakCanary, które umożliwia programistom analizowanie wycieków pamięci w czasie rzeczywistym i mapowanie śladów bezpośrednio w IDE.

Zadanie profilera LeakCanary w Android Studio aktywnie przenosi analizę wycieków pamięci z urządzenia na komputer używany do programowania, co znacznie zwiększa wydajność w fazie analizy wycieków w porównaniu z analizą wycieków na urządzeniu.

pic4-android-studio-leaks.png
 Analiza wycieków pamięci w LeakCanary z kontekstem funkcji „Przejdź do deklaracji” na potrzeby debugowania

Dodatkowo analiza wycieków jest teraz kontekstowa w środowisku IDE i w pełni zintegrowana z kodem źródłowym, co zapewnia funkcje takie jak przejście do deklaracji i inne przydatne połączenia kodu, które znacznie zmniejszają trudności i czas potrzebny na zbadanie i naprawienie wycieków pamięci.  

Przykłady typowych wycieków pamięci

Wycieki pamięci występują, gdy obiekt pozostaje w pamięci dłużej niż powinien. Zwykle dzieje się tak z powodu:

  • Zachowywanie odniesień do fragmentów, aktywności lub widoków, które nie są już używane.
  • Nieprawidłowe zarządzanie odwołaniami do kontekstu.
  • Nieprawidłowe anulowanie rejestracji obserwatorów, detektorów i odbiorników.
  • Tworzenie statycznych odwołań do obiektów powiązanych z komponentami o krótszych cyklach życia.

Oto kilka przykładów:

ScenariuszPrzykład oparty na ComposePrzykład oparty na wyświetleniach
Wyciek kontekstu

Przykład:
Przekazywanie LocalContext.current do ViewModel

Rozwiązanie:
logika zależna od kontekstu powinna być utrzymywana w warstwie interfejsu. W przypadku warstw innych niż interfejs użytkownika zmień kod, aby używać wstrzykiwania zależności lub obserwować stan interfejsu użytkownika za pomocą Kotlin Flow.

Przykład: 
przechowywanie Activity w obiekcie towarzyszącym lub zmiennej statycznej.

Rozwiązanie:
nie przechowuj statycznych odwołań do komponentów interfejsu. Przeprowadź refaktoryzację, aby używać wstrzykiwania zależności lub obserwować stan interfejsu za pomocą przepływu Kotlin.

Detektory wycieków

Przykład:
użycie DisposableEffect do uruchomienia odbiornika, ale pozostawienie onDispose pustego.

Poprawka:
wykonaj wyrejestrowanie i logikę czyszczenia w bloku onDispose.

Przykład:
zarejestrowanie się w celu otrzymywania aktualizacji SensorManager i zapomnienie o wyrejestrowaniu.

Rozwiązanie:
ręcznie wywołaj funkcję unregisterListener()onStop() lub onDestroy() cyklu życia.

Wyświetlenia wyciekające

Przykład:
przechowywanie odwołania do starszego elementu ViewAndroidView bez strategii zwalniania.


Rozwiązanie:
użyj bloku release w komponencie AndroidView, aby usunąć starszy komponent View.

Przykład:
zachowanie odwołania do obiektu powiązania widoku po zniszczeniu Fragment.

Rozwiązanie:
ustaw zmienną wiązania na null w metodzie cyklu życia onDestroyView().

Przycinanie pamięci, gdy aplikacja przestaje być widoczna

Android może odzyskać pamięć z aplikacji lub całkowicie ją zatrzymać, jeśli jest to konieczne, aby zwolnić pamięć na potrzeby krytycznych zadań, jak wyjaśniono w omówieniu zarządzania pamięcią. Android zwykle odzyskuje pamięć z aplikacji, gdy nie jest ona widoczna dla użytkownika, np. przez odrzucenie niektórych stron z kodem i danymi aplikacji w pamięci lub skompresowanie przydziałów sterty. Gdy użytkownik wznowi działanie aplikacji, a aplikacja spróbuje uzyskać dostęp do pamięci, która została odzyskana, system operacyjny przywróci tę pamięć na żądanie. Takie przełączanie może być powolne i powodować nieoczekiwane zacinanie się lub przerywanie działania aplikacji.

Jeśli pozwolisz systemowi operacyjnemu decydować, jaką pamięć odzyskać z aplikacji, może się okazać, że odzyskał on pamięć, która będzie Ci potrzebna wkrótce po wznowieniu aplikacji. Zamiast tego aplikacja może dobrowolnie odrzucać przydziały pamięci, które może później odtworzyć na żądanie i niskim kosztem. Aby to zrobić, możesz wdrożyć interfejs ComponentCallbacks2. Możesz zaimplementować onTrimMemory w klasie Activity, Fragment, Service lub nawet w klasie niestandardowej Application. Używanie go w klasie Application jest bardzo skuteczne w zarządzaniu globalną pamięcią podręczną.

Podana metoda wywołania zwrotnego onTrimMemory() powiadamia aplikację o zdarzeniach związanych z cyklem życia lub pamięcią, które stanowią dobrą okazję do dobrowolnego zmniejszenia wykorzystania pamięci.

W przypadku zarządzania cyklem życia pamięci implementacja powinna koncentrować się wyłącznie na TRIM_MEMORY_UI_HIDDENTRIM_MEMORY_BACKGROUND. Od Androida 14 system nie dostarcza już powiadomień dotyczących innych starszych stałych, które zostały formalnie wycofane w Androidzie 15.

TRIM_MEMORY_UI_HIDDEN: ten sygnał wskazuje, że interfejs aplikacji został usunięty z widoku użytkownika. Daje to możliwość zwolnienia znacznych ilości pamięci powiązanych ściśle z interfejsem, takich jak mapy bitowe, bufory odtwarzania wideo czy złożone zasoby animacji.

TRIM_MEMORY_BACKGROUND: na tym poziomie proces działa w tle i może zostać zakończony, aby zaspokoić globalne potrzeby systemu w zakresie pamięci. Aby wydłużyć czas, przez jaki proces pozostaje w stanie buforowanym, i zmniejszyć liczbę uruchomień aplikacji „na zimno”, należy agresywnie zwalniać wszystkie zasoby, które można łatwo odtworzyć, gdy użytkownik wznowi sesję.

import android.content.ComponentCallbacks2
// Other import statements.

class MainActivity : AppCompatActivity(), ComponentCallbacks2 {

    /**
     * Release memory when the UI becomes hidden or when system resources become low.
     * @param level the memory-related event that is raised.
     */
    override fun onTrimMemory(level: Int) {

        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release memory related to UI elements, such as bitmap caches.
        }

        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release memory related to background processing, such as by
            // closing a database connection.
        }
    }
}

Uwaga: onTrimMemory integracja może zależeć od obsługi pakietu SDK. Niektóre gry korzystają na przykład z silnika gry, aby włączyć tę funkcję. Zapoznaj się z dokumentami dotyczącymi optymalizacji pamięci gry.

Zaawansowana obserwacja pamięci za pomocą ProfilingManager

Aby wykrywać i diagnozować problemy z pamięcią w środowisku produkcyjnym, których nie można odtworzyć lokalnie, użyj interfejsu ProfilingManager API. Ten zaawansowany interfejs API do obserwacji, wprowadzony w Androidzie 15, umożliwia programowe zbieranie profili Perfetto prawdziwych użytkowników. 

Dla zespołów, które nie mają dedykowanej infrastruktury do zarządzania artefaktami wydajności i ich hostowania, Crashlytics opracowuje specjalne rozwiązanie, które usprawni ten proces. Zapraszają deweloperów do przekazywania opinii.

Android 17 wprowadza nowe reguły oparte na zdarzeniach, w tym przede wszystkim TRIGGER_TYPE_OOMTRIGGER_TYPE_ANOMALY:

  • Wywołanie błędu braku pamięci automatycznie zbiera zrzut sterty Javy w momencie wystąpienia błędu OutOfMemoryError, co zapewnia dokładne stany alokacji. Zebrany profil OOM jest udostępniany przy następnym uruchomieniu aplikacji i zarejestrowaniu wywołania zwrotnego registerForAllProfilingResults.
  • Wywoływanie anomalii wykrywa poważne problemy z wydajnością, takie jak nadmierne spamowanie lub przekroczenie progów pamięci. Anomalia pamięci powoduje zrzut sterty tuż przed zakończeniem działania aplikacji przez system.
    val profilingManager = 
applicationContext.getSystemService(ProfilingManager::class.java)
    val triggers = ArrayList<ProfilingTrigger>()  


    triggers.add(ProfilingTrigger.Builder(
                 ProfilingTrigger.TRIGGER_TYPE_ANOMALY))
    val mainExecutor: Executor = Executors.newSingleThreadExecutor()
    val resultCallback = Consumer<ProfilingResult> { profilingResult ->
        if (profilingResult.errorCode != ProfilingResult.ERROR_NONE) {
            // upload profile result to server for further analysis          
            setupProfileUploadWorker(profilingResult.resultFilePath)
        } 

    profilingManager.registerForAllProfilingResults(mainExecutor, resultCallback)
    profilingManager.addProfilingTriggers(triggers)

Po zebraniu zrzutu pamięci możesz pobrać profil z serwera lub lokalnie za pomocą polecenia adb pull i przeciągnąć plik do interfejsu Perfetto. Aby usprawnić proces debugowania pamięci, użyj Eksploratora zrzutów sterty. Jest to nowy widok domyślny zrzutów sterty w interfejsie Perfetto. To narzędzie udostępnia intuicyjny interfejs do sprawdzania zrzutów sterty Javy, który umożliwia wizualizację hierarchii alokacji obiektów, obliczanie rozmiarów pamięci zachowanej i identyfikowanie najkrótszej ścieżki od głównego elementu odśmiecania pamięci. Korzystając z Eksploratora zrzutów sterty, możesz szybko lokalizować wycieki pamięci, nadmiernie duże obiekty zachowane, takie jak nadmierne przydziały map bitowych, i analizować przydziały obiektów sterty w jednym miejscu.

pic5-perfettoheapdump-analyzer.png
Użyj wbudowanego wykresu płomieniowego w Eksploratorze zrzutów sterty, aby wizualnie sprawdzić obiekty z największą liczbą przydzielonych obszarów sterty i poruszać się między nimi.

Podsumowanie

Optymalizacja kodu bajtowego za pomocą R8, stosowanie sprawdzonych metod wczytywania obrazów i usuwanie wycieków pamięci to kluczowe kroki, które pozwalają zapewnić wysoką jakość obsługi użytkowników przy jednoczesnym efektywnym zarządzaniu zasobami w trudnych warunkach. Wdrożenie tych proaktywnych środków pomaga utrzymać stabilność i wydajność aplikacji, zapobiegać nieoczekiwanemu zamykaniu i chronić kontekst użytkownika. Aby poszerzyć swoją wiedzę o wydajności, zapoznaj się z naszymi zaktualizowanymi wskazówkami dotyczącymi pamięci.

Autorzy:
Czytaj dalej