Problème de mémoire étrange lors du chargement d'une image dans un objet Bitmap

1290

J'ai une vue de liste avec quelques boutons d'image sur chaque ligne. Lorsque vous cliquez sur la ligne de liste, elle lance une nouvelle activité. J'ai dû créer mes propres onglets en raison d'un problème avec la disposition de la caméra. L'activité qui se lance pour le résultat est une carte. Si je clique sur mon bouton pour lancer l'aperçu de l'image (charger une image sur la carte SD), l'application retourne de l'activité à l' listviewactivité pour le gestionnaire de résultats pour relancer ma nouvelle activité qui n'est rien de plus qu'un widget d'image.

L'aperçu de l'image sur la vue de liste se fait avec le curseur et ListAdapter. Cela le rend assez simple, mais je ne sais pas comment je peux mettre une image redimensionnée (c'est-à-dire une taille de bit plus petite pas un pixel comme srcpour le bouton d'image à la volée. Je viens donc de redimensionner l'image qui est sortie de l'appareil photo du téléphone.

Le problème est que j'obtiens une erreur de mémoire insuffisante lorsqu'il essaie de revenir en arrière et de relancer la 2e activité.

  • Existe-t-il un moyen de créer facilement l'adaptateur de liste ligne par ligne, où je peux redimensionner à la volée (au niveau des bits )?

Ce serait préférable car je dois également apporter des modifications aux propriétés des widgets / éléments de chaque ligne car je ne peux pas sélectionner une ligne avec l'écran tactile en raison du problème de focus. ( Je peux utiliser le rollerball. )

  • Je sais que je peux faire un redimensionnement hors bande et enregistrer mon image, mais ce n'est pas vraiment ce que je veux faire, mais un exemple de code pour cela serait bien.

Dès que j'ai désactivé l'image sur la liste, cela a de nouveau fonctionné correctement.

FYI: Voici comment je le faisais:

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

R.id.imagefilenameest un ButtonImage.

Voici mon 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 

J'ai également une nouvelle erreur lors de l'affichage d'une image:

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
Chrispix
la source
8
J'ai résolu ce problème en évitant Bitmap.decodeStream ou decodeFile et en utilisant la méthode BitmapFactory.decodeFileDescriptor.
Fraggle
1
J'ai également rencontré un problème similaire il y a quelques semaines et je l'ai résolu en réduisant les images jusqu'à un point optimal. J'ai écrit une approche complète dans mon blog codingjunkiesforum.wordpress.com/2014/06/12/… et téléchargé un exemple de projet complet avec le code OOM prone vs le code OOM Proof sur http://github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat
5
La réponse acceptée sur cette question est en cours de discussion sur méta
rene
4
Cela se produit lorsque vous ne lisez pas les guides du développeur Android
Pedro Varela
2
Cela se produit en raison d'une mauvaise architecture Android. Il devrait redimensionner les images comme ios et UWP le fait. Je n'ai pas à faire ça moi-même. Les développeurs Android s'habituent à cet enfer et pensent que cela fonctionne comme il se doit.
Accès refusé

Réponses:

651

La classe de formation Android , " Affichage efficace des bitmaps ", offre d'excellentes informations pour comprendre et gérer l'exception java.lang.OutOfMemoryError: bitmap size exceeds VM budgetlors du chargement des bitmaps.


Lire les dimensions et le type de bitmap

La BitmapFactoryclasse fournit plusieurs méthodes de décodage ( decodeByteArray(), decodeFile(), decodeResource(), etc.) pour créer un à Bitmappartir de diverses sources. Choisissez la méthode de décodage la plus appropriée en fonction de votre source de données d'image. Ces méthodes tentent d'allouer de la mémoire pour le bitmap construit et peuvent donc facilement entraîner une OutOfMemoryexception. Chaque type de méthode de décodage a des signatures supplémentaires qui vous permettent de spécifier des options de décodage via la BitmapFactory.Optionsclasse. La définition de la inJustDecodeBoundspropriété truependant le décodage évite l'allocation de mémoire, le retour nullde l'objet bitmap mais le paramétrage outWidth, outHeightet outMimeType. Cette technique vous permet de lire les dimensions et le type des données d'image avant la construction (et l'allocation de mémoire) du 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;

Pour éviter les java.lang.OutOfMemoryexceptions, vérifiez les dimensions d'une image bitmap avant de la décoder, sauf si vous faites entièrement confiance à la source pour vous fournir des données d'image de taille prévisible qui tiennent confortablement dans la mémoire disponible.


Charger une version réduite dans la mémoire

Maintenant que les dimensions de l'image sont connues, elles peuvent être utilisées pour décider si l'image complète doit être chargée en mémoire ou si une version sous-échantillonnée doit être chargée à la place. Voici quelques facteurs à considérer:

  • Estimation de l'utilisation de la mémoire pour charger l'image complète en mémoire.
  • La quantité de mémoire que vous êtes prêt à consacrer au chargement de cette image, compte tenu des autres besoins en mémoire de votre application.
  • Dimensions du composant ImageView ou UI cible dans lequel l'image doit être chargée.
  • Taille d'écran et densité de l'appareil actuel.

