Je travaille sur une application Android. L'application a 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 puisse me donner des suggestions.
L'application fonctionnait très bien sur tous les tests locaux. Cependant, j'ai reçu de nombreux plantages de la part des utilisateurs : java.lang.OutOfMemoryError: bitmap size exceeds VM budget
Voici 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 des appareils anciens.
J'ai mis en œuvre de nombreuses mesures pour tenter de résoudre ce problème :
-
Pas de fuites de mémoire : Je me suis assuré qu'il n'y a pas de fuite de mémoire du tout. J'ai supprimé les vues lorsque je n'en ai pas besoin. J'ai également recyclé tous les bitmaps et vérifié que le garbage collector fonctionne comme il le devrait. Et j'ai implémenté toutes les étapes nécessaires dans le
onDestroy()
méthode -
Mise à l'échelle correcte de la taille de l'image : Avant d'obtenir l'image, j'obtiens ses dimensions et je calcule la taille de l'image.
inSampleSize
. - Taille du tas : Je détecte également la taille maximale du Heap avant de récupérer l'image et m'assure qu'il y a suffisamment d'espace. S'il n'y en a pas assez, je redimensionne l'image en conséquence.
Code pour calculer le inSampleSize correct
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 pour calculer la taille de l'image en fonction de la taille du tas 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 vient du Heap, peut-être que parfois le système d'exploitation ne permet pas à l'application d'utiliser la taille maximale du Heap. Le plus gros problème est que je n'ai pas été en mesure de reproduire le problème, donc quand j'essaie un correctif, je dois attendre un peu pour voir si les utilisateurs obtiennent toujours l'erreur.
Que puis-je faire de plus pour éviter les problèmes de manque de mémoire ? Toute suggestion serait grandement appréciée. Merci beaucoup.