Strano problema di memoria esaurita durante il caricamento di un'immagine su un oggetto Bitmap


1289

Ho una vista elenco con un paio di pulsanti immagine su ogni riga. Quando fai clic sulla riga dell'elenco, avvia una nuova attività. Ho dovuto creare le mie schede a causa di un problema con il layout della fotocamera. L'attività che viene avviata per il risultato è una mappa. Se faccio clic sul mio pulsante per avviare l'anteprima dell'immagine (carica un'immagine dalla scheda SD) l'applicazione ritorna dall'attività all'attività al listviewgestore dei risultati per riavviare la mia nuova attività che non è altro che un widget immagine.

L'anteprima dell'immagine nella visualizzazione elenco viene eseguita con il cursore e ListAdapter. Questo lo rende piuttosto semplice, ma non sono sicuro di come posso mettere un'immagine ridimensionata (vale a dire bit più piccoli non pixel come srcper il pulsante immagine al volo. Quindi ho appena ridimensionato l'immagine che è venuta fuori dalla fotocamera del telefono.

Il problema è che viene visualizzato un errore di memoria esaurita quando si tenta di tornare indietro e riavviare la seconda attività.

  • C'è un modo per costruire facilmente l'adattatore elenco riga per riga, dove posso ridimensionare al volo (un po 'saggio )?

Questo sarebbe preferibile in quanto ho anche bisogno di apportare alcune modifiche alle proprietà dei widget / elementi in ogni riga poiché non sono in grado di selezionare una riga con il touchscreen a causa del problema di messa a fuoco. ( Posso usare la sfera del rullo. )

  • So che posso fare un ridimensionamento fuori banda e salvare la mia immagine, ma non è proprio quello che voglio fare, ma un po 'di codice di esempio sarebbe carino.

Non appena ho disabilitato l'immagine nella visualizzazione elenco, ha funzionato di nuovo bene.

FYI: Ecco come lo stavo facendo:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME,DBHelper.KEY_ADDRESS,DBHelper.KEY_CITY,DBHelper.KEY_GPSLONG,DBHelper.KEY_GPSLAT,DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] {R.id.businessname,R.id.address,R.id.city,R.id.gpslong,R.id.gpslat,R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

Dov'è R.id.imagefilenamea ButtonImage.

Ecco il mio LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

Ho anche un nuovo errore durante la visualizzazione di un'immagine:

01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed

8
Ho risolto questo problema evitando Bitmap.decodeStream o decodeFile e usando il metodo BitmapFactory.decodeFileDescriptor.
Fraggle,

1
Ho anche affrontato un problema simile un paio di settimane fa e l'ho risolto ridimensionando le immagini fino al punto ottimale. Ho scritto un approccio completo nel mio blog codingjunkiesforum.wordpress.com/2014/06/12/… e ho caricato un progetto di esempio completo con il codice incline OOM vs il codice di prova OOM athttps: //github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat

5
La risposta accettata su questa domanda è in discussione il
8:15

4
Questo succede quando non leggi le guide per sviluppatori Android
Pedro Varela

2
Questo accade a causa della cattiva architettura Android. Dovrebbe ridimensionare le immagini come iOS e UWP. Non devo fare queste cose da solo. Gli sviluppatori Android si abituano a quell'inferno e pensano che funzioni come dovrebbe.
Accesso negato il

Risposte:


651

Il corso di formazione Android , " Visualizzazione efficiente delle bitmap ", offre alcune informazioni utili per comprendere e gestire l'eccezione java.lang.OutOfMemoryError: bitmap size exceeds VM budgetdurante il caricamento delle bitmap.


Leggi le dimensioni e il tipo di bitmap

La BitmapFactoryclasse fornisce diversi metodi di decodifica ( decodeByteArray(), decodeFile(), decodeResource(), ecc) per creare un Bitmapda varie fonti. Scegli il metodo di decodifica più appropriato in base alla tua fonte di dati immagine. Questi metodi tentano di allocare memoria per la bitmap costruita e quindi possono facilmente comportare OutOfMemoryun'eccezione. Ogni tipo di metodo di decodifica ha firme aggiuntive che consentono di specificare le opzioni di decodifica tramite la BitmapFactory.Optionsclasse. Impostare la inJustDecodeBoundsproprietà truedurante la decodifica evita allocazione di memoria, restituendo nullper l'oggetto bitmap ma impostazione outWidth, outHeighte outMimeType. Questa tecnica consente di leggere le dimensioni e il tipo di dati dell'immagine prima della costruzione (e allocazione della memoria) della bitmap.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

Per evitare java.lang.OutOfMemoryeccezioni, controlla le dimensioni di una bitmap prima di decodificarla, a meno che non ti fidi assolutamente della fonte per fornirti dati immagine di dimensioni prevedibili che si adattano comodamente alla memoria disponibile.


Carica una versione ridotta in memoria

Ora che le dimensioni dell'immagine sono note, possono essere utilizzate per decidere se caricare l'immagine completa in memoria o se invece dovrebbe essere caricata una versione sottocampionata. Ecco alcuni fattori da considerare:

  • Utilizzo stimato della memoria per caricare l'intera immagine in memoria.
  • La quantità di memoria che sei disposto a impegnare per caricare questa immagine dati altri requisiti di memoria della tua applicazione.
  • Dimensioni del componente ImageView o dell'interfaccia utente di destinazione in cui caricare l'immagine.
  • Dimensioni dello schermo e densità del dispositivo corrente.

Ad esempio, non vale la pena caricare in memoria un'immagine di 1024x768 pixel se alla fine verrà visualizzata in una miniatura di 128x96 pixel in una ImageView.

A dire il decoder per sottocampionare l'immagine, il caricamento di una versione più piccola nella memoria, insieme inSampleSizea truein BitmapFactory.Optionsoggetto. Ad esempio, un'immagine con risoluzione 2048x1536 decodificata con un valore inSampleSizedi 4 produce una bitmap di circa 512x384. Il caricamento in memoria utilizza 0,75 MB anziché 12 MB per l'immagine completa (presupponendo una configurazione bitmap di ARGB_8888). Ecco un metodo per calcolare un valore di dimensione del campione che è una potenza di due basato su una larghezza e altezza target:

public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) > reqHeight
                && (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

Nota : viene calcolata una potenza di due valori poiché il decodificatore utilizza un valore finale arrotondando per difetto alla potenza di due più vicina, come indicato nella inSampleSizedocumentazione.

Per utilizzare questo metodo, prima decodifica con inJustDecodeBoundsset to true, passa le opzioni e poi decodifica nuovamente usando il nuovo inSampleSizevalore e inJustDecodeBoundsimposta su false:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
    int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

Questo metodo semplifica il caricamento di una bitmap di dimensioni arbitrariamente grandi in una ImageViewche visualizza una miniatura di 100x100 pixel, come mostrato nel seguente codice di esempio:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

Puoi seguire un processo simile per decodificare bitmap da altre fonti, sostituendo il BitmapFactory.decode*metodo appropriato secondo necessità.


21
Questa risposta è stata discussa il meta
rene il

9
Questa risposta (ad eccezione delle informazioni raggiunte tramite il collegamento) non offre molto di una soluzione come per una risposta. Le parti importanti del collegamento dovrebbero essere unite alla domanda.
FallenAngel

7
Questa risposta, come la domanda e le altre risposte, sono Wiki della comunità, quindi questo è qualcosa che la comunità può risolvere modificando, qualcosa che non richiede l'intervento del moderatore.
Martijn Pieters

il collegamento corrente al contenuto e il supporto di Kotlin sono disponibili all'indirizzo: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr

891

Per correggere l'errore OutOfMemory, dovresti fare qualcosa del genere:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

Questa inSampleSizeopzione riduce il consumo di memoria.

Ecco un metodo completo. Prima legge le dimensioni dell'immagine senza decodificare il contenuto stesso. Quindi trova il inSampleSizevalore migliore , dovrebbe essere una potenza di 2 e infine l'immagine viene decodificata.

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);

        // The new size we want to scale to
        final int REQUIRED_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            scale *= 2;
        }

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
    } catch (FileNotFoundException e) {}
    return null;
}

