Problemi di memoria esaurita dell'app Android: ho provato di tutto e ancora in perdita


87

Ho passato 4 giorni interi a provare tutto il possibile per capire la perdita di memoria in un'app che sto sviluppando, ma le cose hanno smesso di avere senso molto tempo fa.

L'app che sto sviluppando è di natura sociale, quindi pensa alle attività del profilo (P) ed elenca le attività con i dati, ad esempio i badge (B). Puoi passare da un profilo a un elenco di badge ad altri profili, ad altri elenchi, ecc.

Quindi immagina un flusso come questo P1 -> B1 -> P2 -> B2 -> P3 -> B3, ecc. Per coerenza, sto caricando profili e badge dello stesso utente, quindi ogni pagina P è la stessa e così è ogni pagina B.

L'essenza generale del problema è: dopo aver navigato per un po ', a seconda delle dimensioni di ogni pagina, ottengo un'eccezione di memoria esaurita in luoghi casuali - Bitmap, Stringhe, ecc. - Non sembra essere coerente.

Dopo aver fatto tutto quanto immaginabile per capire perché sto esaurendo la memoria, non ho trovato nulla. Quello che non capisco è perché Android non sta uccidendo P1, B1, ecc.Se esaurisce la memoria al caricamento e invece si blocca. Mi aspetterei che queste attività precedenti morissero e fossero resuscitate se mai tornassi a loro tramite onCreate () e onRestoreInstanceState ().

Per non parlare di questo - anche se faccio P1 -> B1 -> Back -> B1 -> Back -> B1, ottengo comunque un crash. Questo indica una sorta di perdita di memoria, ma anche dopo aver scaricato hprof e usando MAT e JProfiler, non riesco a individuarlo.

Ho disabilitato il caricamento delle immagini dal web (e aumentato i dati di prova caricati per compensare e rendere equo il test) e mi sono assicurato che la cache delle immagini utilizzi SoftReferences. Android in realtà cerca di liberare i pochi SoftReferences che ha, ma subito prima che la memoria si blocchi.

Le pagine dei badge ottengono i dati dal Web, li caricano in un array di EntityData da un BaseAdapter e li alimentano a un ListView (in realtà sto usando l' eccellente MergeAdapter di CommonsWare , ma in questa attività Badge c'è comunque solo 1 adattatore, ma io volevo menzionare questo fatto in entrambi i casi).

Ho esaminato il codice e non sono riuscito a trovare nulla che potesse trapelare. Ho cancellato e annullato tutto ciò che potevo trovare e persino System.gc () a sinistra ea destra, ma l'app si blocca ancora.

Ancora non capisco perché le attività inattive che sono in pila non vengono raccolte e mi piacerebbe davvero capirlo.

A questo punto, sto cercando suggerimenti, consigli, soluzioni ... tutto ciò che potrebbe aiutare.

Grazie.


Quindi, se ho aperto 15 attività negli ultimi 20 secondi (l'utente sta eseguendo molto velocemente), potrebbe essere questo il problema? Quale riga di codice devo aggiungere per cancellare l'attività dopo che è stata visualizzata? Ottengo un outOfMemoryerrore. Grazie!
Ruchir Baronia

Risposte:


109

Ancora non capisco perché le attività inattive che sono in pila non vengono raccolte e mi piacerebbe davvero capirlo.

Non è così che funzionano le cose. L'unica gestione della memoria che influisce sul ciclo di vita delle attività è la memoria globale in tutti i processi, poiché Android decide che la memoria si sta esaurendo e quindi deve interrompere i processi in background per recuperarne un po '.

Se la tua applicazione è in primo piano e inizia sempre più attività, non passerà mai in background, quindi raggiungerà sempre il limite di memoria del processo locale prima che il sistema si avvicini all'arresto del processo. (E quando interrompe il suo processo, ucciderà il processo che ospita tutte le attività, incluso ciò che è attualmente in primo piano.)

Quindi mi sembra che il tuo problema di base sia: stai lasciando che troppe attività vengano svolte contemporaneamente e / o ciascuna di queste attività si trattiene di troppe risorse.

Hai solo bisogno di riprogettare la tua navigazione per non fare affidamento sull'accumulo di un numero arbitrario di attività potenzialmente pesanti. A meno che tu non faccia una grande quantità di cose in onStop () (come chiamare setContentView () per cancellare la gerarchia di visualizzazione dell'attività e cancellare le variabili da qualsiasi altra cosa a cui potrebbe trattenersi), stai per esaurire la memoria.

