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?
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
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.
Ce que j'ai fait moi-même, c'est:
inJustDecodeBounds
pour obtenir la taille d'origine de l'imageinSampleSize
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;