Как загрузить большие изображения в Android и избежать ошибки из памяти?
Я работаю над приложением, которое использует большие изображения (1390 × 870: 150kb - 50kb). Я добавляю изображения при нажатии триггера /ImageView.
В какой-то момент у меня возникает ошибка в памяти:
java.lang.OutOfMemoryError
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:613)
E/AndroidRuntime(23369): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:378)
Чтобы изменить размер изображения, я делаю это:
Bitmap productIndex = null;
final String imageLoc = IMAGE_LOCATION;
InputStream imageStream;
try {
imageStream = new FileInputStream(imageLoc);
productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
productIV.setImageBitmap(productIndex);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
public static Bitmap decodeSampledBitmapFromResource(Resources res, String resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(resId, options);
}
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 / 3;
final int halfWidth = width / 3;
// 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;
}
У меня есть такой способ изменения размера, чтобы сэкономить место в Android-документах:
Эффективная загрузка больших растровых изображений
В соответствии с журналом это похоже на виновника в методе decodeSampledBitmapFromResource
:
return BitmapFactory.decodeFile(resId, options);
----- edit -----
Вот как я добавляю каждый элемент в FrameLayout.
for(int ps=0;ps<productSplit.size();ps++){
//split each product by the equals sign
List<String> productItem = Arrays.asList(productSplit.get(ps).split("="));
String tempCarID = productItem.get(0);
tempCarID = tempCarID.replace(" ", "");
if(String.valueOf(carID).equals(tempCarID)){
ImageView productIV = new ImageView(Configurator.this);
LayoutParams productParams = new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
productIV.setId(Integer.parseInt(partIdsList.get(x)));
productIV.setLayoutParams(productParams);
final String imageLoc = productItem.get(2);
InputStream imageStream;
try {
imageStream = new FileInputStream(imageLoc);
productIndex = decodeSampledBitmapFromResource(getResources(), imageLoc, 400, 400);
productIV.setImageBitmap(productIndex);
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
productLayers.addView(productIV);
}
}
Ответы
Ответ 1
Вы можете использовать другой bitmap-config для значительного уменьшения размера изображений. По умолчанию используется RGB-config ARGB8888, что означает использование четырех 8-битных каналов (красный, зеленый, синий, alhpa). Альфа - прозрачность растрового изображения. Это занимает много памяти - изображение X 4. Так что если изображение будет 4 мегапикселя, 16 мегабайт будет сразу же распределено на куче - быстро изнурительная память.
Вместо этого используйте RGB_565, который в какой-то мере ухудшает качество, но чтобы компенсировать это, вы можете сгладить изображения.
Итак - к вашему методу decodeSampledBitmapFromResource - добавьте следующие фрагменты:
options.inPreferredConfig = Config.RGB_565;
options.inDither = true;
В вашем коде:
public static Bitmap decodeSampledBitmapFromResource(Resources res, String resId, int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
options.inPreferredConfig = Config.RGB_565;
options.inDither = true;
return BitmapFactory.decodeFile(resId, options);
}
Литература:
http://developer.android.com/reference/android/graphics/Bitmap.Config.html#ARGB_8888
Ответ 2
У устройств с высоким разрешением, таких как S4, обычно заканчивается память, если у вас нет изображения в соответствующей папке drawable-xxhdpi
. Вы также можете поместить свое изображение в drawable-nodpi
. Причина, по которой у него закончилось бы воспоминание, если бы ваш образ только в drawable
, чтобы андроид масштабировал изображение, думая, что изображение было спроектировано для низкого разрешения.
Ответ 3
Вы можете использовать эту красивую библиотеку https://github.com/davemorrissey/subsampling-scale-image-view
Ответ 4
Here is how I'm adding each item to the FrameLayout
что проблема, код продолжает добавлять и добавлять больше изображений, и не имеет значения, насколько хорошо вы изменяете размер или объем памяти устройства, в какой-то момент у него закончится память. Это потому, что каждое изображение добавляется в память.
Для такого типа ситуаций приложениями является использование ViewGroup, которое может перерабатывать представления. Я не знаю вашего макета, но обычно это ListView
, GridView
или ViewPager
, путем повторного использования представлений, которые вы повторно используете в макете, и при необходимости можете повторно загружать изображения.
В целях загрузки и изменения размеров изображений я настоятельно рекомендую использовать библиотеку Picasso, поскольку она ОЧЕНЬ хорошо написана, проста в использовании и стабильна.
Ответ 5
Вам все равно понадобится управлять растровой памятью, так как я бы не попытался выделить общее пространство более чем в 3 раза по размеру экрана (если вы думаете, что это имеет смысл для поведения прокрутки). Если вы накладываете одно изображение поверх другого, в какой-то момент вы получаете ошибку Out Of Memory. Возможно, вам придется посмотреть на захват предыдущего изображения экрана в виде одного фонового изображения, чтобы убедиться, что вы все еще вписываетесь в доступную память. Или когда новое изображение перекрывает существующее изображение, загружает только и визуализирует видимую часть. Если производительность становится проблемой, вам может потребоваться рассмотреть OpenGL Textures, но проблема с распределением памяти остается прежней.
Пройдите через все Отображение обучения растровых изображений, поскольку оно должно дать вам дополнительные идеи о том, как обрабатывать отображение.
Ответ 6
Использование библиотеки Fresco для загрузки больших изображений позволит избежать этой ошибки.
в макете xml
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="1300dp"
android:layout_height="1300dp"
fresco:placeholderImage="@drawable/my_drawable"
/>
и в javacode
Uri uri = Uri.parse("https://image.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);