web-dev-qa-db-fra.com

Comment charger de grandes images dans Android et éviter les erreurs de mémoire insuffisante?

Je travaille sur une application qui utilise de grandes images (1390 × 870: 150 Ko - 50 Ko). J'ajoute des images lorsque je tape sur un déclencheur/ImageView.

À un moment donné, je reçois une erreur de mémoire insuffisante:

Java.lang.OutOfMemoryError
E/AndroidRuntime(23369): at Android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(23369): at Android.graphics.BitmapFactory.decodeStream(BitmapFactory.Java:613)
E/AndroidRuntime(23369): at Android.graphics.BitmapFactory.decodeFile(BitmapFactory.Java:378)

Pour redimensionner l'image, je fais ceci:

Bitmap productIndex = null;
final String imageLoc = IMAGE_LOCATION;
InputStream imageStream;
try {
     imageStream = new FileInputStream(imageLoc);
     productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);

     productIV.setImageBitmap(productIndex);
     } catch (FileNotFoundException e1) {
          // TODO Auto-generated catch block
          e1.printStackTrace();
     }
}


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

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

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

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

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 / 3;
    final int halfWidth = width / 3;

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

J'ai eu cette façon de redimensionner pour économiser de l'espace sous Android Docs: Chargement efficace de bitmaps

Selon le journal, ceci ressemble au coupable de la méthode decodeSampledBitmapFromResource:

return BitmapFactory.decodeFile(resId, options);

Voici comment j'ajoute chaque élément au FrameLayout.

for(int ps=0;ps<productSplit.size();ps++){
    //split each product by the equals sign
    List<String> productItem = Arrays.asList(productSplit.get(ps).split("="));

    String tempCarID = productItem.get(0);
    tempCarID = tempCarID.replace(" ", "");
    if(String.valueOf(carID).equals(tempCarID)){

        ImageView productIV = new ImageView(Configurator.this);
        LayoutParams productParams = new LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        productIV.setId(Integer.parseInt(partIdsList.get(x)));
        productIV.setLayoutParams(productParams);

        final String imageLoc = productItem.get(2);

        InputStream imageStream;
        try {
            imageStream = new FileInputStream(imageLoc);
            productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
            productIV.setImageBitmap(productIndex);
        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        productLayers.addView(productIV);

    }
}
15
dcp3450

Vous pouvez utiliser une autre configuration bitmap pour réduire considérablement la taille des images. La valeur par défaut est RGB-config ARGB8888, ce qui signifie que quatre canaux de 8 bits sont utilisés (rouge, vert, bleu, alhpa). Alpha est la transparence du bitmap. Cela occupe beaucoup de mémoire - imagesize X 4. Donc, si la taille d'image est de 4 mégapixels, 16 mégaoctets seront immédiatement alloués sur le tas - épuisant rapidement la mémoire. 

Au lieu de cela, utilisez RGB_565, ce qui détériore dans une certaine mesure la qualité, mais pour compenser cela, vous pouvez utiliser un tramage des images.

Donc, à votre méthode decodeSampledBitmapFromResource, ajoutez les extraits suivants:

 options.inPreferredConfig = Config.RGB_565;
 options.inDither = true;

Dans votre code:

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

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

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

 // Decode bitmap with inSampleSize set
 options.inJustDecodeBounds = false;
 options.inPreferredConfig = Config.RGB_565;
 options.inDither = true;
 return BitmapFactory.decodeFile(resId, options);
 }

Références:

http://developer.Android.com/reference/Android/graphics/Bitmap.Config.html#ARGB_8888

27
Björn Hallström

Les périphériques haute résolution tels que S4 manquent généralement de mémoire si votre image ne se trouve pas dans le dossier approprié, à savoir drawable-xxhdpi. Vous pouvez également mettre votre image dans drawable-nodpi. La raison pour laquelle il manquerait de mémoire si votre image dans drawable indique qu'Android redimensionnerait l'image en pensant que l'image était conçue pour une faible résolution.

25
serge

Vous pouvez utiliser cette belle bibliothèque https://github.com/davemorrissey/subsampling-scale-image-view

4
Rohit Arya

Here is how I'm adding each item to the FrameLayout C'est le problème, le code ajoute et ajoute encore des images, et peu importe la qualité de votre redimensionnement ou la quantité de mémoire dont dispose le périphérique. À un moment donné, il SERA épuisé. C'est parce que chaque image que vous ajoutez est gardée en mémoire.

Pour ce type de situation, les applications utilisent un ViewGroup capable de recycler les vues. Je ne connais pas votre mise en page, mais vous avez l'habitude d'utiliser ListView, GridView ou ViewPager en recyclant les vues, de réutiliser la mise en page et de disposer des images rechargées si nécessaire.

Dans le but spécifique de charger et de redimensionner des images, je vous recommande vivement d’utiliser la bibliothèque Picasso, car elle est TRÈS bien écrite, simple à utiliser et stable.

1
Budius

Vous allez toujours avoir besoin de gérer la mémoire bitmap car je n'essaierais pas d'allouer un espace total supérieur à 3 fois la taille de l'écran (si vous y réfléchissez, cela a du sens pour un comportement de défilement). Si vous superposez une image l'une sur l'autre, à un moment donné, vous rencontrez une erreur de mémoire insuffisante. Vous devrez peut-être envisager de capturer l'image d'écran précédente en tant qu'image d'arrière-plan unique pour vous assurer que vous gardez toujours votre place dans la mémoire disponible. Ou lorsqu'une nouvelle image chevauche une image existante, chargez et rendez la partie visible uniquement. Si les performances deviennent un problème, vous devrez peut-être prendre en compte OpenGL Textures, mais le problème d'allocation de mémoire reste identique.

Parcourez toutes les Affichage de la formation Bitmaps car cela devrait vous donner quelques idées supplémentaires sur la façon de gérer l’affichage.

0
Morrison Chang

Utiliser la bibliothèque Fresco pour charger des images volumineuses évitera cette erreur . In XML layout

<com.facebook.drawee.view.SimpleDraweeView
    Android:id="@+id/my_image_view"
    Android:layout_width="1300dp"
    Android:layout_height="1300dp"
    fresco:placeholderImage="@drawable/my_drawable"
  />

et en javacode

Uri uri = Uri.parse("https://image.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
0
lee