Remarque : Plusieurs bibliothèques suivent les bonnes pratiques de chargement d'images. Vous pouvez utiliser ces bibliothèques dans votre application pour charger des images de la manière la plus optimisée possible. Nous vous recommandons la bibliothèque Glide, qui permet de charger et d'afficher des images aussi rapidement et facilement que possible. Parmi les autres bibliothèques d'images populaires figurent Picasso de Square, Coil d'Instacart et Fresco de Facebook. Ces bibliothèques simplifient la plupart des tâches complexes associées aux bitmaps et aux autres types d'images sur Android.
Les images sont de toutes formes et de toutes tailles. Bien souvent, elles sont plus volumineuses que ce qui est nécessaire pour une interface utilisateur (UI) d'application standard. Par exemple, l'application système Galerie affiche les photos prises avec l'appareil photo de votre appareil Android, dont la résolution est généralement beaucoup plus haute que la densité d'écran de votre appareil.
Étant donné que vous travaillez avec une mémoire limitée, idéalement, vous ne devez charger qu'une version basse résolution en mémoire. La version basse résolution doit correspondre à la taille du composant d'interface utilisateur qui l'affiche. Une image avec une résolution plus élevée ne présente aucun avantage apparent, mais elle utilise beaucoup de mémoire et ralentit les performances en raison de mises à l'échelle ajoutées à la volée.
Cette leçon explique comment décoder des bitmaps volumineux sans dépasser la limite de mémoire par application en chargeant une plus petite version en sous-échantillon en mémoire.
Lire les dimensions et le type de bitmap
La classe BitmapFactory
fournit plusieurs méthodes de décodage (decodeByteArray()
, decodeFile()
, decodeResource()
, etc.) pour créer un Bitmap
à partir de différentes sources. Choisissez la méthode de décodage la plus appropriée en fonction de votre source de données d'images. Ces méthodes tentent d'allouer de la mémoire pour le bitmap construit et peuvent donc facilement entraîner une exception OutOfMemory
. Chaque type de méthode de décodage comporte des signatures supplémentaires qui vous permettent de spécifier des options de décodage via la classe BitmapFactory.Options
. Définir la propriété inJustDecodeBounds
sur true
lors du décodage évite l'allocation de mémoire, en affichant null
pour l'objet bitmap, mais en définissant outWidth
, outHeight
et outMimeType
. Cette technique vous permet de lire les dimensions et le type des données d'image avant la construction (et l'allocation de mémoire) du bitmap.
Kotlin
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } BitmapFactory.decodeResource(resources, R.id.myimage, options) val imageHeight: Int = options.outHeight val imageWidth: Int = options.outWidth val imageType: String = options.outMimeType
Java
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;
Pour éviter les exceptions java.lang.OutOfMemory
, vérifiez les dimensions d'un bitmap avant de le décoder, sauf si vous avez entièrement confiance en la source qui vous fournit des données d'image de taille prévisible, qui sont adaptées à la mémoire disponible.
Charger une version réduite dans la mémoire
Maintenant que vous connaissez les dimensions de l'image, vous pouvez décider si l'image entière doit être chargée en mémoire ou si une version sous-échantillonnée doit être chargée à la place. Voici quelques facteurs à prendre en compte :
- Estimation de l'utilisation de la mémoire pour charger l'image entière en mémoire.
- Quantité de mémoire que vous souhaitez consacrer au chargement de cette image en fonction des autres exigences de mémoire de votre application.
- Dimensions de l'
ImageView
cible ou du composant d'interface utilisateur dans lequel l'image doit être chargée. - Taille et densité d'écran de l'appareil actuel.
Par exemple, il n'est pas intéressant de charger une image de 1 024 x 768 pixels dans la mémoire si elle s'affiche au final dans une vignette de 128 x 96 pixels dans une ImageView
.
Pour indiquer au décodeur de sous-échantillonner l'image en chargeant une version plus petite dans la mémoire, définissez inSampleSize
sur true
dans votre objet BitmapFactory.Options
. Par exemple, une image qui mesure 2 048 x 1 536 pixels décodée avec une inSampleSize
de 4 génère un bitmap d'environ 512 x 384 pixels. Le chargement en mémoire utilise 0,75 Mo au lieu de 12 Mo pour l'image complète (à condition d'utiliser une configuration bitmap de ARGB_8888
). Voici une méthode pour calculer un exemple de valeur de taille qui est une puissance de deux en fonction d'une largeur et d'une hauteur cibles :
Kotlin
fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int { // Raw height and width of image val (height: Int, width: Int) = options.run { outHeight to outWidth } var inSampleSize = 1 if (height > reqHeight || width > reqWidth) { val halfHeight: Int = height / 2 val halfWidth: Int = width / 2 // 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 }
Java
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 / 2; final int halfWidth = width / 2; // 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; }
Remarque : Une valeur est calculée via une puissance de deux, car le décodeur utilise une valeur finale en arrondissant à la puissance de deux la plus proche, conformément à la documentation inSampleSize
.
Pour utiliser cette méthode, vous devez tout d'abord effectuer le décodage avec un inJustDecodeBounds
défini sur true
, transmettre les options, puis procéder à un nouveau décodage à l'aide de la nouvelle valeur inSampleSize
et d'un inJustDecodeBounds
défini sur false
:
Kotlin
fun decodeSampledBitmapFromResource( res: Resources, resId: Int, reqWidth: Int, reqHeight: Int ): Bitmap { // First decode with inJustDecodeBounds=true to check dimensions return BitmapFactory.Options().run { inJustDecodeBounds = true BitmapFactory.decodeResource(res, resId, this) // Calculate inSampleSize inSampleSize = calculateInSampleSize(this, reqWidth, reqHeight) // Decode bitmap with inSampleSize set inJustDecodeBounds = false BitmapFactory.decodeResource(res, resId, this) } }
Java
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }
Cette méthode permet de charger facilement un bitmap de taille arbitraire dans une ImageView
affichant une vignette de 100 x 100 pixels, comme illustré dans l'exemple de code suivant :
Kotlin
imageView.setImageBitmap( decodeSampledBitmapFromResource(resources, R.id.myimage, 100, 100) )
Java
imageView.setImageBitmap( decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Vous pouvez adopter un processus similaire pour décoder des bitmaps provenant d'autres sources en utilisant la méthode BitmapFactory.decode*
appropriée aux besoins particuliers.