2012-10-03 11 views
10

Prima di abilitare l'opzione largeHeap, gestivo bitmap di grandi dimensioni e si consuma quasi l'intera memoria disponibile per l'applicazione, e il suo riciclo sulla navigazione e il caricamento di nuovi funziona su quasi l'intero heap disponibile. Tuttavia, quando alcune operazioni richiedono un po 'più di memoria, l'applicazione si blocca. Quindi ho abilitato lo largeHeap=true per avere un po 'di memoria in più.Ricicla bitmap con largeHeap abilitato

Ma facendo questo ha un comportamento imprevisto, è sembra che recycle() metodo bitmap non funzionano il più delle volte, e l'applicazione che ha lavorato in 58MB di memoria (e supera talvolta gettando un OutOfMemoryException) ora utilizza la memoria in modo esponenziale e mantiene in crescita (per ora il test che ho fatto è arrivato a 231Mb di memoria allocata), il comportamento previsto è che la gestione della memoria continua a funzionare e l'applicazione non utilizzerà più di 60Mb.

Come posso evitarlo? O riciclare in modo efficiente bitmap?

MODIFICA: In realtà, ho assegnato un OutOfMemoryError durante l'allocazione di più di 390 Mb di memoria sul dispositivo. La lettura dei log GC_ * ha mostrato che solo GC_FOR_ALLOC che ha liberato 3,8 Mb a volte, ma quasi mai nessun altro GC esegue liberato qualcosa.

+1

Hai guardato questo bel video? http://www.youtube.com/watch?v=_CruQY55HOk Suppongo tu abbia una perdita di memoria nel tuo codice – HitOdessit

+0

Dovrai dirci se si tratta di pre-Honeycomb o> = Honeycomb –

risposta

21

Probabilmente si dovrebbe dare un'occhiata a Displaying Bitmaps Efficiently che comprende diversi modi per gestire grandi bitmap in modo efficiente,

  • Caricamento bitmap di grandi dimensioni in modo efficiente
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.inJustDecodeBounds = true; 
BitmapFactory.decodeResource(getResources(), R.id.myimage, options); 
int imageHeight = options.outHeight; 
int imageWidth = options.outWidth; 

Questo darà tu la dimensione dell'immagine prima di scaricare e su quella base puoi controllare la dimensione del dispositivo e la scala usando calculateInSampleSize() e decodeSampledBitmapFromResource() dato nella spiegazione dei documenti.

il calcolo di quanto abbiamo bisogno di scalare l'immagine,

if (imageHeight > reqHeight || imageWidth > reqWidth) { 
     if (imageWidth > imageHeight) { 
      inSampleSize = Math.round((float)imageHeight/(float)reqHeight); 
     } else { 
      inSampleSize = Math.round((float)imageWidth/(float)reqWidth); 
     } 
    } 
int inSampleSize = Math.min(imageWidth/reqWidth,imageHeight/reqHeight); 

L'è possibile impostare il inSampleSize,

options.inSampleSize = inSampleSize; 

Poi finalmente assicuratevi di chiamare,

options.inJustDecodeBounds = false; 

altrimenti tornerà Bitmap come null

  • Elaborazione Bitmap Off the UI Discussione

    lavorazione bitmap sul thread UI non è mai sicura per cui il suo sempre meglio farlo in un UI thread in background e aggiornamento dopo il processo è completato.

  • Caching bitmap

    LruCache è disponibile da API 12, ma se siete interessati utilizzando sotto le versioni è disponibile in Support Library troppo anche. Quindi il caching delle immagini dovrebbe essere fatto efficientemente usando quello. Inoltre è possibile utilizzare DiskLruCache per le immagini in cui si desidera quindi rimanere per un periodo più lungo nella memoria esterna.

  • Cancellazione della cache

    A volte, quando la dimensione dell'immagine è troppo grande anche la memorizzazione nella cache l'immagine provoca OutOfMemoryError quindi in questo caso è meglio cancellare la cache quando l'immagine è fuori dalla portata o non utilizzati per periodo più lungo in modo che altre immagini possano essere memorizzate nella cache.

    avevo creato un esempio di demo per la stessa, è possibile scaricare dal here

+3

suona bene, +1 per you Mr. –

+2

buona risposta per la gestione di bitmap ma che ne dite di "riciclare in modo efficiente bitmap"? – ALiGOTec

+0

perché riciclare Bitmap? Se una bitmap viene riciclata, non può essere riutilizzata e ciò comporterà il download insolito di immagini ancora e ancora. Quindi, meglio è usare il caching di Bitmap. –

3

Il tuo caso si comporta come previsto. Prima di Honeycomb, recycle() liberava incondizionatamente la memoria. Ma su 3.0 e sopra, le bitmap fanno parte della normale memoria raccolta dati inutili. Hai un sacco di RAM sul dispositivo, hai permesso alla JVM di allocare più del limite di 58M, ora il garbage collector è soddisfatto e non ha alcun incentivo a recuperare la memoria occupata dai tuoi bitmap.

È possibile verificarlo eseguendo su un emulatore con quantità controllata di RAM o caricando alcuni servizi che consumano memoria sul dispositivo. GC salterà al lavoro. Puoi use DDMS esaminare ulteriormente l'utilizzo della memoria.

si può provare alcune soluzioni per la gestione della memoria bitmap: Bitmaps in AndroidBitmap memory leakshttp://blog.javia.org/how-to-work-around-androids-24-mb-memory-limit/, ma iniziare con il official Android bitmap tips, come spiegato in @Lalit Poptani s' detailed answer.