Potresti prendere in considerazione l'utilizzo delle nuove API Fragment per sostituire questo stack arbitrario di attività con una singola attività che gestisce più strettamente la sua memoria. Ad esempio, se si utilizzano le funzionalità di back stack dei frammenti, quando un frammento va in back stack e non è più visibile, viene chiamato il suo metodo onDestroyView () per rimuovere completamente la sua gerarchia di visualizzazione, riducendo notevolmente il suo footprint.

Ora, per quanto riguarda il flusso in cui premi indietro, vai a un'attività, premi indietro, vai a un'altra attività, ecc.E non hai mai uno stack profondo, allora sì, hai solo una perdita. Questo post del blog descrive come eseguire il debug delle perdite: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html


47
A difesa dell'OP, questo non è ciò che suggerisce la documentazione. Citando developer.android.com/guide/topics/fundamentals/… "(Quando un'attività viene interrotta), non è più visibile all'utente e può essere interrotta dal sistema quando la memoria è necessaria altrove." "Se un'attività viene sospesa o interrotta, il sistema può eliminarla dalla memoria ... chiedendole di terminare (chiamando il suo metodo finish ())" "(viene chiamato onDestroy ()) perché il sistema sta temporaneamente distruggendo questa istanza di l'attività per risparmiare spazio ".
CommonsWare

42
Nella stessa pagina, "Tuttavia, quando il sistema distrugge un'attività per recuperare la memoria". Quello che stai indicando è che Android non distrugge mai le attività per recuperare la memoria, ma interromperà solo il processo per farlo. In tal caso, questa pagina necessita di una seria riscrittura, poiché suggerisce ripetutamente che Android distruggerà un'attività per recuperare la memoria. Notare inoltre che molti dei passaggi citati esistono anche nei ActivityJavaDocs.
CommonsWare

6
Pensavo di averlo inserito qui, ma ho pubblicato una richiesta per aggiornare la documentazione per questo: code.google.com/p/android/issues/detail?id=21552
Justin Breitfeller

15
È il 2013 e la documentazione è arrivata solo per presentare il falso punto in modo più chiaro: developer.android.com/training/basics/activity-lifecycle/… , "Una volta che la tua attività viene interrotta, il sistema potrebbe distruggere l'istanza se deve essere ripristinata memoria di sistema. In casi ESTREMI, il sistema potrebbe semplicemente terminare il processo dell'app "
kaay

2
Bene, merda. Ho sicuramente sempre pensato (a causa dei documenti) che il sistema gestisse le attività interrotte nel tuo processo e le avrebbe serializzate e deserializzate (distrutte e create) secondo necessità.
dcow

21

Alcuni suggerimenti:

  1. Assicurati di non essere il contesto dell'attività di perdita.

  2. Assicurati di non mantenere i riferimenti sulle bitmap. Pulisci tutti i tuoi ImageView in Activity # onStop, qualcosa del genere:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. Ricicla le bitmap se non ti servono più.

  4. Se usi la cache di memoria, come memory-lru, assicurati che non utilizzi molta memoria.

  5. Non solo le immagini occupano molta memoria, assicurati di non tenere in memoria troppi altri dati. Questo può accadere facilmente se hai elenchi infiniti nella tua app. Prova a memorizzare nella cache i dati in DataBase.

  6. Su Android 4.2, c'è un bug (stackoverflow # 13754876) con l'accelerazione hardware, quindi se usi hardwareAccelerated=truenel tuo manifest perderà memoria.GLES20DisplayList- continua a mantenere i riferimenti, anche se hai eseguito il passaggio (2) e nessun altro fa riferimento a questa bitmap. Qui hai bisogno di:

    a) disabilitare l'accelerazione hardware per api 16/17;
    oppure
    b) scollegare la vista che contiene bitmap

  7. Per Android 3+ puoi provare a usare android:largeHeap="true"nel tuo AndroidManifest. Ma non risolverà i tuoi problemi di memoria, basta rimandarli.

  8. Se hai bisogno, tipo, di navigazione infinita, allora Fragments dovrebbe essere la tua scelta. Quindi avrai 1 attività, che passerà da un frammento all'altro. In questo modo risolverai anche alcuni problemi di memoria, come il numero 4.

  9. Usa Memory Analyzer per scoprire la causa della tua perdita di memoria.
    Ecco un ottimo video da Google I / O 2011: Gestione della memoria per app Android
    Se hai a che fare con bitmap, dovresti leggere questo: Visualizzazione efficiente delle bitmap


