Исключение OutOfMemory при загрузке растрового изображения из внешнего хранилища
В моем приложении загружается несколько изображений из файлов JPEG и PNG. Когда я помещаю все эти файлы в каталог ресурсов и загружаю их таким образом, все в порядке:
InputStream stream = getAssets().open(path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
return new BitmapDrawable(bitmap);
Но когда я пытаюсь загрузить точные изображения с SD-карты, я получаю исключение OutOfMemory!
InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
return new BitmapDrawable(bitmap);
Это то, что я получаю в журнале:
11-05 00:53:31.003: ERROR/dalvikvm-heap(13183): 827200-byte external allocation too large for this process.
11-05 00:53:31.003: ERROR/GraphicsJNI(13183): VM won't let us allocate 827200 bytes
...
11-05 00:53:31.053: ERROR/AndroidRuntime(13183): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
11-05 00:53:31.053: ERROR/AndroidRuntime(13183): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
...
Почему это может случиться?
UPDATE: пробовал оба из них на реальном устройстве - кажется, что я не могу загрузить более 12 МБ растровых изображений во все, что называется "внешняя память" (это не SD-карта).
Ответы
Ответ 1
Вероятно, ничего плохого в использовании API-интерфейса, я думаю, что все, что мы можем сделать, заключается в том, что использование AssetManager включает в себя меньшее количество закулисных кучей, чем открытие случайного файла с SD-карты.
800 КБ - серьезное распределение в любой книге... это, несомненно, будет для пикселей с декомпрессированным изображением. Учитывая, что вы знаете размер изображения, какая у него глубина? Если это 32bpp, попробуйте переопределить это, используя inPreferredConfig
.
Ответ 2
Я пробовал все подходы здесь и в других ресурсах, но я пришел к выводу, что установка ссылки ImageView на null решит проблему:
public Bitmap getimage(String path ,ImageView iv)
{
//iv is passed to set it null to remove it from external memory
iv=null;
InputStream stream = new FileInputStream("/mnt/sdcard/mydata/" + path);
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, null);
stream.close();
stream=null;
return bitmap;
}
& вы закончили!
Примечание. Хотя это может решить вышеперечисленную проблему, но я предлагаю вам проверить Tom van Zummeren оптимизированную загрузку изображений.
И также проверьте SoftReference: Все SoftReferences, указывающие на объекты с возможностью достижения цели, гарантированно будут очищены до того, как VM выведет OutOfMemoryError.
Ответ 3
- Когда вы делаете много с растровыми изображениями, не отлаживайте приложение - просто запустите его. Отладчик оставит утечку памяти.
- Растровые изображения очень дороги. Если возможно, уменьшите их нагрузку, создав
BitmapFactory.Options
и установив inSampleSize
в > 1.
РЕДАКТИРОВАТЬ: Также не забудьте проверить приложение на наличие утечек памяти. Утечка растрового изображения (наличие static
Bitmaps - отличный способ сделать это) быстро исчерпает доступную память.
Ответ 4
The best solution i found and edited according to my need
public static Bitmap getImageBitmap(String path) throws IOException{
// Allocate files and objects outside of timingoops
File file = new File(thumbpath);
RandomAccessFile in = new RandomAccessFile(file, "rws");
final FileChannel channel = in.getChannel();
final int fileSize = (int)channel.size();
final byte[] testBytes = new byte[fileSize];
final ByteBuffer buff = ByteBuffer.allocate(fileSize);
final byte[] buffArray = buff.array();
@SuppressWarnings("unused")
final int buffBase = buff.arrayOffset();
// Read from channel into buffer, and batch read from buffer to byte array;
long time1 = System.currentTimeMillis();
channel.position(0);
channel.read(buff);
buff.flip();
buff.get(testBytes);
long time1 = System.currentTimeMillis();
Bitmap bmp = Bitmap_process(buffArray);
long time2 = System.currentTimeMillis();
System.out.println("Time taken to load: " + (time2 - time1) + "ms");
return bmp;
}
public static Bitmap Bitmap_process(byte[] buffArray){
BitmapFactory.Options options = new BitmapFactory.Options();
options.inDither=false; //Disable Dithering mode
options.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared
options.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
options.inTempStorage=new byte[32 * 1024]; //Allocate some temporal memory for decoding
options.inSampleSize=1;
Bitmap imageBitmap = BitmapFactory.decodeByteArray(buffArray, 0, buffArray.length, options);
return imageBitmap;
}
Ответ 5
Это довольно распространенная проблема, с которой все мы сталкиваемся при загрузке изображений с SD-карты.
Решение, которое я нашел, заключалось в том, чтобы сначала использовать inJustDecodeBounds при загрузке изображения с помощью decodeFileDescriptor. Это фактически не декодирует изображение, а дает размер изображения. Теперь я могу масштабировать его соответствующим образом (используя параметры), чтобы изменить размер изображения для области отображения. Его необходимо, потому что низкая память на телефоне может быть легко захвачена вашим 5-мегапиксельным изображением. Это я считаю самым элегантным решением.
Ответ 6
Здесь есть две проблемы.
- Растровая память не находится в куче VM, а скорее в нативной куче - см. BitmapFactory OOM заставляет меня гадать
- Сбор мусора для родной кучи более ленив, чем куча VM, поэтому вам нужно быть достаточно агрессивным в том, чтобы делать bitmap.recycle и bitmap = null каждый раз, когда вы проходите через Activity onPause или onDestroy
Ответ 7
Вместо того, чтобы напрямую загружать его с SD-карты, почему бы не переместить изображение в кеш в внутреннем хранилище телефона с помощью getCacheDir() или использовать временную директорию для хранения изображений?
См. this, this в отношении использования внешней памяти. Кроме того, эта статья может иметь отношение к вам.
Ответ 8
Благодаря всем потокам я нашел решение, которое работает для меня на реальном устройстве.
Трюки - это использование
BitmapFactory.Options opts=new BitmapFactory.Options();
opts.inSampleSize=(int)(target_size/bitmap_size); //if original bitmap is bigger
Но для меня этого было недостаточно. Мое исходное изображение (взятое из приложения "Камера" ) было 3264x2448. Правильное соотношение для меня было 3, так как я хотел простое изображение VGA с разрешением 1024x768.
Но установка inSampleSize в 3 была недостаточной: все еще исключение из памяти.
Поэтому в конце я выбрал итеративный подход: я начинаю с вычисленного правильного размера и увеличиваю его до тех пор, пока не прекращу исключение OOM.
Для меня это было у образца 4.
// Decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
// o2.inSampleSize = scale;
float trueScale = o.outWidth / 1024;
o2.inPurgeable = true;
o2.inDither = false;
Bitmap b = null;
do {
o2.inSampleSize = (int) trueScale;
Log.d(TAG, "Scale is " + trueScale);
try {
b = BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (OutOfMemoryError e) {
Log.e(TAG,"Error decoding image at sampling "+trueScale+", resampling.."+e);
System.gc();
try {
Thread.sleep(50);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
trueScale += 1;
} while (b==null && trueScale < 10);
return b;
Ответ 9
Попробуйте по-другому...
Bitmap bmpOrignal = BitmapFactory.decodeFile("/sdcard/mydata/" + path");
Ответ 10
Вы не должны зависеть от GC для утилизации вашей растровой памяти.
Вы должны четко переработать растровое изображение, когда оно не понадобится.
См. метод Bitmap:
void recycle()
Освободите память, связанную с этими растровыми пикселями, и пометьте растровое изображение как "мертвое", то есть он вызовет исключение, если вызывается getPixels() или setPixels() и ничего не рисует.
Ответ 11
Одной из наиболее распространенных ошибок, которые я обнаружил при разработке приложений для Android, является ошибка "java.lang.OutOfMemoryError: Bitmap Size Exceeds VM Budget". Я быстро обнаружил эту ошибку при работе с большим количеством растровых изображений после изменения ориентации: активность уничтожена, снова создана, а макеты "завышены" из XML, потребляющего память VM, доступную для растровых изображений.
Растровые изображения в предыдущем макете деятельности не были должным образом освобождены сборщиком мусора, поскольку они пересекли ссылки на их активность. После многих экспериментов я нашел неплохое решение этой проблемы.
Сначала установите атрибут "id" в родительском представлении вашего XML-макета:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/RootView"
>
...
Затем в методе onDestroy() вашей операции вызовите метод unbindDrawables(), передающий подтверждение родительскому представлению, а затем выполните команду System.gc()
@Override
protected void onDestroy() {
super.onDestroy();
unbindDrawables(findViewById(R.id.RootView));
System.gc();
}
private void unbindDrawables(View view) {
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
}
}
Этот метод unbindDrawables() исследует дерево представления рекурсивно и:
- Удаляет обратные вызовы во всех фоновых рисунках
- Удаляет дочерние элементы во всех группах просмотра
Ответ 12
Используйте приведенный ниже код, и вы никогда не получите следующую ошибку: java.lang.OutOfMemoryError: размер растрового изображения превышает бюджет VM
BitmapFactory.Options bounds = new BitmapFactory.Options();
bounds.inSampleSize = 4;
myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath(), bounds);
picturesView.setImageBitmap(myBitmap);
Ответ 13
Позволяет inSampleSize изменить размер последнего прочитанного изображения.
getLength() из AssetFileDescriptor позволяет получить размер файла.
Вы можете различать inSampleSize в соответствии с getLength(), чтобы предотвратить OutOfMemory следующим образом:
private final int MAX_SIZE = 500000;
public Bitmap readBitmap(Uri selectedImage)
{
Bitmap bm = null;
AssetFileDescriptor fileDescriptor = null;
try
{
fileDescriptor = this.getContentResolver().openAssetFileDescriptor(selectedImage,"r");
long size = fileDescriptor.getLength();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = (int) (size / MAX_SIZE);
bm = BitmapFactory.decodeFileDescriptor(fileDescriptor.getFileDescriptor(), null, options);
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
try {
if(fileDescriptor != null) fileDescriptor.close();
} catch (IOException e) {}
}
return bm;
}