web-dev-qa-db-fra.com

Suggestions pour éviter l'erreur de mémoire insuffisante bitmap

Je travaille sur une application Android. L'application dispose d'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 pourra me donner des suggestions.

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

C'est 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 d'anciens périphériques.

J'ai mis en œuvre beaucoup de choses pour essayer de résoudre ceci:

  1. Pas de fuite de mémoire : Je me suis assuré qu'il n'y avait aucune fuite de mémoire. J'ai enlevé les vues quand je n'en ai pas besoin. J'ai également recyclé toutes les bitmaps et veillé à ce que le ramasse-miettes fonctionne comme il se doit. Et j'ai implémenté toutes les étapes nécessaires dans la méthode onDestroy()
  2. La taille de l'image est correctement mise à l'échelle : Avant d'obtenir l'image, je reçois sa dimension et calcule la inSampleSize.
  3. Taille de tas : Je détecte également la taille de tas maximale avant d'obtenir l'image et m'assure de disposer de suffisamment d'espace. S'il n'y en a pas assez, je redimensionne l'image en conséquence.

Code permettant de calculer le bon inSampleSize

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 permettant de calculer la taille de l'image en fonction de la taille de segment de mémoire 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 est avec le tas, peut-être que parfois le système d'exploitation ne permet pas à l'application d'utiliser le maxheapsize. De plus, mon plus gros problème est que je n'ai pas été en mesure de reproduire le problème. Par conséquent, lorsque j'essaie de corriger un correctif, je dois attendre un peu pour voir si les utilisateurs obtiennent toujours l'erreur.

Que puis-je essayer d'éviter les problèmes de mémoire insuffisante? Toutes les suggestions seraient grandement appréciées. Merci beaucoup

35
Youssef

utilisez cette fonction pour décoder ... c’est la solution parfaite pour votre erreur ...

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;
 }
8
Mehul Ranpara

En réduisant/redimensionnant la taille de l’image, vous pourrez vous débarrasser de l’exception de mémoire insuffisante,

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

Bonjour, vous devez décoder le fichier. pour cela, essayez avec 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;
        }

    }
4
itsrajesh4uguys

J'ai écrit un résumé des suggestions dans une autre question StackOverFlow: Android: BitmapFactory.decodeStream () saturé en mémoire avec un fichier de 400 Ko avec 2 Mo d'espace libre

0
Paulo Cheque

Si vous voulez éviter le MOO, vous pouvez le capturer et augmenter la taille de l'échantillon jusqu'à ce que l'image puisse être résolue:

private Bitmap getBitmapSafely(Resources res, int id, int sampleSize) {
// res = context.getResources(), id = R.drawable.yourimageid
    Bitmap bitmap = null;
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPurgeable = true;
    options.inSampleSize = sampleSize;
    try {
          bitmap = BitmapFactory.decodeResource(res,
                      id, options);
    } catch (OutOfMemoryError oom) {
        Log.w("ImageView", "OOM with sampleSize " + sampleSize, oom);
        System.gc();
        bitmap = getBitmapSafely(res, id, sampleSize + 1);
    }

    return bitmap;
}

J'espère que ça aide.

Il ne convient pas d'attraper l'erreur, juste une solution de contournement.

0
Euporie