31
Si noti che 10 potrebbe non essere il valore migliore per inSampleSize, tuttavia la documentazione suggerisce l'utilizzo di potenze di 2.
Mirko N.

70
Sto affrontando lo stesso problema di Chrispix, ma non credo che la soluzione qui risolva davvero il problema, ma piuttosto lo elude. La modifica delle dimensioni del campione riduce la quantità di memoria utilizzata (a scapito della qualità dell'immagine, che probabilmente va bene per un'anteprima dell'immagine), ma non impedirà l'eccezione se viene decodificato un flusso di immagini abbastanza grande, se sono presenti più flussi di immagini decodificato. Se trovo una soluzione migliore (e potrebbe non essercene una), posterò una risposta qui.
Flynn81,

4
Hai solo bisogno di una dimensione appropriata per abbinare lo schermo alla densità di pixel, per lo zoom in avanti e così puoi prendere un campione dell'immagine a una densità più alta.
stealthcopter

4
REQUIRED_SIZE è la nuova dimensione su cui vuoi ridimensionare.
Fedor

8
questa soluzione mi ha aiutato ma la qualità dell'immagine è terribile. Sto usando un viewfilpper per visualizzare le immagini eventuali suggerimenti?
user1106888

373

Ho apportato un piccolo miglioramento al codice di Fedor. Praticamente fa lo stesso, ma senza il (secondo me) brutto ciclo while e si traduce sempre in una potenza di due. Complimenti a Fedor per aver realizzato la soluzione originale, sono rimasto bloccato fino a quando non ho trovato la sua, e poi sono stato in grado di farlo :)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}