Par exemple, cela ne vaut pas la peine de charger une image 1024x768 pixels dans la mémoire si elle sera finalement affichée dans une vignette 128x96 pixels dans un fichier ImageView.

Pour dire au décodeur de sous-échantillonner l'image, chargez une version plus petite en mémoire, définie inSampleSizesur truedans votre BitmapFactory.Optionsobjet. Par exemple, une image avec une résolution de 2048x1536 qui est décodée avec un inSampleSizede 4 produit un bitmap d'environ 512x384. Le chargement en mémoire utilise 0,75 Mo au lieu de 12 Mo pour l'image complète (en supposant une configuration bitmap de ARGB_8888). Voici une méthode pour calculer une valeur de taille d'échantillon qui est une puissance de deux en fonction d'une largeur et d'une hauteur cibles:

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

Remarque : Une puissance de deux valeurs est calculée car le décodeur utilise une valeur finale en arrondissant à la puissance de deux la plus proche, conformément à la inSampleSizedocumentation.

Pour utiliser cette méthode, décodez d'abord avec inJustDecodeBoundsset to true, passez les options, puis décodez à nouveau en utilisant la nouvelle inSampleSizevaleur et inJustDecodeBoundsdéfinissez sur 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);
}

Cette méthode permet de charger facilement un bitmap de taille arbitrairement grande dans un ImageViewqui affiche une miniature de 100 x 100 pixels, comme indiqué dans l'exemple de code suivant:

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

Vous pouvez suivre un processus similaire pour décoder les bitmaps d'autres sources, en remplaçant la BitmapFactory.decode*méthode appropriée si nécessaire.

Sazid
la source
21
Cette réponse est en cours de discussion sur meta
rene
9
Cette réponse (à l'exception des informations accessibles via le lien) n'offre pas beaucoup de solution quant à une réponse. Les parties importantes du lien devraient être fusionnées dans la question.
FallenAngel
7
Cette réponse, comme la question et les autres réponses, est le wiki de la communauté, c'est donc quelque chose que la communauté peut corriger en modifiant, quelque chose qui ne nécessite pas d'intervention du modérateur.
Martijn Pieters
le lien actuel vers le contenu et la prise en charge de Kotlin peut être trouvé à: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr
891

Pour corriger l'erreur OutOfMemory, vous devez faire quelque chose comme ceci:

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

Cette inSampleSizeoption réduit la consommation de mémoire.

Voici une méthode complète. Tout d'abord, il lit la taille de l'image sans décoder le contenu lui-même. Ensuite, il trouve la meilleure inSampleSizevaleur, ce devrait être une puissance de 2, et enfin l'image est décodée.

// 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;
}
Fedor
la source
31
Notez que 10 n'est peut-être pas la meilleure valeur pour inSampleSize, la documentation suggère d'utiliser des puissances de 2.
Mirko N.
70
Je suis confronté au même problème que Chrispix, mais je ne pense pas que la solution ici résout vraiment le problème, mais le contourne plutôt. La modification de la taille de l'échantillon réduit la quantité de mémoire utilisée (au détriment de la qualité de l'image, ce qui est probablement correct pour un aperçu d'image), mais cela n'empêchera pas l'exception si un flux d'images suffisamment grand est décodé, ou si plusieurs flux d'images sont décodé. Si je trouve une meilleure solution (et il n'y en a peut-être pas), je posterai une réponse ici.
Flynn81
4
Vous avez seulement besoin d'une taille appropriée pour correspondre à l'écran en densité de pixels, pour zoomer et vous pouvez ainsi prendre un échantillon de l'image à une densité plus élevée.
stealthcopter
4
REQUIRED_SIZE est la nouvelle taille que vous souhaitez mettre à l'échelle.
Fedor
8
cette solution m'a aidé mais la qualité d'image est terrible. J'utilise un viewfilpper pour afficher les images des suggestions?
user1106888
373

J'ai apporté une petite amélioration au code de Fedor. Il fait essentiellement la même chose, mais sans la boucle (à mon avis) moche tandis que cela donne toujours une puissance de deux. Félicitations à Fedor pour avoir créé la solution d'origine, j'ai été coincé jusqu'à ce que je trouve la sienne, puis j'ai pu faire celle-ci :)

 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;
}
Thomas Vervest
la source
40
Oui tu as raison alors que ce n'est pas si beau. J'ai juste essayé de le faire comprendre à tout le monde. Merci pour votre code.
Fedor
10
@Thomas Vervest - Il y a un gros problème avec ce code. ^ n'élève pas 2 à une puissance, il xors 2 avec le résultat. Vous voulez Math.pow (2.0, ...). Sinon, cela semble bon.
DougW
6
Ooh, c'est un très bon! Mon mauvais, je vais le corriger immédiatement, merci pour la réponse!
Thomas Vervest du
8
Vous créez deux nouveaux FileInputStreams, un pour chaque appel au BitmapFactory.decodeStream(). N'avez-vous pas à enregistrer une référence à chacun d'eux afin qu'ils puissent être fermés dans un finallybloc?
matsev
1
@Babibu La documentation n'indique pas que le flux est fermé pour vous, donc je suppose qu'il doit toujours être fermé. Une discussion intéressante et connexe peut être trouvée ici . Notez le commentaire d'Adrian Smith, qui se rapporte directement à notre débat.
Thomas Vervest
233