Quindi, se ho aperto 15 attività negli ultimi 20 secondi (l'utente sta eseguendo molto velocemente), potrebbe essere questo il problema? Quale riga di codice devo aggiungere per cancellare l'attività dopo che è stata visualizzata? Ottengo un outOfMemoryerrore. Grazie!
Ruchir Baronia

4

Le bitmap sono spesso il colpevole degli errori di memoria su Android, quindi sarebbe una buona area da ricontrollare.


Quindi, se ho aperto 15 attività negli ultimi 20 secondi (l'utente sta eseguendo molto velocemente), potrebbe essere questo il problema? Quale riga di codice devo aggiungere per cancellare l'attività dopo che è stata visualizzata? Ottengo un outOfMemoryerrore. Grazie!
Ruchir Baronia

2

Hai alcuni riferimenti a ciascuna attività? Per quanto ne so, questo è un motivo che impedisce ad Android di eliminare le attività dallo stack.

Siamo in grado di riprodurre questo errore anche su altri dispositivi? Ho riscontrato uno strano comportamento di alcuni dispositivi Android a seconda del produttore di ROM e / o hardware.


Sono stato in grado di riprodurlo su un Droid che esegue CM7 con l'heap massimo impostato su 16 MB, che è lo stesso valore che sto testando sull'emulatore.
Artem Russakovskii

Potresti aver scoperto qualcosa. Quando inizia la seconda attività, la prima eseguirà in Pausa-> in Stop o solo in Pausa? Perché sto stampando tutto sulle chiamate del ciclo di vita ******* e vedo onPause -> onCreate, senza onStop. E uno dei crash dump in realtà diceva qualcosa come onPause = true o onStop = false per 3 attività che stava uccidendo.
Artem Russakovskii

OnStop dovrebbe essere chiamato quando l'attività lascia lo schermo, ma potrebbe non esserlo se viene recuperato in anticipo dal sistema.
dten

Non viene recuperato perché non vedo onCreate e onRestoreInstanceState chiamato se clicco indietro.
Artem Russakovskii

secondo il ciclo di vita quelli potrebbero non essere mai chiamati, però, controlla la mia risposta che ha un link al blog ufficiale degli sviluppatori, è più che probabile il modo in cui stai passando le tue bitmap in giro
dten

2

Penso che il problema forse una combinazione di molti fattori indicati qui nelle risposte sia ciò che ti dà problemi. Come ha detto @Tim, un riferimento (statico) a un'attività o un elemento in tale attività può far sì che il GC salti l'attività. Qui l'articolo che discute di questo aspetto. Penso che il probabile problema derivi da qualcosa che mantiene l'attività in uno stato "Processo visibile" o superiore, il che garantirà praticamente che l'attività e le sue risorse associate non verranno mai recuperate.

Qualche tempo fa ho affrontato il problema opposto con un servizio, quindi questo è ciò che mi ha fatto pensare a questo: c'è qualcosa che mantiene la tua attività in alto nell'elenco delle priorità del processo in modo che non sia soggetta al GC di sistema, come un riferimento (@Tim) o un ciclo (@Alvaro). Il ciclo non deve essere un elemento infinito o di lunga durata, solo qualcosa che funziona molto come un metodo ricorsivo o un ciclo a cascata (o qualcosa del genere).

EDIT: A quanto ho capito, onPause e onStop vengono chiamati automaticamente da Android secondo necessità. I metodi sono lì principalmente da sovrascrivere in modo che tu possa occuparti di ciò di cui hai bisogno prima che il processo di hosting venga interrotto (salvataggio delle variabili, salvataggio manuale dello stato, ecc.); ma si noti che è chiaramente affermato che onStop (insieme a onDestroy) potrebbe non essere chiamato in ogni caso. Inoltre, se il processo di hosting ospita anche un'attività, un servizio, ecc. Che ha uno stato "In primo piano" o "Visibile", il sistema operativo potrebbe non fermare il processo / thread. Ad esempio: un'attività e un servizio vengono entrambi forniti nello stesso processo e il servizio ritorna START_STICKYdaonStartCommand()il processo assume automaticamente almeno uno stato visibile. Questa potrebbe essere la chiave qui, prova a dichiarare un nuovo proc per l'attività e vedi se cambia qualcosa. Prova ad aggiungere questa riga alla dichiarazione della tua attività nel manifesto come: android:process=":proc2"e poi esegui di nuovo i test se la tua attività condivide un processo con qualcos'altro. Il pensiero qui è che se hai ripulito la tua attività e sei abbastanza sicuro che il problema non sia la tua attività, allora qualcos'altro è il problema ed è tempo di cacciarlo.

Inoltre, non ricordo dove l'ho visto (anche se l'ho visto nei documenti di Android) ma ricordo che qualcosa riguardo a un PendingIntentriferimento a un'attività può far sì che un'attività si comporti in questo modo.

Ecco un link per la onStartCommand()pagina con alcuni approfondimenti sul fronte del processo di non uccisione.


In base al collegamento che hai fornito (grazie), un'attività può essere interrotta se è stato chiamato onStop, ma nel mio caso, penso che qualcosa stia impedendo l'esecuzione di onStop. Cercherò sicuramente di capire perché. Tuttavia, dice anche che le attività con solo onPause possono essere uccise, cosa che non accade nel mio caso (vedo che onPause viene chiamato ma non onStop).
Artem Russakovskii

1

quindi l'unica cosa a cui posso davvero pensare è se hai una variabile statica che fa riferimento direttamente o indirettamente al contesto. Anche qualcosa come un riferimento a una parte dell'applicazione. Sono sicuro che l'hai già provato, ma lo suggerirò per ogni evenienza, prova a annullare TUTTE le tue variabili statiche in onDestroy () solo per assicurarti che il garbage collector lo ottenga


1

La più grande fonte di perdita di memoria che ho trovato è stata causata da qualche riferimento globale, di alto livello o di vecchia data al contesto. Se si mantiene il "contesto" memorizzato in una variabile ovunque, è possibile che si verifichino perdite di memoria imprevedibili.


Sì, l'ho sentito e letto molte volte, ma ho avuto problemi a definirlo. Se solo potessi capire in modo affidabile come rintracciarli.
Artem Russakovskii

1
Nel mio caso questo si è tradotto in qualsiasi variabile a livello di classe impostata uguale al contesto (ad esempio Class1.variable = getContext ();). In generale, sostituire ogni utilizzo di "contesto" nella mia app con una nuova chiamata a "getContext" o simili ha risolto i miei maggiori problemi di memoria. Ma i miei erano instabili e irregolari, non prevedibili come nel tuo caso, quindi forse qualcosa di diverso.
D2TheC

1

Prova a passare getApplicationContext () a tutto ciò che necessita di un contesto. Potresti avere una variabile globale che contiene un riferimento alle tue attività e impedisce che vengano raccolte in modo indesiderato.


1

Una delle cose che ha davvero aiutato il problema della memoria nel mio caso è stata l'impostazione inPurgeable su true per le mie bitmap. Vedi Perché NON dovrei mai usare l'opzione inPurgeable di BitmapFactory? e la discussione della risposta per maggiori informazioni.

La risposta di Dianne Hackborn e la nostra successiva discussione (grazie anche a CommonsWare) hanno aiutato a chiarire alcune cose su cui ero confuso, quindi grazie per questo.


So che questo è un vecchio argomento ma vedo che sembra trovare questo thread su molte ricerche. Voglio collegare un ottimo tutorial che ho imparato così tanto da cui puoi trovare qui: developer.android.com/training/displaying-bitmaps/index.html
Codeversed

0

Ho riscontrato lo stesso problema con te. Stavo lavorando ad un'app di messaggistica istantanea, per lo stesso contatto è possibile avviare una ProfileActivity in una ChatActivity e viceversa. Aggiungo solo una stringa extra nell'intento di avviare un'altra attività, prende le informazioni del tipo di classe dell'attività iniziale e l'ID utente. Ad esempio, ProfileActivity avvia una ChatActivity, quindi in ChatActivity.onCreate, contrassegno il tipo di classe invoker 'ProfileActivity' e l'ID utente, se sta per avviare un'attività, controllerei se è un 'ProfileActivity' per l'utente o meno . Se è così, chiama semplicemente 'finish ()' e torna al precedente ProfileActivity invece di crearne uno nuovo. La perdita di memoria è un'altra cosa.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.