40
Sì, hai ragione, mentre non è così bello. Ho appena provato a renderlo chiaro a tutti. Grazie per il tuo codice
Fedor

10
@ Thomas Vervest - C'è un grosso problema con quel codice. ^ non aumenta 2 a una potenza, ma xors 2 con il risultato. Vuoi Math.pow (2.0, ...). Altrimenti, sembra buono.
DougW,

6
Ooh, è molto bello! Mio male, lo correggerò immediatamente, grazie per la risposta!
Thomas Vervest,

8
Stai creando due nuovi FileInputStreams, uno per ogni chiamata al BitmapFactory.decodeStream(). Non è necessario salvare un riferimento a ciascuno di essi in modo che possano essere chiusi in un finallyblocco?
Matsev,

1
@Babibu La documentazione non afferma che il flusso sia chiuso per te, quindi presumo che dovrebbe essere ancora chiuso. Una discussione interessante e correlata può essere trovata qui . Nota il commento di Adrian Smith, che si riferisce direttamente al nostro dibattito.
Thomas Vervest,

233

Vengo dall'esperienza iOS ed ero frustrato nello scoprire un problema con qualcosa di così semplice come caricare e mostrare un'immagine. Dopotutto, tutti coloro che hanno questo problema stanno provando a visualizzare immagini di dimensioni ragionevoli. Comunque, ecco le due modifiche che hanno risolto il mio problema (e reso la mia app molto reattiva).

1) Ogni volta che lo fai BitmapFactory.decodeXYZ(), assicurati di passare a BitmapFactory.Optionscon inPurgeableset su true(e preferibilmente inInputShareableanche con set su true).

2) MAI usare Bitmap.createBitmap(width, height, Config.ARGB_8888). Intendo MAI! Non ho mai avuto quella cosa non aumentare l'errore di memoria dopo pochi passaggi. Nessuna quantità di recycle(), System.gc(), qualunque sia aiutato. Solleva sempre un'eccezione. L'altro modo che funziona in realtà è di avere un'immagine fittizia nei disegni (o in un'altra Bitmap decodificata utilizzando il passaggio 1 sopra), ridimensionarla in base a ciò che si desidera, quindi manipolare la Bitmap risultante (come passarla su una tela per più divertimento). Allora, che cosa si dovrebbe usare invece è: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Se per qualsiasi motivo DEVI usare il metodo di creazione della forza bruta, almeno passa Config.ARGB_4444.

Questo è quasi garantito per farti risparmiare ore se non giorni. Tutto ciò che parla di ridimensionare l'immagine, ecc. Non funziona davvero (a meno che non si consideri una dimensione errata o un'immagine degradata una soluzione).


22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;e Bitmap.createScaledBitmap(srcBitmap, width, height, false);ho risolto il mio problema con l'eccezione di memoria insufficiente su Android 4.0.0. Grazie compagno!
Jan-Terje Sørensen,

5
Nella chiamata Bitmap.createScaledBitmap () probabilmente dovresti usare true come parametro flag. Altrimenti la qualità dell'immagine non sarà uniforme quando si ingrandisce. Controllare questa discussione stackoverflow.com/questions/2895065/...
rOrlig

11
Questo è davvero un consiglio favoloso. Vorrei poterti dare un +1 in più per aver portato Google all'attività per questo bug incredibilmente stravagante. Voglio dire ... se non si tratta di un bug, la documentazione deve davvero avere alcune insegne al neon che lampeggiano seriamente che dicono "QUESTO È COME PROCEDERE LE FOTO", perché ho avuto problemi con questo per 2 anni e proprio ora ho trovato questo post. Grande scoperta.
Evgenij Simkin,

Ridimensionare le immagini sicuramente aiuta, ma questo è un passo importante e ciò che alla fine ha risolto questo problema per me. Il problema con il ridimensionamento delle immagini è se ne hai molte o se le immagini di origine sono molto grandi, puoi comunque imbatterti nello stesso problema. +1 a te Efraim.
Dave,

10
A partire da Lollipop, BitmapFactory.Options.inPurgeablee BitmapFactory.Options.inInputShareablesono
obsoleti

93

È un bug noto , non a causa di file di grandi dimensioni. Poiché Android memorizza nella cache i Drawable, la memoria si esaurisce dopo aver utilizzato poche immagini. Ma ho trovato un modo alternativo per farlo, saltando il sistema di cache predefinito Android.

Soluzione : spostare le immagini nella cartella "risorse" e utilizzare la seguente funzione per ottenere BitmapDrawable:

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}

79

Ho avuto lo stesso problema e risolto evitando le funzioni BitmapFactory.decodeStream o decodeFile e invece ho usato BitmapFactory.decodeFileDescriptor

decodeFileDescriptor sembra che chiama metodi nativi diversi rispetto a decodeStream / decodeFile.