Je viens de l'expérience iOS et j'étais frustré de découvrir un problème avec quelque chose d'aussi basique que le chargement et l'affichage d'une image. Après tout, tous ceux qui rencontrent ce problème essaient d'afficher des images de taille raisonnable. Quoi qu'il en soit, voici les deux changements qui ont résolu mon problème (et rendu mon application très réactive).

1) Chaque fois que vous faites BitmapFactory.decodeXYZ(), assurez - vous de passer un BitmapFactory.Optionsavec inPurgeableensemble à true(et de préférence inInputShareableégalement réglé true).

2) NE JAMAIS utiliser Bitmap.createBitmap(width, height, Config.ARGB_8888). Je veux dire JAMAIS! Je n'ai jamais eu cette chose qui n'a pas soulevé d'erreur de mémoire après quelques passes. Aucun montant de recycle(), System.gc(), ce qui a aidé. Cela a toujours soulevé une exception. Une autre façon qui fonctionne réellement est d'avoir une image factice dans vos dessinables (ou une autre Bitmap que vous avez décodée en utilisant l'étape 1 ci-dessus), de la redimensionner à tout ce que vous voulez, puis de manipuler la Bitmap résultante (telle que la transmettre à un canevas) pour plus de plaisir). Donc, ce que vous devez utiliser à la place: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Si pour une raison quelconque, vous DEVEZ utiliser la méthode de création de force brute, passez au moins Config.ARGB_4444.

C'est presque garanti pour vous faire gagner des heures, voire des jours. Tout ce qui parle de redimensionner l'image, etc. ne fonctionne pas vraiment (sauf si vous envisagez une mauvaise taille ou une image dégradée une solution).

Ephraim
la source
22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;et Bitmap.createScaledBitmap(srcBitmap, width, height, false);résolu mon problème que j'avais avec une exception de mémoire insuffisante sur Android 4.0.0. Merci mon pote!
Jan-Terje Sørensen
5
Dans l'appel Bitmap.createScaledBitmap (), vous devez probablement utiliser true comme paramètre indicateur. Sinon, la qualité de l'image ne sera pas lisse lors de la mise à l'échelle. Vérifiez ce thread stackoverflow.com/questions/2895065/…
rOrlig
11
C'est vraiment un conseil fabuleux. J'aimerais pouvoir vous donner un +1 supplémentaire pour avoir pris Google à partie pour ce bug de dink incroyablement rinky. Je veux dire ... si ce n'est pas un bogue, alors la documentation doit vraiment avoir des enseignes au néon clignotant sérieusement disant "C'EST COMMENT VOUS TRAITEZ LES PHOTOS", car je lutte avec cela depuis 2 ans et je viens de trouver cet article. Super trouvaille.
Yevgeny Simkin
La réduction de la taille de vos images aide certainement, mais c'est une étape importante et ce qui a finalement résolu ce problème pour moi. Le problème avec la simple mise à l'échelle de vos images est que si vous en avez beaucoup ou si les images source sont très grandes, vous pouvez toujours rencontrer le même problème. +1 à vous Ephraïm.
Dave
10
À partir de Lollipop, BitmapFactory.Options.inPurgeableet BitmapFactory.Options.inInputShareablesont obsolètes developer.android.com/reference/android/graphics/…
Denis Kniazhev
93

C'est un bug connu , ce n'est pas à cause de gros fichiers. Depuis Android met en cache les Drawables, il manque de mémoire après avoir utilisé peu d'images. Mais j'ai trouvé un autre moyen pour cela, en ignorant le système de cache par défaut Android.

Solution : déplacez les images vers le dossier "assets" et utilisez la fonction suivante pour obtenir 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);
}
Anto Binish Kaspar
la source
79

J'ai eu ce même problème et l'ai résolu en évitant les fonctions BitmapFactory.decodeStream ou decodeFile et utilisé à la place BitmapFactory.decodeFileDescriptor

decodeFileDescriptor semble qu'il appelle différentes méthodes natives que decodeStream / decodeFile.

Quoi qu'il en soit, ce qui a fonctionné était le suivant (notez que j'ai ajouté certaines options comme certaines l'ont fait ci-dessus, mais ce n'est pas ce qui a fait la différence. Ce qui est critique est l'appel à BitmapFactory.decodeFileDescriptor au lieu de decodeStream ou 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;                        
}

