Предложения, чтобы избежать ошибки битовой карты Out of Memory
Я работаю над приложением для Android. Приложение имеет вид, содержащий много изображений. У меня была ошибка, я постараюсь дать как можно больше информации, надеясь, что кто-то может дать мне несколько советов.
Приложение прекрасно работало на всех локальных тестах. Тем не менее, я получил много сбоев от пользователей: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
Это трассировка стека
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)
Моя самая большая проблема заключается в том, что я не смог воспроизвести проблему локально даже на старых устройствах.
Я реализовал много вещей, чтобы попытаться решить эту проблему:
- Нет утечек памяти: я удостоверился, что нет никаких утечек памяти вообще. Я удалил взгляды, когда они мне не нужны. Я также переработал все растровые изображения и убедился, что сборщик мусора работает должным образом. И я реализовал все необходимые шаги в
onDestroy()
метод - Размер изображения масштабируется правильно: перед получением изображения я получаю его размер и вычисляю
inSampleSize
, - Размер кучи: я также определяю максимальный размер кучи перед получением изображения и проверяю, достаточно ли места. Если этого недостаточно, я соответственно изменяю масштаб изображения.
Код для вычисления правильного inSampleSize
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;
}
Код для получения растрового изображения
// 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;
}
Код для расчета размера изображения на основе размера кучи для старых устройств
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);
}
}
}
}
Я думаю, что проблема с кучей, может быть, иногда ОС не позволяет приложению использовать maxheapsize. Также моя самая большая проблема заключается в том, что я не смог воспроизвести проблему, поэтому, когда я пытаюсь исправить ее, мне нужно немного подождать, чтобы увидеть, все еще ли пользователи получают ошибку.
Что еще я могу попытаться избежать проблем с нехваткой памяти? Любые предложения будут ценны. большое спасибо
6 ответов
Просто используйте эту функцию для декодирования... это идеальное решение для вашей ошибки.. потому что я также получаю ту же ошибку, и я получил это решение..
public static Bitmap decodeFile(File f,int WIDTH,int HIGHT){
try {
//Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f),null,o);
//The new size we want to scale to
final int REQUIRED_WIDTH=WIDTH;
final int REQUIRED_HIGHT=HIGHT;
//Find the correct scale value. It should be the power of 2.
int scale=1;
while(o.outWidth/scale/2>=REQUIRED_WIDTH && o.outHeight/scale/2>=REQUIRED_HIGHT)
scale*=2;
//Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize=scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException e) {}
return null;
}
Уменьшая / уменьшая размер изображения, вы можете избавиться от исключения из нехватки памяти, попробуйте это
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 6;
Bitmap receipt = BitmapFactory.decodeFile(photo.toString(),options); //From File You can customise on your needs.
Привет, ты должен расшифровать файл. для этого попробуйте с помощью следующего метода.
public static Bitmap new_decode(File f) {
// decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
o.inDither = false; // Disable Dithering mode
o.inPurgeable = true; // Tell to gc that whether it needs free memory,
// the Bitmap can be cleared
o.inInputShareable = true; // Which kind of reference will be used to
// recover the Bitmap data after being
// clear, when it will be used in the future
try {
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE = 300;
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = 1;
while (true) {
if (width_tmp / 1.5 < REQUIRED_SIZE && height_tmp / 1.5 < REQUIRED_SIZE)
break;
width_tmp /= 1.5;
height_tmp /= 1.5;
scale *= 1.5;
}
// decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
// o2.inSampleSize=scale;
o.inDither = false; // Disable Dithering mode
o.inPurgeable = true; // Tell to gc that whether it needs free memory,
// the Bitmap can be cleared
o.inInputShareable = true; // Which kind of reference will be used to
// recover the Bitmap data after being
// clear, when it will be used in the future
// return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
try {
// return BitmapFactory.decodeStream(new FileInputStream(f), null,
// null);
Bitmap bitmap= BitmapFactory.decodeStream(new FileInputStream(f), null, null);
System.out.println(" IW " + width_tmp);
System.out.println("IHH " + height_tmp);
int iW = width_tmp;
int iH = height_tmp;
return Bitmap.createScaledBitmap(bitmap, iW, iH, true);
} catch (OutOfMemoryError e) {
// TODO: handle exception
e.printStackTrace();
// clearCache();
// System.out.println("bitmap creating success");
System.gc();
return null;
// System.runFinalization();
// Runtime.getRuntime().gc();
// System.gc();
// decodeFile(f);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
Я написал резюме предложений в другом вопросе StackOverFlow: Android: BitmapFactory.decodeStream () не хватает памяти с файлом 400 КБ с 2 МБ свободной кучи
Если вы хотите избежать OOM, вы можете поймать OOM и увеличивать sampleSize, пока изображение не будет разрешено:
private Bitmap getBitmapSafely(Resources res, int id, int sampleSize) {
// res = context.getResources(), id = R.drawable.yourimageid
Bitmap bitmap = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPurgeable = true;
options.inSampleSize = sampleSize;
try {
bitmap = BitmapFactory.decodeResource(res,
id, options);
} catch (OutOfMemoryError oom) {
Log.w("ImageView", "OOM with sampleSize " + sampleSize, oom);
System.gc();
bitmap = getBitmapSafely(res, id, sampleSize + 1);
}
return bitmap;
}
Надеюсь, поможет.
Ошибка не подходит, просто обходной путь.
На самом деле проблема с разработкой ОС. В Android, в отличие от iOS, пользователи Google разрабатывают это на основе разрешения камеры. Растровые изображения занимают много памяти, особенно для богатых изображений, таких как фотографии. Разные камеры снимают изображения с разными пикселями (разные мобильные телефоны имеют разную емкость пикселей камеры). Вот в андроиде исходя из того что пиксели только захваченное изображение займет память. поэтому очевидно, что изображение с высоким разрешением не будет загружено телефоном с низкой пиксельной емкостью. В Android OS выделяет максимум 16 МБ для каждого приложения. Если загруженное изображение занимает больше, чем это, то произойдет java.lang.OutofMemoryError: размер растрового изображения превысит бюджет виртуальной машины, и произойдет сбой приложения. обратитесь к этому http://developer.android.com/training/displaying-bitmaps/index.html