Comunque, ciò che ha funzionato è stato questo (nota che ho aggiunto alcune opzioni come alcune sopra, ma non è questo che ha fatto la differenza. Ciò che è fondamentale è la chiamata a BitmapFactory.decodeFileDescriptor invece di decodeStream o decodeFile ):

private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.inInputShareable=true;              //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future
    bfOptions.inTempStorage=new byte[32 * 1024]; 

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

Penso che ci sia un problema con la funzione nativa utilizzata in decodeStream / decodeFile. Ho confermato che viene chiamato un metodo nativo diverso quando si utilizza decodeFileDescriptor. Inoltre quello che ho letto è "che le immagini (bitmap) non sono allocate in modo Java standard ma tramite chiamate native; le allocazioni vengono eseguite al di fuori dell'heap virtuale, ma vengono conteggiate al suo interno! "


1
stesso risultato dalla memoria, in realtà non importa quale metodo lo stai usando dipende dal numero di byte che stai trattenendo per leggere i dati che esauriscono la memoria.
PiyushMishra,

72

Penso che il modo migliore per evitare OutOfMemoryErrorsia affrontarlo e capirlo.

Ho creato un'app per causare intenzionalmente OutOfMemoryErrore monitorare l'utilizzo della memoria.

Dopo aver fatto molti esperimenti con questa app, ho le seguenti conclusioni:

Parlerò prima delle versioni dell'SDK prima di Honey Comb.

  1. La bitmap è memorizzata nell'heap nativo, ma raccoglierà automaticamente la spazzatura, chiamando recycle () è inutile.

  2. Se {Dimensione heap VM} + {memoria heap nativa allocata}> = {Limite dimensione heap VM per il dispositivo} e si sta tentando di creare bitmap, verrà generato OOM.

    AVVISO: viene conteggiata la DIMENSIONE HEAP VM anziché la MEMORIA ASSEGNATA VM.

  3. Le dimensioni dell'heap di macchine virtuali non si ridurranno mai dopo l'espansione, anche se la memoria della macchina virtuale allocata viene ridotta.

  4. Quindi devi mantenere la memoria di picco della VM il più bassa possibile per evitare che la dimensione dell'heap della VM cresca troppo grande per salvare la memoria disponibile per le bitmap.

  5. Chiamare manualmente System.gc () non ha senso, il sistema lo chiamerà prima di provare ad aumentare le dimensioni dell'heap.

  6. Anche la dimensione dell'heap nativo non si ridurrà mai, ma non viene conteggiata per OOM, quindi non è necessario preoccuparsene.

Quindi, parliamo di SDK inizia da Honey Comb.

  1. Bitmap è memorizzata nell'heap di VM, la memoria nativa non viene conteggiata per OOM.

  2. La condizione per OOM è molto più semplice: {Dimensione heap VM}> = {Limite dimensione heap VM per il dispositivo}.

  3. Quindi hai più memoria disponibile per creare bitmap con lo stesso limite di dimensione dell'heap, è meno probabile che venga lanciata OOM.

Ecco alcune delle mie osservazioni su Garbage Collection e Memory Leak.

Puoi vederlo tu stesso nell'app. Se un'attività ha eseguito un AsyncTask che era ancora in esecuzione dopo che l'attività è stata distrutta, l'attività non riceverà la spazzatura raccolta fino al termine di AsyncTask.

Questo perché AsyncTask è un'istanza di una classe interna anonima, contiene un riferimento all'attività.

La chiamata a AsyncTask.cancel (true) non interromperà l'esecuzione se l'attività è bloccata in un'operazione IO nel thread in background.

Anche i callback sono classi interne anonime, quindi se un'istanza statica nel progetto li contiene e non li rilascia, la memoria verrebbe a mancare.

Se hai programmato un'attività ripetuta o ritardata, ad esempio un Timer, e non chiami cancel () e purge () in onPause (), la memoria sarebbe trapelata.


AsyncTask non deve necessariamente essere "un'istanza di una classe interna anonima", e lo stesso vale per Callbackks. È possibile creare una nuova classe pubblica nel proprio file che estende AsyncTask o anche una private static classnella stessa classe. Non terranno alcun riferimento all'attività (a meno che tu non ne dia uno ovviamente)
Simon Forsberg,

65

Ultimamente ho visto molte domande sulle eccezioni OOM e sulla memorizzazione nella cache. La guida per gli sviluppatori ha un ottimo articolo su questo, ma alcuni tendono a non riuscire a implementarlo in modo adeguato.

Per questo motivo ho scritto un'applicazione di esempio che dimostra la memorizzazione nella cache in un ambiente Android. Questa implementazione non ha ancora ottenuto una OOM.

Guarda la fine di questa risposta per un link al codice sorgente.

Requisiti:

  • API Android 2.1 o successive (semplicemente non sono riuscito a ottenere la memoria disponibile per un'applicazione in API 1.6 - questo è l'unico pezzo di codice che non funziona in API 1.6)
  • Pacchetto di supporto Android

Immagine dello schermo