Je pense qu'il y a un problème avec la fonction native utilisée dans decodeStream / decodeFile. J'ai confirmé qu'une méthode native différente est appelée lors de l'utilisation de decodeFileDescriptor. De plus, ce que j'ai lu est "que les images (bitmaps) ne sont pas allouées de manière Java standard mais via des appels natifs; les allocations se font en dehors du tas virtuel, mais sont prises en compte! "

Fraggle
la source
1
même résultat hors de la mémoire, en fait, peu importe la méthode que vous utilisez, cela dépend du nombre d'octets que vous détenez pour lire les données qui donnent de la mémoire.
PiyushMishra
72

Je pense que la meilleure façon d'éviter cela OutOfMemoryErrorest d'y faire face et de le comprendre.

J'ai créé une application pour provoquer intentionnellement OutOfMemoryErroret surveiller l'utilisation de la mémoire.

Après avoir fait beaucoup d'expériences avec cette application, j'ai les conclusions suivantes:

Je vais d'abord parler des versions du SDK avant Honey Comb.

  1. Bitmap est stocké dans un tas natif, mais il récupérera automatiquement les ordures, appeler recycle () est inutile.

  2. Si {taille de segment de machine virtuelle} + {mémoire de segment de mémoire native allouée}> = {limite de taille de segment de mémoire virtuelle pour le périphérique} et que vous essayez de créer un bitmap, un MOO sera lancé.

    AVIS: VM HEAP SIZE est compté plutôt que VM ALLOCATED MEMORY.

  3. La taille du tas de VM ne diminuera jamais après sa croissance, même si la mémoire de VM allouée est réduite.

  4. Vous devez donc garder la mémoire maximale de la machine virtuelle aussi faible que possible pour empêcher la taille du tas de machine virtuelle de devenir trop grande pour économiser la mémoire disponible pour les bitmaps.

  5. Appeler manuellement System.gc () n'a aucun sens, le système l'appellera d'abord avant d'essayer d'augmenter la taille du tas.

  6. La taille de segment de mémoire native ne diminuera jamais trop, mais elle n'est pas prise en compte pour le MOO, donc pas besoin de s'en inquiéter.

Ensuite, parlons du SDK Starts de Honey Comb.

  1. Le bitmap est stocké dans le tas VM, la mémoire native n'est pas comptée pour le MOO.

  2. La condition pour le MOO est beaucoup plus simple: {taille de tas VM}> = {limite de taille de tas VM pour le périphérique}.

  3. Donc, vous avez plus de mémoire disponible pour créer un bitmap avec la même limite de taille de tas, le MOO est moins susceptible d'être levé.

Voici quelques-unes de mes observations sur la collecte des déchets et les fuites de mémoire.

Vous pouvez le voir vous-même dans l'application. Si une activité a exécuté une tâche AsyncTask qui était toujours en cours d'exécution après la destruction de l'activité, l'activité ne sera pas récupérée à la fin de la tâche AsyncTask.

En effet, AsyncTask est une instance d'une classe interne anonyme, il contient une référence de l'activité.

L'appel de AsyncTask.cancel (true) n'arrêtera pas l'exécution si la tâche est bloquée dans une opération d'E / S dans le thread d'arrière-plan.

Les rappels sont également des classes internes anonymes, donc si une instance statique de votre projet les contient et ne les libère pas, la mémoire serait perdue.

Si vous avez planifié une tâche répétée ou retardée, par exemple une minuterie, et que vous n'appelez pas cancel () et purge () dans onPause (), la mémoire serait perdue.

co-good
la source
AsyncTask ne doit pas nécessairement être "une instance d'une classe interne anonyme", et il en va de même pour les Callbackks. Vous pouvez créer une nouvelle classe publique dans son propre fichier qui étend AsyncTask, ou même une private static classdans la même classe. Ils ne contiendront aucune référence à l'activité (à moins que vous ne leur en donniez un bien sûr)
Simon Forsberg
65

J'ai récemment vu beaucoup de questions sur les exceptions OOM et la mise en cache. Le guide du développeur a un très bon article à ce sujet, mais certains échouent généralement à l'implémenter de manière appropriée.

Pour cette raison, j'ai écrit un exemple d'application qui illustre la mise en cache dans un environnement Android. Cette implémentation n'a pas encore obtenu de MOO.

Regardez à la fin de cette réponse pour un lien vers le code source.

Exigences:

  • Android API 2.1 ou supérieur (je n'arrivais tout simplement pas à obtenir la mémoire disponible pour une application dans l'API 1.6 - c'est le seul morceau de code qui ne fonctionne pas dans l'API 1.6)
  • Package de support Android

Capture d'écran

Fonctionnalités:

  • Conserve le cache en cas de changement d'orientation , à l'aide d'un singleton
  • Utilisez un huitième de la mémoire d'application attribuée au cache (modifiez si vous le souhaitez)
  • Les grands bitmaps sont mis à l'échelle (vous pouvez définir le nombre maximal de pixels que vous souhaitez autoriser)
  • Contrôle qu'une connexion Internet est disponible avant de télécharger les bitmaps
  • S'assure que vous n'instanciez qu'une seule tâche par ligne
  • Si vous Jetant l' ListViewécart, il ne sera pas tout simplement télécharger les bitmaps entre

