Android geheugenlek

Hoe goed Lifecycle Management Android geheugenlekken kan voorkomen

OutOfMemoryException is een veel voorkomende en frustrerende bug, en een van de belangrijkste oorzaken van onverwachte app shutdown.

“Waarom gebeurt dit nu, als de app gisteren nog perfect werkte?” Het is een vraag die zowel beginnende als gevorderde Android-ontwikkelaars over de hele wereld verbijstert.

Er zijn een verscheidenheid aan potentiële oorzaken van OutOfMemory Exceptions, maar een van de meest voorkomende is het geheugenlek – de toewijzing van geheugen in de app dat nooit wordt vrijgegeven. Dit artikel legt uit hoe dit risico kan worden geminimaliseerd door middel van effectief lifecycle management – een cruciaal maar vaak over het hoofd gezien onderdeel van het ontwikkelingsproces.

TL;DR

In dit artikel bespreken we:

  • De redenen voor Android-geheugenlekkage
  • Basisstappen om het te voorkomen
  • Meer geavanceerde vormen van lifecycle management
  • Tools die de levenscyclus van uw product kunnen beheren

Waarom RAM-geheugenlekkage zich voordoet op Android?

Het probleem is simpel. Bepaalde objecten zouden slechts een vaste levensduur moeten hebben, en wanneer hun nuttige levensduur voorbij is, moeten ze worden verwijderd.

In theorie zou dit geheugen moeten worden verwijderd wanneer een proces wordt gedood met onStop of onDestroy. Misbruik van de objectverwijzing kan echter voorkomen dat de vuilnisman ongebruikte objecten dealloceert. Bijvoorbeeld: als het ongebruikte object A verwijst naar het ongebruikte object B, zul je eindigen met twee onnodige objecten die de vuilnisman nooit zal dealloceren omdat ze naar elkaar verwijzen.

Gemeenschappelijke trucs om dit te stoppen

