glide - bitmap oom android




Strano problema di memoria durante il caricamento di un'immagine su un oggetto Bitmap (20)

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

Soluzione : spostare le immagini nella cartella "assets" 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);
}

Ho una vista elenco con un paio di pulsanti immagine su ogni riga. Quando fai clic sulla riga dell'elenco, viene avviata una nuova attività. Ho dovuto creare le mie schede personali a causa di un problema con il layout della videocamera. L'attività che viene lanciata 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à listview al gestore di risultati per rilanciare la mia nuova attività che non è altro che un widget immagine.

L'anteprima dell'immagine nella vista elenco viene eseguita con il cursore e ListAdapter . Questo lo rende piuttosto semplice, ma non sono sicuro di come sia possibile inserire un'immagine ridimensionata (cioè, la dimensione del bit più piccola non è il pixel src per il pulsante dell'immagine al volo.) Così ho appena ridimensionato l'immagine che è uscita dalla fotocamera del telefono.

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

  • C'è un modo per costruire facilmente l'elenco delle liste, riga per riga, dove posso ridimensionarlo al volo ( bit saggio )?

Ciò sarebbe preferibile in quanto ho anche bisogno di apportare alcune modifiche alle proprietà dei widget / elementi in ogni riga in quanto non riesco a selezionare una riga con touch screen a causa del problema di messa a fuoco. ( Posso usare la palla roller ) .

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

Appena ho disabilitato l'immagine nella visualizzazione elenco ha funzionato di nuovo bene.

Cordiali saluti: 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);

Dove R.id.imagefilename è un 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

Ho apportato un piccolo miglioramento al codice di Fedor. Fondamentalmente fa lo stesso, ma senza il (a parer mio) brutto ciclo mentre risulta sempre in potenza di due. Complimenti a Fedor per aver reso la soluzione originale, sono rimasto bloccato fino a quando non ho trovato il suo, e quindi sono riuscito a 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;
}

Ho visto molte domande sulle eccezioni OOM e sul caching ultimamente. La guida per gli sviluppatori ha un ottimo articolo su questo, ma alcuni tendono a fallire nell'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 successiva (semplicemente non riuscivo a ottenere la memoria disponibile per un'applicazione in API 1.6: questa è l'unica parte di codice che non funziona in API 1.6)
  • Pacchetto di supporto Android

Caratteristiche:

  • Mantiene la cache se c'è un cambiamento di orientamento , usando un singleton
  • Utilizzare un ottavo della memoria dell'applicazione assegnata alla cache (modificare se si desidera)
  • I bitmap di grandi dimensioni vengono ridimensionati (è possibile definire i pixel massimi che si desidera consentire)
  • Controlla che sia disponibile una connessione Internet prima di scaricare le bitmap
  • Ti assicura che stai solo istanziando un compito per riga
  • Se stai scappando da ListView , semplicemente non scaricherà i bitmap tra

Questo non include:

  • Memorizzazione nella cache del disco. Questo dovrebbe essere facile da implementare comunque - basta puntare a un compito diverso che afferra le bitmap dal disco

Codice di esempio:

Le immagini che vengono scaricate sono immagini (75x75) da Flickr. Tuttavia, inserisci qualsiasi URL di immagine che desideri venga elaborato e l'applicazione ridimensiona se supera il massimo. In questa applicazione gli URL sono semplicemente in una matrice di String .

Il LruCache ha un buon modo per gestire le bitmap. Tuttavia, in questa applicazione ho inserito un'istanza di un LruCache all'interno di un'altra classe cache che ho creato per rendere l'applicazione più fattibile.

Le cose critiche di loadBitmap() metodo loadBitmap() è 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.

Le 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 se non abbiamo implementato un controllo che ci assicuri che non avvieremo una quantità infinita di thread per riga. Cache.java controlla se il rowObject.mBitmapUrlgià è in un compito e se lo è, non ne avvierà un altro. Pertanto, è molto probabile che non si superi la restrizione della coda di lavoro dal AsyncTaskpool.

Scaricare:

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

Ultime parole:

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

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

Segnala errori nei commenti! :-)


La classe di allenamento Android , " Visualizzazione efficiente di bitmap ", offre alcune informazioni utili per comprendere e gestire l'eccezione java.lang.OutOfMemoryError: bitmap size exceeds VM budget durante il caricamento di Bitmap.

Leggi le dimensioni e il tipo di bitmap