Cela ne comprend pas:

  • Mise en cache du disque. Cela devrait être facile à implémenter de toute façon - pointez simplement sur une tâche différente qui récupère les bitmaps du disque

Exemple de code:

Les images téléchargées sont des images (75 x 75) de Flickr. Cependant, mettez toutes les URL d'image que vous souhaitez traiter, et l'application les réduira si elles dépassent le maximum. Dans cette application, les URL sont simplement dans unString tableau.

Le LruCachea un bon moyen de gérer les bitmaps. Cependant, dans cette application, je mets une instance d'une LruCachedans une autre classe de cache que j'ai créée afin de rendre l'application plus faisable.

Les trucs critiques de Cache.java (la loadBitmap()méthode est la plus 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();
        }
    } 
}

Vous ne devez pas modifier quoi que ce soit dans le fichier Cache.java, sauf si vous souhaitez implémenter la mise en cache du disque.

Les éléments critiques de 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()est appelé très souvent. Normalement, ce n'est pas une bonne idée de télécharger des images là-bas si nous n'avons pas mis en œuvre une vérification qui nous assure que nous ne démarrerons pas une quantité infinie de threads par ligne. Cache.java vérifie si le rowObject.mBitmapUrldéjà est dans une tâche et s'il l'est, il n'en commencera pas une autre. Par conséquent, nous ne dépassons probablement pas la restriction de file d'attente de travailAsyncTask pool.

Télécharger:

Vous pouvez télécharger le code source depuis https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip .


Derniers mots:

J'ai testé cela depuis quelques semaines maintenant, je n'ai pas encore obtenu une seule exception OOM. J'ai testé cela sur l'émulateur, sur mon Nexus One et sur mon Nexus S. J'ai testé des URL d'images contenant des images en qualité HD. Le seul goulot d'étranglement est qu'il prend plus de temps à télécharger.

Il n'y a qu'un seul scénario possible où j'imagine que le MOO apparaîtra, et c'est si nous téléchargeons beaucoup, de très grandes images, et avant qu'elles ne soient mises à l'échelle et mises en cache, prennent simultanément plus de mémoire et provoquent un MOO. Mais ce n'est même pas une situation idéale de toute façon et il ne sera probablement pas possible de résoudre de manière plus réalisable.

Signaler des erreurs dans les commentaires! :-)

Wroclai
la source
43

J'ai fait ce qui suit pour prendre l'image et la redimensionner à la volée. J'espère que cela t'aides

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
Chrispix
la source
26
Cette approche met à l'échelle le bitmap. Mais cela ne résout pas le problème OutOfMemory car le bitmap complet est de toute façon décodé.
Fedor
5
Je vais voir si je peux regarder mon ancien code, mais je pense qu'il a résolu mes problèmes de mémoire insuffisante. Va vérifier mon ancien code.
Chrispix
2
Dans cet exemple au moins, il semble que vous ne gardiez pas la référence au bitmap complet, d'où les économies de mémoire.
NoBugs
Pour moi, cela a résolu le problème de mémoire, mais a réduit la qualité des images.
Pamela Sillah
35

Il semble que ce soit un problème très long, avec beaucoup d'explications différentes. J'ai suivi les conseils des deux réponses présentées les plus courantes ici, mais aucune de celles-ci n'a résolu mes problèmes de machine virtuelle prétendant qu'elle ne pouvait pas se permettre les octets pour effectuer la partie de décodage du processus. Après avoir creusé, j'ai appris que le vrai problème ici est le processus de décodage qui enlève le tas NATIVE .

Voir ici: BitmapFactory OOM me rend fou

Cela m'a amené à un autre fil de discussion où j'ai trouvé quelques solutions supplémentaires à ce problème. L'une consiste à appeler System.gc();manuellement après l'affichage de votre image. Mais cela fait en fait que votre application utilise PLUS de mémoire, dans un effort pour réduire le tas natif. La meilleure solution à partir de la sortie de 2.0 (Donut) est d'utiliser l'option BitmapFactory "inPurgeable". J'ai donc simplement ajouté o2.inPurgeable=true;juste après o2.inSampleSize=scale;.

Plus d'informations sur ce sujet ici: la limite de la mémoire n'est-elle que de 6 Mo?

Maintenant, après avoir dit tout cela, je suis aussi un cancre complet avec Java et Android. Donc, si vous pensez que c'est une façon terrible de résoudre ce problème, vous avez probablement raison. ;-) Mais cela a fait des merveilles pour moi, et j'ai trouvé impossible d'exécuter la machine virtuelle hors du cache de tas maintenant. Le seul inconvénient que je peux trouver est que vous jetez votre image dessinée en cache. Ce qui signifie que si vous revenez à DROITE sur cette image, vous la redessinez à chaque fois. Dans le cas du fonctionnement de mon application, ce n'est pas vraiment un problème. Votre kilométrage peut varier.

