37 votes

Suggestions pour éviter l'erreur "Out of Memory" des bitmaps

Je travaille sur une application Android. L'application a une vue contenant beaucoup d'images. J'ai eu une erreur, je vais essayer de donner autant d'informations que possible en espérant que quelqu'un puisse me donner des suggestions.

L'application fonctionnait très bien sur tous les tests locaux. Cependant, j'ai reçu de nombreux plantages de la part des utilisateurs : java.lang.OutOfMemoryError: bitmap size exceeds VM budget

Voici la trace de la pile

0       java.lang.OutOfMemoryError: bitmap size exceeds VM budget
1   at  android.graphics.Bitmap.nativeCreate(Native Method)
2   at  android.graphics.Bitmap.createBitmap(Bitmap.java:507)
3   at  android.graphics.Bitmap.createBitmap(Bitmap.java:474)
4   at  android.graphics.Bitmap.createScaledBitmap(Bitmap.java:379)
5   at  android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498)
6   at  android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473)
7   at  android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
8   at  android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:359)
9   at  android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:385)

Mon plus gros problème est que je n'ai pas été en mesure de reproduire le problème localement, même sur des appareils anciens.

J'ai mis en œuvre de nombreuses mesures pour tenter de résoudre ce problème :

  1. Pas de fuites de mémoire : Je me suis assuré qu'il n'y a pas de fuite de mémoire du tout. J'ai supprimé les vues lorsque je n'en ai pas besoin. J'ai également recyclé tous les bitmaps et vérifié que le garbage collector fonctionne comme il le devrait. Et j'ai implémenté toutes les étapes nécessaires dans le onDestroy() méthode
  2. Mise à l'échelle correcte de la taille de l'image : Avant d'obtenir l'image, j'obtiens ses dimensions et je calcule la taille de l'image. inSampleSize .
  3. Taille du tas : Je détecte également la taille maximale du Heap avant de récupérer l'image et m'assure qu'il y a suffisamment d'espace. S'il n'y en a pas assez, je redimensionne l'image en conséquence.

Code pour calculer le inSampleSize correct

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)
      {
         if(width > height)
         {
            inSampleSize = Math.round((float) height / (float) reqHeight);
         }
         else
         {
            inSampleSize = Math.round((float) width / (float) reqWidth);
         }
      }
      return inSampleSize;
   }

Code pour obtenir le bitmap

    // decodes image and scales it to reduce memory consumption
   private static Bitmap decodeFile(File file, int newWidth, int newHeight)
   {// target size
      try
      {

         Bitmap bmp = MediaStore.Images.Media.getBitmap(getContext().getContentResolver(), Uri.fromFile(file));
         if(bmp == null)
         {
            // avoid concurrence
            // Decode image size
            BitmapFactory.Options option = new BitmapFactory.Options();

            // option = getBitmapOutput(file);

            option.inDensity = res.getDisplayMetrics().densityDpi < DisplayMetrics.DENSITY_HIGH ? 120 : 240;
            option.inTargetDensity = res.getDisplayMetrics().densityDpi;

            if(newHeight > 0 && newWidth > 0) 
                option.inSampleSize = calculateInSampleSize(option, newWidth, newWidth);

            option.inJustDecodeBounds = false;
            byte[] decodeBuffer = new byte[12 * 1024];
            option.inTempStorage = decodeBuffer;
            option.inPurgeable = true;
            option.inInputShareable = true;
            option.inScaled = true;

            bmp = BitmapFactory.decodeStream(new FileInputStream(file), null, option);
            if(bmp == null)
            {
               return null;
            }

         }
         else
         {
            int inDensity = res.getDisplayMetrics().densityDpi < DisplayMetrics.DENSITY_HIGH ? 120 : 240;
            int inTargetDensity = res.getDisplayMetrics().densityDpi;
            if(inDensity != inTargetDensity)
            {
               int newBmpWidth = (bmp.getWidth() * inTargetDensity) / inDensity;
               int newBmpHeight = (bmp.getHeight() * inTargetDensity) / inDensity;
               bmp = Bitmap.createScaledBitmap(bmp, newBmpWidth, newBmpHeight, true);
            }
         }

         return bmp;
      }
      catch(Exception e)
      {
         Log.e("Error calling Application.decodeFile Method params: " + Arrays.toString(new Object[]{file }), e);
      }
      return null;
   }

Code pour calculer la taille de l'image en fonction de la taille du tas pour les appareils plus anciens

private void calculateImagesSize()
   {
      // only for android older than HoneyComb that does not support large heap
      if(Build.VERSION.SDK_INT < Constants.HONEYCOMB)
      {
         long maxHeapSize = Runtime.getRuntime().maxMemory();
         long maxImageHeap = maxHeapSize - 10485760;
         if(Application.getResource().getDisplayMetrics().densityDpi >= DisplayMetrics.DENSITY_XHIGH)
         {
            maxImageHeap -= 12 * 1048576;
         }
         if(maxImageHeap < (30 * 1048576))
         {
            int screenHeight = Math.min(Application.getResource().getDisplayMetrics().heightPixels, Application.getResource()
               .getDisplayMetrics().widthPixels);
            long maxImageSize = maxImageHeap / 100;
            long maxPixels = maxImageSize / 4;
            long maxHeight = (long) Math.sqrt(maxPixels / 1.5);
            if(maxHeight < screenHeight)
            {
               drawableHeight = (int) maxHeight;
               drawableWidth = (int) (drawableHeight * 1.5);
            }
         }
      }
   }