presente che in caso di bitmap in memoria OpenGL come texture ha alcune implicazioni di prestazioni (ma perfetta se si vuole rendere queste immagini bitmap attraverso OpenGL alla fine). Sia le trame che le soluzioni malloc richiedono la liberazione esplicita della memoria bitmap che non si utilizza più.

1

Per affrontare il tuo dilemma, credo che questo è il comportamento previsto.

Se si desidera liberare memoria, è possibile chiamare occasionalmente lo System.gc(), ma in realtà si dovrebbe in genere consentire la gestione della raccolta dati inutili.

Quello che consiglio è di mantenere una semplice cache (url/nomefile per bitmap) di qualche tipo che tenga traccia del proprio utilizzo della memoria calcolando il numero di byte che ogni bitmap sta rilevando.

/** 
* Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config 
* @param width 
* @param height 
* @param config 
* @return 
*/ 
public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){ 
    long pixels=width*height; 
    switch(config){ 
    case ALPHA_8: // 1 byte per pixel 
     return pixels; 
    case ARGB_4444: // 2 bytes per pixel, but depreciated 
     return pixels*2; 
    case ARGB_8888: // 4 bytes per pixel 
     return pixels*4; 
    case RGB_565: // 2 bytes per pixel 
     return pixels*2; 
    default: 
     return pixels; 
    } 
} 

Allora la tua ricerca la quantità di memoria l'applicazione sta usando e quanto è disponibile, forse prendere la metà di questo e cercare di mantenere la dimensione totale della cache immagine sotto che, semplicemente rimuovendo (dereferenziando) le immagini più vecchie da la vostra lista quando il vostro stanno arrivando contro questo limite, non riciclaggio. Lascia che il garbage collector ripulisca i bitmap quando sono entrambi esclusi dalla cache e non vengono utilizzati da nessuna vista.

/** 
* Calculates and adjusts the cache size based on amount of memory available and average file size 
* @return 
*/ 
synchronized private int calculateCacheSize(){ 
    if(this.cachedBitmaps.size()>0){ 
     long maxMemory = this.getMaxMemory(); // Total max VM memory minus runtime memory 
     long maxAllocation = (long) (ImageCache.MEMORY_FRACTION*maxMemory); 
     long avgSize = this.bitmapCacheAllocated/this.cachedBitmaps.size(); 
     this.bitmapCacheSize = (int) (maxAllocation/avgSize); 
    } 
    return this.bitmapCacheSize; 
} 

vi consiglio di stare lontano da utilizzando recycle(), provoca un sacco di eccezioni intermittenti (come quando una vista apparentemente finalizzati tentano di accedere bitmap riciclati) e in generale sembra buggy.

+0

Bene, ho già posticipato gli oggetti, ma prima di riciclarlo, proverò senza ricorrere al recycle() –

1

bisogna essere molto attenti con la manipolazione bitmap su Android. Lasciatemi riformulare questo: devi fare attenzione a gestire bitmap anche su un sistema con 4 GB di RAM. Quanto sono grandi questi ragazzi e ne hai molti? Potrebbe essere necessario tagliarlo e affiancarlo se è grande. Ricorda che usi la RAM video, che è un animale diverso dalla RAM del sistema.

Pre-Honeycomb, le bitmap sono state allocate sul livello C++, in modo che l'utilizzo della RAM fosse invisibile a Java e non fosse accessibile al garbage collector. Un bitmap non compresso da 3 MP con lo spazio dei colori RGB24 utilizza circa 9-10 megabyte (circa 2048x1512). Quindi, le immagini più grandi possono facilmente riempire il tuo heap. Ricorda inoltre che in qualsiasi cosa venga utilizzata per la RAM video (a volte dedicata alla RAM, a volte condivisa con il sistema), i dati vengono solitamente memorizzati non compressi.

In sostanza, se si prendono di mira i dispositivi pre-Honeycomb, è quasi necessario gestire oggetto bitmap come se si stesse codifica di un programma di C++. Esecuzione del onDestory bitmap riciclo()() di solito funziona se non ci sono molte immagini, ma se si dispone di una tonnellata di immagini sullo schermo, potrebbe essere necessario gestirli on-the-fly. Inoltre, se si avvia un'altra attività, potrebbe essere necessario inserire la logica in onPause() e onResume().

È anche possibile memorizzare nella cache le immagini utilizzando il file system Android o SQLite quando non sono in RAM video. Potresti riuscire a farla finita con la cache nella RAM se usi un formato come .jpg o .png con molti dati ripetuti/

+1

Questo è il caso, ci sono tonnellate di immagini che sono paginate attraverso l'applicazione stessa. Gestisco al volo con il riciclo e funziona davvero bene. –

2

Definitivamente @Lalit La risposta di Poptani è il modo per farlo, dovresti scala veramente il tuo Bitmaps se sono molto grandi.Un modo preferibile è che questo sia fatto server-side se questo è possibile, poiché ridurrai anche il tempo di NetworkOperation.

Per quanto riguarda l'attuazione di un MemoryCache e DiskCache questo è ancora il modo migliore per farlo, ma io consiglio comunque di utilizzare una libreria esistente, che fa esattamente questo (Ignition) e vi farà risparmiare un sacco di tempo, e anche un sacco di perdite di memoria, dal momento che il tuo Heap non si svuota dopo GC Posso presumere che probabilmente hai anche qualche memory leaks.

+0

Beh, letteralmente "Cerca e distruggi" le perdite di memoria, la memoria ruota attorno alla navigazione come previsto. –