RayHaque
la source
MOO fixe inPurgeable pour moi.
Artem Russakovskii
35

malheureusement, si aucun des éléments ci-dessus ne fonctionne, ajoutez-le à votre fichier manifeste . Balise d' application interne

 <application
         android:largeHeap="true"
Himanshu Mori
la source
1
Pouvez-vous expliquer ce que cela fait réellement? Dire simplement aux gens d'ajouter cela n'aide pas.
Rabbin furtif
1
C'est une très mauvaise solution. Fondamentalement, vous n'essayez pas de résoudre le problème. Demandez plutôt au système Android d'allouer plus d'espace de stockage à votre application. Cela aura de très mauvaises implications sur votre application, comme votre application consommant beaucoup d'énergie de la batterie, car le GC doit parcourir un grand espace de mémoire pour nettoyer la mémoire et les performances de votre application seront plus lentes.
Prakash
2
alors pourquoi android nous autorise-t-il à ajouter cet android: largeHeap = "true" dans notre manifeste? Maintenant, vous défiez Android.
Himanshu Mori
32

Utiliser ceci bitmap.recycle();Cela aide sans aucun problème de qualité d'image.

Arsalan
la source
9
Selon l'API, l'appel à recycle () n'est pas nécessaire.
Artem Russakovskii
28

J'ai une solution beaucoup plus efficace qui n'a besoin d'aucune mise à l'échelle. Décodez simplement votre bitmap une seule fois, puis mettez-le en cache dans une carte par rapport à son nom. Ensuite, récupérez simplement le bitmap par rapport au nom et définissez-le dans ImageView. Il n'y a plus rien à faire.

Cela fonctionnera car les données binaires réelles du bitmap décodé ne sont pas stockées dans le tas VM dalvik. Il est stocké en externe. Ainsi, chaque fois que vous décodez un bitmap, il alloue de la mémoire en dehors du tas VM qui n'est jamais récupéré par GC

Pour vous aider à mieux apprécier cela, imaginez que vous avez conservé votre image dans le dossier dessinable. Vous obtenez simplement l'image en faisant un getResources (). GetDrwable (R.drawable.). Cela ne décode PAS votre image à chaque fois, mais réutilise une instance déjà décodée chaque fois que vous l'appelez. Donc, en substance, il est mis en cache.

Maintenant que votre image est dans un fichier quelque part (ou peut même provenir d'un serveur externe), il est de VOTRE responsabilité de mettre en cache l'instance bitmap décodée pour la réutiliser où elle est nécessaire.

J'espère que cela t'aides.

Parth Mehta
la source
4
"puis le mettre en cache dans une carte par rapport à son nom." Comment cachez-vous exactement vos images?
Vincent
3
Avez-vous réellement essayé cela? Même si les données de pixels ne sont pas réellement stockées dans le tas Dalvik, leur taille dans la mémoire native est signalée à la machine virtuelle et comptabilisée dans sa mémoire disponible.
ErikR
3
@Vincent Je pense que ce n'est pas difficile de les stocker dans une carte. Je suggérerais quelque chose comme la carte HashMap <KEY, Bitmap>, où la clé peut être une chaîne de la source ou tout ce qui a du sens pour vous. Supposons que vous preniez un chemin en tant que KEY, que vous le stockiez sous map.put (Path, Bitmap) et que vous le receviez via map.get (Path)
Rafael T
3
vous voudrez probablement utiliser HashMap <String, SoftReference <Bitmap>> si vous implémentez un cache d'image sinon vous risquez de manquer de mémoire de toute façon - je ne pense pas non plus qu'il "alloue de la mémoire en dehors du tas VM qui n'est jamais récupéré par GC "est vrai, la mémoire est récupérée, si je comprends bien, peut être un retard, à quoi sert bitmap.recycle (), comme un indice pour récupérer le mem tôt ...
Dori
28

J'ai résolu le même problème de la manière suivante.

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);
Prerna
la source
c'est très bien mais j'utilise plusieurs bitmap pour dessiner un cercle dans OnCreate et l'appel d'activité 4-5 fois alors comment effacer le bitmap et comment supprimer le bitmap et recréer pour la deuxième fois lorsque l'activité 0nCreate ..
ckpatel
27

Il y a deux problèmes ici ...

  • La mémoire bitmap n'est pas dans le tas de machine virtuelle mais plutôt dans le tas natif - voir BitmapFactory OOM me rend fou
  • La récupération de place pour le tas natif est plus paresseuse que le tas de machine virtuelle - vous devez donc être assez agressif pour faire bitmap.recycle et bitmap = null chaque fois que vous passez par onPause ou onDestroy d'une activité
Torid
la source
Il est dans le tas de VM depuis Android 2.3+
FindOut_Quran
27