Je pense que le problème vient du Heap, peut-être que parfois le système d'exploitation ne permet pas à l'application d'utiliser la taille maximale du Heap. Le plus gros problème est que je n'ai pas été en mesure de reproduire le problème, donc quand j'essaie un correctif, je dois attendre un peu pour voir si les utilisateurs obtiennent toujours l'erreur.

Que puis-je faire de plus pour éviter les problèmes de manque de mémoire ? Toute suggestion serait grandement appréciée. Merci beaucoup.

8voto

Mehul Ranpara Points 2496

Il suffit d'utiliser cette fonction pour décoder ... c'est la solution parfaite pour votre erreur ... parce que j'ai également obtenu la même erreur et j'ai obtenu cette solution ...

public static Bitmap decodeFile(File f,int WIDTH,int HIGHT){
     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_WIDTH=WIDTH;
         final int REQUIRED_HIGHT=HIGHT;
         //Find the correct scale value. It should be the power of 2.
         int scale=1;
         while(o.outWidth/scale/2>=REQUIRED_WIDTH && o.outHeight/scale/2>=REQUIRED_HIGHT)
             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;
 }

4voto

Vino Points 3249

En réduisant la taille de l'image, vous pouvez vous débarrasser de l'exception "Out of Memory", Essayez ceci

  BitmapFactory.Options options = new BitmapFactory.Options();
  options.inSampleSize = 6; 
  Bitmap receipt = BitmapFactory.decodeFile(photo.toString(),options);  //From File You can customise on your needs.

4voto

itsrajesh4uguys Points 2309

Bonjour, vous devez décoder le fichier. Pour cela, essayez la méthode suivante.

  public static Bitmap new_decode(File f) {

        // decode image size

        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        o.inDither = false; // Disable Dithering mode

        o.inPurgeable = true; // Tell to gc that whether it needs free memory,
                                // the Bitmap can be cleared

        o.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
        try {
            BitmapFactory.decodeStream(new FileInputStream(f), null, o);
        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        // Find the correct scale value. It should be the power of 2.
        final int REQUIRED_SIZE = 300;
        int width_tmp = o.outWidth, height_tmp = o.outHeight;
        int scale = 1;
        while (true) {
            if (width_tmp / 1.5 < REQUIRED_SIZE && height_tmp / 1.5 < REQUIRED_SIZE)
                break;
            width_tmp /= 1.5;
            height_tmp /= 1.5;
            scale *= 1.5;
        }

        // decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        // o2.inSampleSize=scale;
        o.inDither = false; // Disable Dithering mode

        o.inPurgeable = true; // Tell to gc that whether it needs free memory,
                                // the Bitmap can be cleared

        o.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
        // return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
        try {

//          return BitmapFactory.decodeStream(new FileInputStream(f), null,
//                  null);
            Bitmap bitmap= BitmapFactory.decodeStream(new FileInputStream(f), null, null);
            System.out.println(" IW " + width_tmp);
            System.out.println("IHH " + height_tmp);           
               int iW = width_tmp;
                int iH = height_tmp;

               return Bitmap.createScaledBitmap(bitmap, iW, iH, true);

        } catch (OutOfMemoryError e) {
            // TODO: handle exception
            e.printStackTrace();
            // clearCache();

            // System.out.println("bitmap creating success");
            System.gc();
            return null;
            // System.runFinalization();
            // Runtime.getRuntime().gc();
            // System.gc();
            // decodeFile(f);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }

    }

0voto

Paulo Cheque Points 335

J'ai rédigé un résumé des suggestions dans une autre question de StackOverFlow : Android : BitmapFactory.decodeStream() hors de la mémoire avec un fichier de 400KB et 2MB de tas libre

0voto

BadAttemtsFirst Points 345

En fait, le problème vient de l'OS de développement. Dans Android, contrairement à iOS, les gens de Google développent cela en fonction de la résolution de l'appareil photo. Les bitmaps occupent beaucoup de mémoire, surtout pour les images riches comme les photographies. différents appareils photo capturent des images avec différents pixels (différents mobiles ont une capacité de pixels différente). Ici, dans Android, en fonction de ces pixels, seule l'image capturée prendra de la mémoire. Il est donc évident qu'une image à haute résolution ne sera pas téléchargée par un téléphone à faible capacité en pixels. Dans Android, l'OS alloue au maximum 16MB à chaque application. Si l'image téléchargée prend plus que cela, alors java.lang.OutofMemoryError : bitmap size exceeds VM budget se produira et l'application se plantera. Voir ceci http://developer.Android.com/training/displaying-bitmaps/index.html

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X