web-dev-qa-db-fra.com

Gestion des bitmaps volumineux

J'ai un tas d'URL d'images. Je dois télécharger ces images et les afficher une par une dans mon application. J'enregistre les images dans une collection en utilisant SoftReferences et également sur Sdcard pour éviter les rechutes et améliorer l'expérience utilisateur.

Le problème est que je ne sais rien de la taille des bitmaps. Et comme il se trouve, j'obtiens OutOfMemoryExceptions de façon sporadique, lorsque j'utilise la méthode BitmapFactory.decodeStream(InputStream). J'ai donc choisi de sous-échantillonner les images à l'aide des options BitmapFactory (taille de l'échantillon = 2). Cela a donné une meilleure sortie: pas de MOO, mais cela affecte la qualité des images plus petites.

Comment dois-je gérer de tels cas? Existe-t-il un moyen de sous-échantillonner sélectivement uniquement les images haute résolution?

31
Samuh

Il y a une option dans BitmapFactory.Options classe (celle que j'ai ignorée) nommée inJustDecodeBounds, dont javadoc se lit comme suit:

S'il est défini sur true, le décodeur retournera null (pas de bitmap), mais les champs out ... seront toujours définis, permettant à l'appelant d'interroger le bitmap sans avoir à allouer de la mémoire pour ses pixels.

Je l'ai utilisé pour connaître la taille réelle du bitmap, puis j'ai choisi de le réduire en utilisant l'option inSampleSize. Cela évite au moins toute erreur MOO lors du décodage du fichier.

Référence:
1. Gestion de bitmaps plus grands
2. Comment obtenir des informations Bitmap avant de décoder

55
Samuh

Après quelques jours de lutte pour éviter toutes les erreurs OutOfMemory que j'obtenais avec différents appareils, je crée ceci:

private Bitmap getDownsampledBitmap(Context ctx, Uri uri, int targetWidth, int targetHeight) {
    Bitmap bitmap = null;
    try {
        BitmapFactory.Options outDimens = getBitmapDimensions(uri);

        int sampleSize = calculateSampleSize(outDimens.outWidth, outDimens.outHeight, targetWidth, targetHeight);

        bitmap = downsampleBitmap(uri, sampleSize);

    } catch (Exception e) {
        //handle the exception(s)
    }

    return bitmap;
}

private BitmapFactory.Options getBitmapDimensions(Uri uri) throws FileNotFoundException, IOException {
    BitmapFactory.Options outDimens = new BitmapFactory.Options();
    outDimens.inJustDecodeBounds = true; // the decoder will return null (no bitmap)

    InputStream is= getContentResolver().openInputStream(uri);
    // if Options requested only the size will be returned
    BitmapFactory.decodeStream(is, null, outDimens);
    is.close();

    return outDimens;
}

private int calculateSampleSize(int width, int height, int targetWidth, int targetHeight) {
    int inSampleSize = 1;

    if (height > targetHeight || width > targetWidth) {

        // Calculate ratios of height and width to requested height and
        // width
        final int heightRatio = Math.round((float) height
                / (float) targetHeight);
        final int widthRatio = Math.round((float) width / (float) targetWidth);

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

private Bitmap downsampleBitmap(Uri uri, int sampleSize) throws FileNotFoundException, IOException {
    Bitmap resizedBitmap;
    BitmapFactory.Options outBitmap = new BitmapFactory.Options();
    outBitmap.inJustDecodeBounds = false; // the decoder will return a bitmap
    outBitmap.inSampleSize = sampleSize;

    InputStream is = getContentResolver().openInputStream(uri);
    resizedBitmap = BitmapFactory.decodeStream(is, null, outBitmap);
    is.close();

    return resizedBitmap;
}

Cette méthode fonctionne avec tous les appareils que j'ai testés, mais je pense que la qualité peut être meilleure en utilisant un autre processus que je ne connais pas.

J'espère que mon code pourra aider d'autres développeurs dans la même situation. J'apprécie également si un développeur senior peut aider, en donnant une suggestion sur un autre processus pour éviter de perdre (moins) de qualité dans le processus.

13
Ryan Amaral

Ce que j'ai fait moi-même, c'est:

  • utilisez inJustDecodeBounds pour obtenir la taille d'origine de l'image
  • avoir une surface maximale fixe pour charger le bitmap (disons 1Mpixels)
  • vérifiez si la surface de l'image est inférieure à la limite, si oui, chargez-la directement
  • sinon calculez la largeur et la hauteur idéales auxquelles vous devez charger le bitmap pour rester en dessous de la surface maximale (il y a quelques calculs simples à faire ici). Cela vous donne un ratio flottant que vous souhaitez appliquer lors du chargement du bitmap
  • vous voulez maintenant traduire ce rapport en un inSampleSize approprié (une puissance de 2 qui ne dégrade pas la qualité de l'image). J'utilise cette fonction:
 int k = Integer.highestOneBit ((int) Math.floor (ratio)); 
 if (k == 0) return 1; 
 else return k; 
  • ensuite, parce que le bitmap aura été chargé avec une résolution légèrement supérieure à la surface maximale (car vous avez dû utiliser une puissance inférieure à 2), vous devrez redimensionner le bitmap, mais ce sera beaucoup plus rapide.
12
Greg