Caratteristiche:

  • Mantiene la cache in caso di modifica dell'orientamento , usando un singleton
  • Utilizzare un ottavo della memoria dell'applicazione assegnata alla cache (modificare se lo si desidera)
  • Le bitmap di grandi dimensioni vengono ridimensionate (è possibile definire i pixel massimi che si desidera consentire)
  • Controlla che sia disponibile una connessione Internet prima di scaricare le bitmap
  • Si assicura che si stia istanziando solo un'attività per riga
  • Se si sta gettando la ListViewvia, semplicemente non scaricherà le bitmap tra

Questo non include:

  • Memorizzazione nella cache del disco. Dovrebbe essere comunque facile da implementare: basta puntare a un'attività diversa che afferra le bitmap dal disco

Codice di esempio:

Le immagini che vengono scaricate sono immagini (75x75) da Flickr. Tuttavia, inserisci qualsiasi URL immagine che desideri elaborare e l'applicazione ridimensionerà se supera il massimo. In questa applicazione gli URL sono semplicemente in un Stringarray.

Il LruCacheha un buon modo per affrontare con le bitmap. Tuttavia, in questa applicazione ho inserito un'istanza di LruCacheun'altra classe di cache che ho creato per rendere l'applicazione più fattibile.

Elementi critici di Cache.java (il loadBitmap()metodo è il più importante):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

Non è necessario modificare nulla nel file Cache.java a meno che non si desideri implementare la memorizzazione nella cache del disco.

Cose fondamentali di MainActivity.java:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView()viene chiamato molto spesso. Normalmente non è una buona idea scaricare le immagini lì se non abbiamo implementato un controllo che ci garantisca che non avvieremo una quantità infinita di thread per riga. Cache.java controlla se il rowObject.mBitmapUrlgià è in un'attività e se lo è, non ne avvierà un'altra. Pertanto, molto probabilmente non stiamo superando la restrizione della coda di lavoro dal AsyncTaskpool.

Scarica:

È possibile scaricare il codice sorgente da https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip .


Ultime parole:

Ho provato questo per alcune settimane, non ho ancora ottenuto una sola eccezione OOM. Ho provato questo sull'emulatore, sul mio Nexus One e sul mio Nexus S. Ho testato URL di immagini che contengono immagini di qualità HD. L'unico collo di bottiglia è che ci vuole più tempo per il download.

C'è solo uno scenario possibile in cui posso immaginare che apparirà OOM, e cioè se scarichiamo molte immagini molto grandi, e prima che vengano ridimensionate e messe nella cache, occuperanno contemporaneamente più memoria e causeranno una OOM. Ma questa non è nemmeno una situazione ideale e molto probabilmente non sarà possibile risolverla in un modo più fattibile.

Segnala errori nei commenti! :-)


43

Ho fatto quanto segue per prendere l'immagine e ridimensionarla al volo. Spero che sia di aiuto

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    

26
Questo approccio ridimensiona la bitmap. Ma non risolve il problema di OutOfMemory perché la bitmap completa viene comunque decodificata.
Fedor

5
Vedrò se riesco a guardare il mio vecchio codice, ma penso che abbia risolto i miei problemi di memoria esaurita. Ricontrollerò il mio vecchio codice.
Chrispix,

2
Almeno in questo esempio, sembra che tu non stia mantenendo il riferimento alla bitmap completa, quindi il risparmio di memoria.
NoBugs,

Per me ha risolto il problema di memoria, ma ha ridotto la qualità delle immagini.
Pamela Sillah,

35

Sembra che questo sia un problema molto lungo, con molte spiegazioni diverse. Ho preso il consiglio delle due risposte presentate più comuni qui, ma nessuna di queste ha risolto i miei problemi con la VM sostenendo che non poteva permettersi i byte per eseguire la parte di decodifica del processo. Dopo alcuni scavi ho appreso che il vero problema qui è il processo di decodifica che toglie dal mucchio NATIVE .

Vedi qui: BitmapFactory OOM mi fa impazzire

Ciò mi ha portato ad un altro thread di discussione in cui ho trovato un paio di soluzioni in più per questo problema. Uno è quello di chiamare System.gc();manualmente dopo la visualizzazione dell'immagine. Ma ciò in realtà fa sì che la tua app utilizzi PIÙ memoria, nel tentativo di ridurre l'heap nativo. La soluzione migliore a partire dalla versione 2.0 (Donut) è usare l'opzione BitmapFactory "inPurgeable". Quindi ho semplicemente aggiunto o2.inPurgeable=true;subito dopo o2.inSampleSize=scale;.

Maggiori informazioni su questo argomento qui: il limite di memoria heap è solo 6M?