Cela a fonctionné pour moi!

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());
    }
}
Luke Taylor
la source
20

Aucune des réponses ci-dessus n'a fonctionné pour moi, mais j'ai trouvé une solution de contournement horrible qui a résolu le problème. J'ai ajouté une très petite image de 1 x 1 pixel à mon projet en tant que ressource et je l'ai chargée dans mon ImageView avant d'appeler dans la récupération de place. Je pense qu'il se pourrait que l'ImageView ne libère pas le Bitmap, donc GC ne l'a jamais repris. C'est moche, mais ça semble marcher pour l'instant.

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.
Mike
la source
ressemble à imageView ne recycle pas vraiment le bitmap par lui-même. M'a aidé, merci
Dmitry Zaytsev
@Mike pouvez-vous ajouter le code complet de imageloader ou bien pouvez-vous me donner le lien de chargement des images bitmap. Si j'utilise recycler sur bitmap, toute ma vue de liste est affichée mais tous les éléments sont vides
TNR
@ Mike peut vous dire si je fais imageView = null avant d'appeler dans la collecte des ordures est-ce bon ou non?
Youddh
@TNR Je pense que ce que vous êtes absent ici est que bitmapdans le code ci - dessus est l'image déjà affichée précédente, vous devez le recycler, effacer toute référence à elle, faire l' imageViewoublier et en définissant un petit remplacement gc(); et après tout cela: chargez votre NOUVELLE image bitmapet affichez-la, ...dans le code ci-dessus.
TWiStErRob
C'est faux. Vous devez toujours effacer votre contenu imageView AVANT de recycler le bitmap (plutôt que pendant qu'il est réellement affiché et utilisé).
FindOut_Quran
20

Excellentes réponses ici, mais je voulais un cours entièrement utilisable pour résoudre ce problème .. alors j'en ai fait une.

Voici ma classe BitmapHelper qui est à l'épreuve de 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;
    }

}
Pascal
la source
Pour tous ceux qui utilisent ceci: je viens de corriger un bug: "int scaledBitmapHeight = scaledBitmap.getWidth ();" est faux (évidemment. Je l'ai remplacé par "int scaledBitmapHeight = scaledBitmap.getHeight ();"
Pascal
19

Cela fonctionne pour moi.

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

et c'est sur monodroid C #. vous pouvez facilement changer le chemin de l'image. ce qui importe ici, ce sont les options à définir.

Dobermaxx99
la source
16

Cela semble être l'endroit approprié pour partager ma classe d'utilité pour le chargement et le traitement d'images avec la communauté, vous êtes invités à l'utiliser et à la modifier librement.

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;
    }
}
Emil Davtyan
la source
16

Dans une de mes applications, je dois prendre une photo de Camera/Gallery . Si l'utilisateur clique sur l'image de la caméra (peut être 2MP, 5MP ou 8MP), la taille de l'image varie de kBs à MBs. Si la taille de l'image est inférieure (ou jusqu'à 1-2 Mo) au-dessus du code, cela fonctionne bien, mais si j'ai une image de taille supérieure à 4 Mo ou 5 Mo, alorsOOM vient dans le cadre :(

alors j'ai travaillé pour résoudre ce problème et enfin j'ai apporté l'amélioration ci-dessous au code de Fedor (All Credit to Fedor pour avoir fait une si belle solution) :)

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;

}

J'espère que cela aidera les copains confrontés au même problème!

pour plus s'il vous plaît se référer à ceci

Rupesh Yadav
la source
14

Je viens de tomber sur ce problème il y a quelques minutes. Je l'ai résolu en faisant un meilleur travail de gestion de mon adaptateur listview. Je pensais que c'était un problème avec les centaines d'images 50x50px que j'utilisais, il s'avère que j'essayais de gonfler ma vue personnalisée chaque fois que la ligne était affichée. Simplement en testant pour voir si la ligne a été gonflée, j'ai éliminé cette erreur et j'utilise des centaines de bitmaps. Il s'agit en fait d'un Spinner, mais l'adaptateur de base fonctionne tout de même pour un ListView. Cette solution simple a également considérablement amélioré les performances de l'adaptateur.

@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);
    }
...
BajaBob
la source
3
Je ne vous en remercierai jamais assez! Je cherchais le mauvais problème avant de voir ça. Question pour vous cependant: comme chaque ligne de ma liste a un nom et une photo uniques, j'ai dû utiliser un tableau convertView pour conserver chacune des valeurs des lignes. Je ne pouvais pas voir comment l'utilisation d'une seule variable vous permettrait de le faire. Suis-je en train de manquer quelque chose?
PeteH
13