La classe BitmapFactory fornisce diversi metodi di decodifica ( decodeByteArray() , decodeFile() , decodeResource() , ecc.) Per la creazione di un Bitmap da varie fonti. Scegli il metodo di decodifica più appropriato in base all'origine dati dell'immagine. Questi metodi tentano di allocare memoria per la bitmap costruita e pertanto possono facilmente generare un'eccezione OutOfMemory . Ogni tipo di metodo di decodifica ha firme aggiuntive che consentono di specificare le opzioni di decodifica tramite la classe BitmapFactory.Options . Impostando la proprietà inJustDecodeBounds su true mentre la decodifica evita l'allocazione della memoria, restituendo null per l'oggetto bitmap ma impostando outWidth , outHeight e outMimeType . Questa tecnica consente di leggere le dimensioni e il tipo dei dati dell'immagine prima della costruzione (e dell'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 le eccezioni java.lang.OutOfMemory , controlla le dimensioni di una bitmap prima di decodificarla, a meno che non ti fidi assolutamente dell'origine per fornire dati di immagine di dimensioni prevedibili che si adattino comodamente alla memoria disponibile.

Carica una versione ridotta in memoria

Ora che le dimensioni dell'immagine sono note, possono essere utilizzate per decidere se l'immagine completa deve essere caricata in memoria o se invece deve 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 si è disposti a impegnarsi a caricare questa immagine dati eventuali altri requisiti di memoria della propria applicazione.
  • Dimensioni del componente ImageView o dell'interfaccia utente di destinazione in cui deve essere caricata l'immagine.
  • Dimensioni dello schermo e densità del dispositivo corrente.

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

Per dire al decodificatore di sottocampionare l'immagine, caricando una versione più piccola in memoria, imposta inSampleSize su true nel tuo oggetto BitmapFactory.Options . Ad esempio, un'immagine con risoluzione 2048x1536 che viene decodificata con un inSampleSize di 4 produce una bitmap di circa 512x384. Il caricamento di questo 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 in base alla larghezza e all'altezza di destinazione:

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 in quanto il decodificatore utilizza un valore finale arrotondando per difetto alla potenza più vicina di due, come inSampleSize documentazione inSampleSize .

Per utilizzare questo metodo, decodificare prima con inJustDecodeBounds impostato su true , passare le opzioni attraverso e quindi decodificare nuovamente utilizzando il nuovo valore inJustDecodeBounds e inJustDecodeBounds impostato 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 un ImageView che visualizza una miniatura da 100x100 pixel, come mostrato nel seguente codice di esempio:

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

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


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 opzione inSampleSize riduce il consumo di memoria.

Ecco un metodo completo. Innanzitutto legge le dimensioni dell'immagine senza decodificare il contenuto stesso. Quindi trova il valore inSampleSize 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;
}

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 è sul monodroide C #. puoi facilmente cambiare il percorso dell'immagine. che importante qui è le opzioni da impostare.


sfortunatamente se nessuna delle funzioni sopra funziona, quindi aggiungere questo al file manifest . Tag di applicazione interno

 <application
         android:largeHeap="true"

Ci sono due problemi qui....

  • La memoria bitmap non si trova nell'heap della VM ma piuttosto nell'heap nativo - vedi OOM BitmapFactory che mi fa impazzire
  • La raccolta dei dati inutili per l'heap nativo è più lenta dell'heap della VM, quindi è necessario essere piuttosto aggressivi riguardo a bitmap.recycle e bitmap = null ogni volta che si passa attraverso onPause o onDestroy di un'attività.

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);

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

a) ridimensionamento delle immagini di un fattore 2

b) utilizzando la libreria Picasso nel mio adattatore personalizzato per un ListView, con una chiamata in getView come questa:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);


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());
    }
}

Questo mi sembra il posto appropriato in cui condividere la mia classe di utilità per caricare ed elaborare immagini con la comunità, sei libero di usarla e modificarla 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://.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;
    }
}

Generalmente la dimensione dell'heap del dispositivo Android è di soli 16 MB (varia da dispositivo / sistema operativo vedi dimensioni heap ), se stai caricando le immagini e supera la dimensione di 16 MB, verrà eliminata l'eccezione di memoria, invece di utilizzare Bitmap per, caricamento le immagini dalla scheda SD o dalle risorse o persino dalla rete provano a utilizzare getImageUri , il caricamento di bitmap richiede più memoria oppure puoi impostare bitmap su null se il tuo lavoro è stato eseguito con quella bitmap.


Grandi risposte qui, ma volevo una classe pienamente utilizzabile per affrontare questo problema .. quindi l'ho fatto.

Ecco la mia classe BitmapHelper che è la prova 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 [email protected]
            // 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;
    }

}