Ora, detto tutto ciò, sono un somaro completo anche con Java e Android. Quindi, se pensi che questo sia un modo terribile per risolvere questo problema, probabilmente hai ragione. ;-) Ma questo ha funzionato a meraviglia per me e ho trovato impossibile eseguire la VM dalla cache dell'heap ora. L'unico inconveniente che posso trovare è che stai cestinando la tua immagine disegnata nella cache. Ciò significa che se torni a DESTRA su quell'immagine, la ridisegni ogni volta. Nel caso di come funziona la mia applicazione, questo non è davvero un problema. Il tuo chilometraggio può variare.


OOM fisso eliminabile per me.
Artem Russakovskii,

35

sfortunatamente se nessuna delle opzioni precedenti funziona, quindi aggiungi questo al tuo file manifest . Tag applicazione interno

 <application
         android:largeHeap="true"

1
Puoi spiegarci cosa fa realmente? Dire semplicemente alle persone di aggiungere questo non aiuta.
Stealth Rabbi

1
Questa è una pessima soluzione. Fondamentalmente non stai cercando di risolvere il problema. Invece, chiedi al sistema Android di allocare più spazio heap per la tua applicazione. Ciò avrà pessime implicazioni sulla tua app come la tua app che consuma molta carica della batteria poiché GC deve attraversare un grande spazio heap per pulire la memoria e anche le prestazioni della tua app saranno più lente.
Prakash,

2
allora perché Android ci consente di aggiungere questo Android: largeHeap = "true" nel nostro manifest? Ora stai sfidando Android.
Himanshu Mori,

32

Usa questo bitmap.recycle();Questo aiuta senza alcun problema di qualità dell'immagine.


9
Secondo l'API, non è necessario chiamare recycle ().
Artem Russakovskii,

28

Ho una soluzione molto più efficace che non necessita di ridimensionamento di alcun tipo. Basta decodificare la bitmap solo una volta e quindi memorizzarla nella cache in una mappa con il suo nome. Quindi recupera semplicemente la bitmap rispetto al nome e impostala in ImageView. Non c'è altro da fare.

Questo funzionerà perché i dati binari effettivi della bitmap decodificata non sono memorizzati nell'heap di VM dalvik. È memorizzato esternamente. Quindi ogni volta che decodifichi una bitmap, alloca la memoria al di fuori dell'heap della VM che non viene mai recuperata da GC

Per aiutarti ad apprezzarlo meglio, immagina di aver conservato la tua immagine nella cartella disegnabile. Ottieni semplicemente l'immagine facendo getResources (). GetDrwable (R.drawable.). Questo NON decodificherà l'immagine ogni volta, ma riutilizzerà un'istanza già decodificata ogni volta che la chiami. Quindi, in sostanza, è memorizzato nella cache.

Ora poiché la tua immagine è in un file da qualche parte (o potrebbe persino provenire da un server esterno), è tua responsabilità memorizzare nella cache l'istanza bitmap decodificata per essere riutilizzata ovunque sia necessaria.

Spero che sia di aiuto.


4
"e poi memorizzalo nella cache in una mappa con il suo nome." Come memorizzi esattamente le tue immagini nella cache?
Vincent,

3
Hai davvero provato questo? Anche se i dati pixel non sono effettivamente archiviati nell'heap di Dalvik, le sue dimensioni nella memoria nativa vengono segnalate alla VM e conteggiate rispetto alla memoria disponibile.
ErikR,

3
@Vincent Penso che non sia difficile memorizzarli in una mappa. Vorrei suggerire qualcosa come HashMap <KEY, Bitmap> map, in cui la chiave può essere una stringa della sorgente o qualsiasi cosa abbia senso per te. Supponiamo che tu prenda un percorso come KEY, lo memorizzi come map.put (Path, Bitmap) e lo riceva tramite map.get (Path)
Rafael T

3
probabilmente vorrai usare HashMap <String, SoftReference <Bitmap>> se stai implementando una cache di immagini altrimenti potresti esaurire la memoria comunque - inoltre non penso che "alloca memoria al di fuori dell'heap di VM che non viene mai rivendicato da GC "è vero, la memoria viene recuperata, come ho capito, potrebbe essere solo un ritardo, che è ciò che bitmap.recycle () è, come suggerimento per recuperare il mem in anticipo ...
Dori,

28

Ho risolto lo stesso problema nel modo seguente.

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);

va bene ma sto usando bitmap multiple per disegnare il cerchio in OnCreate e l'attività chiama 4-5 volte, quindi come cancellare bitmap e come rimuovere bitmap e creare di nuovo per la seconda volta quando l'attività 0nCreate ..
ckpatel

27

Ci sono due problemi qui....

  • La memoria bitmap non è nell'heap della VM ma piuttosto nell'heap nativo - vedi BitmapFactory OOM che mi fa impazzire
  • La garbage collection per l'heap nativo è più pigra dell'heap della VM, quindi è necessario essere piuttosto aggressivi nel fare bitmap.recycle e bitmap = null ogni volta che si passa a un'attività onPause o onDestroy

È nell'heap di VM da Android
2.3+

27

Questo ha funzionato per me!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}

20