J'ai passé toute la journée à tester ces solutions et la seule chose qui a fonctionné pour moi est les approches ci-dessus pour obtenir l'image et appeler manuellement le GC, ce que je sais n'est pas censé être nécessaire, mais c'est la seule chose qui a fonctionné lorsque je soumets mon application à des tests de charge élevée pour basculer entre les activités. Mon application a une liste d'images miniatures dans une vue de liste (disons l'activité A) et lorsque vous cliquez sur l'une de ces images, elle vous amène à une autre activité (disons l'activité B) qui montre une image principale pour cet élément. Lorsque je basculais entre les deux activités, j'obtenais finalement l'erreur OOM et l'application se fermait de force.

Lorsque j'arrivais à mi-chemin de la liste, cela plantait.

Maintenant, lorsque j'implémente ce qui suit dans l'activité B, je peux parcourir l'intégralité de la liste sans problème et continuer et continuer et continuer ... et c'est très rapide.

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
Jesse
la source
Aimez votre solution! J'ai aussi passé des heures à résoudre ce bogue, tellement ennuyeux! Edit: Malheureusement, le problème persiste quand je change l'orientation de mon écran en mode paysage ...
Xarialon
Cela m'a finalement aidé avec: - Options BitmapFactory.Options = nouveau BitmapFactory.Options (); options.InPurgeable = true; options.InSampleSize = 2;
user3833732
13

Ce problème se produit uniquement dans les émulateurs Android. J'ai également rencontré ce problème dans un émulateur, mais lorsque j'ai vérifié un appareil, cela a bien fonctionné.

Veuillez donc enregistrer un appareil. Il peut être exécuté dans l'appareil.

hackjutsu
la source
12

Mes 2 cents: j'ai résolu mes erreurs OOM avec des bitmaps en:

a) redimensionner mes images par un facteur 2

b) en utilisant la bibliothèque Picasso dans mon adaptateur personnalisé pour un ListView, avec un appel dans getView comme ceci:Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

matsoftware
la source
Je suis heureux que vous ayez mentionné Picasso, car cela facilite le chargement des images. Particulièrement stockés à distance.
Chrispix
12

utilisez ce code pour chaque image dans la sélection de SdCard ou dessiné pour convertir un objet 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;

utilisez votre chemin d'image instend ImageData_Path.get (img_pos) .getPath () .

Gaurav Pansheriya
la source
12

En règle générale, la taille du tas de l'appareil Android n'est que de 16 Mo (varie selon l'appareil / le système d'exploitation, voir les tailles de tas après ), si vous chargez les images et qu'il dépasse la taille de 16 Mo, cela provoquera une exception de mémoire, au lieu d'utiliser le bitmap pour le chargement les images de la carte SD ou des ressources ou même du réseau essaient d'utiliser getImageUri , le chargement du bitmap nécessite plus de mémoire, ou vous pouvez définir bitmap sur null si votre travail est fait avec ce bitmap.

Rohit Sharma
la source
1
Et si setImageURI obtient toujours une exception, reportez-vous à ce stackoverflow.com/questions/15377186/…
Mahesh
11

Toutes les solutions ici nécessitent la définition d'un IMAGE_MAX_SIZE. Cela limite les appareils avec un matériel plus puissant et si la taille de l'image est trop faible, elle semble moche sur l'écran HD.

Je suis sorti avec une solution qui fonctionne avec mon Samsung Galaxy S3 et plusieurs autres appareils, y compris des appareils moins puissants, avec une meilleure qualité d'image lorsqu'un appareil plus puissant est utilisé.

L'essentiel est de calculer la mémoire maximale allouée à l'application sur un appareil particulier, puis de définir l'échelle pour qu'elle soit la plus basse possible sans dépasser cette mémoire. Voici le code:

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

J'ai défini la mémoire maximale utilisée par ce bitmap à 25% de la mémoire maximale allouée, vous devrez peut-être l'ajuster à vos besoins et vous assurer que ce bitmap est nettoyé et ne reste pas en mémoire lorsque vous avez fini de l'utiliser. En règle générale, j'utilise ce code pour effectuer une rotation d'image (bitmap source et destination), mon application doit donc charger 2 bitmaps en mémoire en même temps, et 25% me donne un bon tampon sans manquer de mémoire lors de la rotation d'image.

J'espère que cela aide quelqu'un là-bas ..

Bruce
la source
11

Cela OutofMemoryExceptionne peut pas être totalement résolu en appelant le System.gc()etc.

En se référant au cycle de vie de l' activité

Les états d'activité sont déterminés par le système d'exploitation lui-même en fonction de l'utilisation de la mémoire pour chaque processus et de la priorité de chaque processus.

Vous pouvez considérer la taille et la résolution de chacune des images bitmap utilisées. Je recommande de réduire la taille, de rééchantillonner à une résolution inférieure, de se référer à la conception des galeries (une petite image PNG et une image originale.)

Raju Gujarati
la source
11

Ce code aidera à charger une grande image bitmap à partir de drawable

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;
    }
}
vigne
la source
Bien, pensez-vous qu'il serait préférable d'utiliser un chargeur au lieu d'une tâche asynchrone?
Chrispix
Et alors Bitmap.Config.ARGB_565? Si la haute qualité n'est pas critique.
Hamzeh Soboh