Ho passato l'intera giornata a testare queste soluzioni e l'unica cosa che ha funzionato per me è l'approccio sopra riportato per ottenere l'immagine e chiamare manualmente il GC, che so non dovrebbe essere necessario, ma è l'unica cosa che ha funzionato quando metto la mia app sotto test carico pesante passaggio tra le attività. La mia app ha un elenco di immagini in miniatura in un listview in (diciamo attività A) e quando fai clic su una di quelle immagini ti porta in 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 ricevevo l'errore OOM e l'applicazione si chiudeva forzatamente.

Quando arrivavo a metà della lista, si bloccava.

Ora, quando implemento quanto segue nell'attività B, posso passare attraverso l'intero listview senza problemi e andare avanti e indietro ... e il suo molto velocemente.

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

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

Ho una soluzione molto più efficace che non necessita di ridimensionamenti di alcun tipo. Decodifica semplicemente la tua bitmap solo una volta e poi la memorizzi in una mappa contro il suo nome. Quindi è sufficiente recuperare la bitmap sul nome e impostarla in ImageView. Non c'è nient'altro che deve essere fatto.

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

Per aiutarti a capire meglio questo, immagina di aver mantenuto l'immagine nella cartella drawable. Hai appena ottenuto l'immagine facendo un getResources (). GetDrwable (R.Drawable.). Questo NON decodificherà l'immagine ogni volta ma riutilizzerà un'istanza già decodificata ogni volta che la chiamerai. Quindi in sostanza è memorizzato nella cache.

Da quando l'immagine è in un file da qualche parte (o potrebbe anche provenire da un server esterno), è TUA la responsabilità di memorizzare nella cache l'istanza della bitmap decodificata da riutilizzare dove necessario.

Spero che questo ti aiuti.


Nessuna delle risposte sopra ha funzionato per me, ma ho trovato una soluzione orribilmente brutta che ha risolto il problema. Ho aggiunto un'immagine molto piccola di 1x1 pixel al mio progetto come risorsa e l'ho caricata nel mio ImageView prima di chiamare la garbage collection. Penso che potrebbe essere che ImageView non stia rilasciando la Bitmap, quindi GC non lo ha mai rilevato. È brutto, ma sembra che funzioni 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.

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

Facendo riferimento 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 la dimensione e la risoluzione per ciascuna delle immagini bitmap utilizzate. Consiglio di ridurre le dimensioni, ricampionare a una risoluzione inferiore, fare riferimento al design delle gallerie (una PNG di piccole dimensioni e un'immagine originale).


Sembra che questo sia un problema di lunga durata, con molte spiegazioni diverse. Ho preso il consiglio delle due risposte presentate più comuni qui, ma nessuno di questi ha risolto i miei problemi della VM affermando 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 porta via dall'heap NATIVO .

Vedi qui: BitmapFactory OOM mi fa impazzire

Questo mi ha portato ad un altro thread di discussione in cui ho trovato un paio di altre soluzioni a questo problema. Uno è chiamare System.gc();manualmente dopo che l'immagine è stata visualizzata. Ma questo 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) consiste nell'utilizzare l'opzione BitmapFactory "inPurgeable". Quindi ho semplicemente aggiunto o2.inPurgeable=true;subito dopoo2.inSampleSize=scale; .

Maggiori informazioni su questo argomento qui: il limite di memoria ammonta solo a 6M?

Ora, detto tutto, sono un dunce 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 ora ho trovato impossibile eseguire la VM fuori dalla cache dell'heap. L'unico inconveniente che riesco a trovare è che stai cestinando l'immagine disegnata nella cache. Il che significa che se vai di nuovo a quell'immagine, la stai ridisegnando ogni volta. Nel caso di come funziona la mia applicazione, non è davvero un problema. Il tuo chilometraggio può variare.


Tutte le soluzioni qui richiedono l'impostazione di un IMAGE_MAX_SIZE. Questo limita i dispositivi con hardware più potente e se la dimensione dell'immagine è troppo bassa sembra 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'essenza di esso è calcolare la memoria massima allocata per l'app su un particolare dispositivo, quindi impostare la scala più bassa 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 per il 25% della memoria allocata massima, potrebbe essere necessario regolarla in base alle proprie esigenze e assicurarsi che questa bitmap sia pulita e che non resti in memoria al termine dell'utilizzo. In genere utilizzo questo codice per eseguire la rotazione dell'immagine (bitmap di origine e di destinazione), quindi la mia app deve caricare contemporaneamente 2 bitmap in memoria e il 25% mi dà un buon buffer senza esaurire la memoria durante l'esecuzione della rotazione dell'immagine.

Spero che questo aiuti qualcuno là fuori ..





android-bitmap