Nessuna delle risposte sopra ha funzionato per me, ma ho trovato una soluzione orribilmente brutta che ha risolto il problema. Ho aggiunto un'immagine di pixel 1x1 molto piccola al mio progetto come risorsa e l'ho caricata in ImageView prima di chiamare in Garbage Collection. Penso che ImageView non stia rilasciando Bitmap, quindi GC non l'ha mai raccolto. È brutto, ma sembra funzionare per ora.

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.

sembra che imageView non ricicli davvero la bitmap da sola. Mi ha aiutato, grazie
Dmitry Zaytsev,

@Mike puoi aggiungere il codice completo di imageloader oppure puoi darmi il link per caricare immagini bitmap. Se uso riciclo su bitmap, viene visualizzata tutta la mia visualizzazione elenco ma tutti gli elementi vengono visualizzati vuoti
TNR

@Mike puoi dire se faccio imageView = null prima di chiamare in Garbage Collection è buono o no?
Youddh,

@TNR Penso che ciò che ti manca qui sia che bitmapnel codice sopra sia l'immagine precedente già visualizzata, devi riciclare quella, cancellare tutti i riferimenti ad essa, farti imageViewdimenticare anche impostando una piccola sostituzione gc(); e dopo tutto questo: carica la tua NUOVA immagine bitmape visualizzala ...nel codice sopra.
TWiStErRob

Questo è sbagliato. Dovresti sempre cancellare il contenuto di ImageView PRIMA di riciclare la bitmap (anziché mentre è effettivamente visualizzata e utilizzata).
FindOut_Quran,

20

Ottime risposte qui, ma volevo che una classe completamente utilizzabile fosse in grado di affrontare questo problema ... quindi l'ho fatto.

Ecco la mia classe BitmapHelper a prova di OutOfMemoryError :-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}

A chiunque lo usi: ho appena corretto un bug: "int scaledBitmapHeight = scaledBitmap.getWidth ();" è sbagliato (ovviamente. L'ho sostituito con "int scaledBitmapHeight = scaledBitmap.getHeight ();"
Pascal

19

Questo funziona per me.

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

e questo è su C # monodroid. puoi facilmente cambiare il percorso dell'immagine. ciò che è importante qui sono le opzioni da impostare.


16

Questo sembra il posto adatto per condividere la mia classe di utilità per il caricamento e l'elaborazione delle immagini con la comunità, sei il benvenuto per usarlo e modificarlo liberamente.

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://stackoverflow.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * 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;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}

16

In una delle mie applicazioni devo scattare una foto da Camera/Gallery. Se l'utente fa clic sull'immagine dalla fotocamera (può essere 2MP, 5MP o 8MP), la dimensione dell'immagine varia da kBs a MBs. Se la dimensione dell'immagine è inferiore (o fino a 1-2 MB) al di sopra del codice funziona bene, ma se ho un'immagine di dimensioni superiori a 4 MB o 5 MB, OOMviene visualizzata nel riquadro :(

poi ho lavorato per risolvere questo problema e finalmente ho apportato il miglioramento di seguito al codice Fedor (tutto merito a Fedor per aver reso una soluzione così piacevole) :)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.inInputShareable = true; // Which kind of reference will be used to
                                    // recover the Bitmap data after being
                                    // clear, when it will be used in the
                                    // future

    BitmapFactory.decodeFile(fPath, opts);

    // The new size we want to scale to
    final int REQUIRED_SIZE = 70;

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

Spero che questo aiuti gli amici ad affrontare lo stesso problema!

per ulteriori informazioni si prega di fare riferimento a questo


14

Ho appena incontrato questo problema un paio di minuti fa. L'ho risolto facendo un lavoro migliore nella gestione dell'adattatore listview. Ho pensato che fosse un problema con le centinaia di immagini 50x50px che stavo usando, risulta che stavo cercando di gonfiare la mia vista personalizzata ogni volta che veniva mostrata la riga. Semplicemente testando per vedere se la riga era stata gonfiata, ho eliminato questo errore e sto usando centinaia di bitmap. Questo è in realtà per uno Spinner, ma l'adattatore di base funziona lo stesso per un ListView. Questa semplice correzione ha inoltre migliorato notevolmente le prestazioni dell'adattatore.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...

3
Non posso ringraziarti abbastanza per questo! Stavo inseguendo il problema sbagliato prima di vedere questo. Domanda per te: dal momento che ogni riga della mia lista ha un nome e una foto univoci, ho dovuto usare un array convertView per conservare ciascuno dei valori delle righe. Non riuscivo a vedere come l'utilizzo di una singola variabile ti avrebbe permesso di farlo. Mi sto perdendo qualcosa?
PeteH,

13