Ontwikkelaars kunnen een aantal stappen nemen om te voorkomen dat dode activiteiten in het geheugen worden gevangen.

  1. Registreer/ontken BroadcastReceivers in onResume()/onPause() of onStart()/onStop()
  2. Gebruik NOOIT statische variabelen voor Views/Activities/Contexts
  3. Singletons die referenties naar de Context moeten bevatten, moeten applicationContext() gebruiken of deze verpakken in WeakReference
  4. Wees voorzichtig met anonieme en nietstatische binnenklassen, omdat die een impliciete verwijzing naar hun omringende klasse bevatten.
    1. Gebruik statische inner classes in plaats van anonieme als ze de ouder class gaan overleven (zoals Handlers).
    2. Als de inner of anonieme class cancellable is (zoals AsyncTask, Thread, RxSubscriptions), cancel deze dan als de activity wordt vernietigd.

    De Android Lifecycle Aware Components

    Als u de bovenstaande basisstappen hebt voltooid, is het tijd voor iets veel belangrijkers: de levenscyclus van de activiteiten van de app. Als we de levenscyclus niet goed beheren, zullen we uiteindelijk geheugen vasthouden wanneer het niet langer nodig is.

    Er zijn veel verschillende taken die hierbij betrokken zijn. Voor elke activiteit moeten we threads onderbreken, de subscripties in RxJava verwijderen, AsyncTask referenties annuleren en ervoor zorgen dat de referentie van deze activiteit (en elke andere activiteit die ermee verbonden is) correct wordt verwijderd. Al deze taken kunnen een enorme hoeveelheid tijd van de ontwikkelaar opslokken.

    De dingen worden nog ingewikkelder gemaakt door de Model View Presenter (MVP), het architectuurpatroon dat vaak wordt gebruikt om de gebruikersinterface in Android te bouwen. MVP is echter nuttig om de bedrijfslogica los te koppelen van de View.

    In het MVP-patroon zijn zowel View als Presenter abstracte implementaties van een contract voor een gedrag tussen hen. De meest gebruikelijke manier om MVP te implementeren is met behulp van een Activity/Fragment als een implementatie voor de view en een eenvoudige implementatie voor de presentator die gewend is een referentie van de View te hebben.

    Dus uiteindelijk hebben we een View met een Presenter-referentie en een Presenter met een View-referentie (Hint: We hebben hier een potentieel lek).

    Gezien deze potentiële moeilijkheden, is het essentieel dat we een goede beheerstructuur opzetten om het overtollige geheugen dat tijdens de levenscyclus wordt gecreëerd, te verwijderen. Er zijn verschillende beproefde manieren om dit te doen:

    Creing Lifecycle-Aware Components Using Android Arch Lifecycle on Android Studio

    Lifecycle-aware componenten zijn slim. Ze kunnen reageren op een verandering in de lifecycle status van een andere component, zoals activiteiten of fragmenten, door bijvoorbeeld geheugen kwijt te raken. Dit betekent dat de code lichter en veel geheugenefficiënter is.

    Arch Lifecycle is een nieuwe bibliotheek van Android die een set tools biedt om lifecycle-aware componenten te bouwen. De bibliotheek werkt op een abstracte manier, wat betekent dat lifecycle-eigenaren zich niet langer zorgen hoeven te maken over het beheren van de levenscyclus van specifieke taken en activiteiten.

    De belangrijkste tools en definities van Arch Lifecycle zijn als volgt:

    • LifeCycle: Een sorteersysteem dat definieert welke objecten een Android-levenscyclus hebben, en waarmee ze vervolgens kunnen worden bewaakt.
    • LifecycleObserver: Een gewone interface die elk object bewaakt waarvan is vastgesteld dat het een Android-levenscyclus heeft, waarbij een eenvoudige formule wordt gebruikt om elke belangrijke levenscyclusgebeurtenis af te handelen.
    • @OnLifecycleEvent: Een annotatie die kan worden gebruikt in klassen die de LifecycleObserver-interface implementeren. Hiermee kunnen we belangrijke lifecycle-events instellen die de geannoteerde methode zullen triggeren wanneer deze wordt gestart. Hier is een lijst van alle gebeurtenissen die kunnen worden ingesteld:
      • ON_ANY
      • ON_CREATE
      • ON_DESTROY
      • ON_PAUSE
      • ON_RESUME
      • ON_START
      • ON_STOP
    • LifecycleOwner is standaard geïmplementeerd voor elk Android-component waarvan de levenscyclus kan worden beheerd, en geeft de ontwikkelaar controle over elke gebeurtenis.

    Met behulp van deze tools zijn we in staat om alle schone taken naar hun eigenaars te sturen (presentatoren in ons geval), zodat we een schone, ontkoppelde code hebben die vrij is van lekken (in ieder geval in de presentator laag).

    Hier is een super-basic implementatie om te laten zien waar we het over hebben:

    interface View: MVPView, LifecycleOwnerclass RandomPresenter : Presenter<View>, LifecycleObserver { private lateinit var view: View override fun attachView(view: View) { this.view = view view.lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.On_DESTROY) fun onClear() {//TODO: clean }

    Gebruik Android Arch View Models als Presenters en LiveData

    Een andere manier om geheugenlekkage door lifecycle mismanagement te voorkomen is door gebruik te maken van de nieuwe ViewModels uit de Arch Components Library.

    Een ViewModel is een abstracte klasse die een enkele functie implementeert, bekend als onClear, die automatisch wordt aangeroepen wanneer een bepaald object moet worden verwijderd. Het ViewModel wordt gegenereerd door het framework en het is gekoppeld aan de levenscyclus van de maker (als een toegevoegde bonus, het is super-gemakkelijk te injecteren met Dagger.)

    Naast het gebruik van ViewModel, LiveData biedt ook een vitaal kanaal van communicatie. Het volgt een reactief, eenvoudig te observeren patroon, wat betekent dat individuele objecten het kunnen observeren (waardoor minder gekoppelde code ontstaat).

    Het grote punt hier is dat LiveData kan worden geobserveerd door een lifecycle-eigenaar, dus de gegevensoverdracht wordt altijd beheerd door de lifecycle – en we kunnen ervoor zorgen dat elke referentie behouden blijft tijdens het gebruik ervan.

    Het gebruik van LeakCanary en Bugfender

    Naast de eerder genoemde stappen, willen we nog twee belangrijke stukken kit aanbevelen: LeakCanary, een populaire tool voor het monitoren van lekken, en onze eigen Bugfender.

    LeakCanary is een geheugenopsporingsbibliotheek voor Android en Java. Het is open-source, dus er is een enorme gemeenschap achter, en het vertelt je niet alleen over een lek – het informeert je over de waarschijnlijke oorzaak.

    Bugfender, onze remote logging tool, stelt je in staat om individuele LeakTraces te debuggen en een klasse genaamd DisplayLeakService uit te breiden, die ons laat weten wanneer een lek wordt opgewekt. Dan kunnen we het eenvoudig met Bugfender loggen.

    public class LeakUploadService extends DisplayLeakService { override fun afterDefaultHandling(heapDump: HeapDump, result: AnalysisResult, leakInfo: String) { if (result.leakFound) { Bugfender.d("LeakCanary", result.toString()) } }}

    Daarnaast krijgen gebruikers alle andere voordelen van Bugfender, inclusief 24/7 log-registratie (zelfs wanneer het apparaat offline is), ingebouwde crash-rapportage en een eenvoudig te gebruiken webconsole, die drill-down in individuele apparaten mogelijk maakt voor betere klantenservice.

    Voor meer over Bugfender, klik hier.

Leave a Reply