Ho passato l'intera giornata a testare queste soluzioni e l'unica cosa che ha funzionato per me sono gli approcci di cui sopra per ottenere l'immagine e chiamare manualmente il GC, che so non dovrebbe essere necessario, ma è l'unica cosa che ha funzionato quando sottopongo la mia app a test di carico pesante, passando da un'attività all'altra. La mia app ha un elenco di immagini in miniatura in una visualizzazione elenco in (diciamo attività A) e quando fai clic su una di quelle immagini ti porta a un'altra attività (diciamo attività B) che mostra un'immagine principale per quell'elemento. Quando passavo avanti e indietro tra le due attività, alla fine avrei ricevuto l'errore OOM e l'app avrebbe forzato la chiusura.

Quando arrivassi a metà strada verso la lista, si sarebbe schiantato.

Ora, quando ho implementato quanto segue nell'attività B, posso passare attraverso l'intera visualizzazione elenco senza problemi e continuare ad andare avanti e indietro ... ed è molto veloce.

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}

Adoro la tua soluzione! Ho anche passato ore a risolvere questo bug, così fastidioso! Modifica: Purtroppo, il problema persiste quando cambio l'orientamento dello schermo in modalità orizzontale ...
Xarialon

Questo alla fine mi ha aiutato insieme a: - BitmapFactory.Options options = new BitmapFactory.Options (); options.InPurgeable = true; options.InSampleSize = 2;
user3833732,

13

Questo problema si verifica solo negli emulatori Android. Ho anche riscontrato questo problema in un emulatore ma quando ho effettuato il check-in in un dispositivo ha funzionato bene.

Quindi, per favore, controlla un dispositivo. Potrebbe essere eseguito nel dispositivo.


12

I miei 2 centesimi: ho risolto i miei errori OOM con bitmap da:

a) ridimensionare le mie immagini di un fattore 2

b) utilizzo della libreria Picasso nel mio adattatore personalizzato per un ListView, con una chiamata in getView in questo modo:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


Sono contento che tu abbia menzionato Picasso, perché semplifica il caricamento delle immagini. Soprattutto quelli memorizzati in remoto.
Chrispix,

12

usa questo codice per ogni immagine selezionata da SdCard o disegnabile per convertire oggetti bitmap.

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

usa il tuo percorso immagine instend di ImageData_Path.get (img_pos) .getPath () .


12

Generalmente la dimensione dell'heap del dispositivo Android è solo di 16 MB (varia da dispositivo / sistema operativo vedi dimensioni heap post ), se stai caricando le immagini e attraversa la dimensione di 16 MB, genererà un'eccezione di memoria, invece di utilizzare Bitmap per, caricamento le immagini dalla scheda SD o dalle risorse o anche dalla rete provano a utilizzare getImageUri , il caricamento di bitmap richiede più memoria, oppure è possibile impostare bitmap su null se il lavoro svolto con quella bitmap.


1
E se setImageURI continua a ricevere un'eccezione, fai riferimento a questo stackoverflow.com/questions/15377186/…
Mahesh

11

Tutte le soluzioni qui richiedono l'impostazione di un IMAGE_MAX_SIZE. Ciò limita i dispositivi con hardware più potente e se la dimensione dell'immagine è troppo bassa, appare brutta sullo schermo HD.

Sono uscito con una soluzione che funziona con il mio Samsung Galaxy S3 e molti altri dispositivi, inclusi quelli meno potenti, con una migliore qualità dell'immagine quando si utilizza un dispositivo più potente.

L'essenziale è calcolare la memoria massima allocata per l'app su un determinato dispositivo, quindi impostare la scala sul valore più basso possibile senza superare questa memoria. Ecco il codice:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

Ho impostato la memoria massima utilizzata da questa bitmap al 25% della memoria massima allocata, potrebbe essere necessario regolarla in base alle proprie esigenze e assicurarsi che questa bitmap sia ripulita e non rimanga in memoria al termine dell'utilizzo. In genere utilizzo questo codice per eseguire la rotazione dell'immagine (bitmap di origine e destinazione), quindi la mia app deve caricare 2 bitmap in memoria contemporaneamente e il 25% mi offre un buon buffer senza esaurire la memoria quando eseguo la rotazione dell'immagine.

Spero che questo aiuti qualcuno là fuori ..


11

Tale OutofMemoryExceptionnon può essere completamente risolto chiamando il System.gc()e così via.

Riferendosi al ciclo di vita delle attività

Gli stati di attività sono determinati dal sistema operativo stesso in base all'utilizzo della memoria per ciascun processo e alla priorità di ciascun processo.

È possibile considerare le dimensioni e la risoluzione per ciascuna delle immagini bitmap utilizzate. Consiglio di ridurre le dimensioni, ricampionare a una risoluzione più bassa, fare riferimento al design delle gallerie (una piccola immagine PNG e una originale).


11

Questo codice aiuterà a caricare bitmap di grandi dimensioni da disegnabili

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Bene, pensi che sarebbe meglio usare un Caricatore invece di Async Task?
Chrispix,

Che ne dici Bitmap.Config.ARGB_565? Se l'alta qualità non è critica.
Hamzeh Soboh,
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.