Странная проблема OutOfMemory при загрузке изображения в объект Bitmap

avatar
Chrispix
25 января 2009 в 11:23
634812
44
1356

У меня есть ListView с парой кнопок изображений в каждой строке. Когда пользователь щелкает строку списка, запускается новое действие. Мне пришлось создавать свои собственные вкладки из-за проблемы с расположением камеры. Действие, которое запускается для результата, - это карта. Если я нажму кнопку, чтобы запустить предварительный просмотр изображения (загрузить изображение с SD-карты), приложение вернется из действия обратно в действие ListView в обработчик результатов, чтобы перезапустить мое новое действие, которое является не чем иным, как виджетом изображения. .

Предварительный просмотр изображения на ListView выполняется с помощью курсора и ListAdapter. Это делает его довольно простым, но я не уверен, как я могу поместить изображение с измененным размером (т.е. меньший битовый размер, а не пиксель, как src для кнопки изображения на лету. Поэтому я просто изменил размер изображения, полученного с камеры телефона .

Проблема в том, что я получаю OutOfMemoryError, когда он пытается вернуться и повторно запустить второе действие.

  • Есть ли способ, которым я могу легко построить адаптер списка построчно, где я могу изменять размер на лету ( побитовое )?

Это было бы предпочтительнее, поскольку мне также нужно внести некоторые изменения в свойства виджетов / элементов в каждой строке, поскольку я не могу выбрать строку с помощью сенсорного экрана из-за проблемы с фокусом. ( Я могу использовать роллер. )

  • Я знаю, что могу выполнить нестандартное изменение размера и сохранить свое изображение, но на самом деле это не то, что я хочу сделать, но было бы неплохо использовать некоторый образец кода для этого.

Как только я отключил изображение на ListView, оно снова заработало нормально.

К вашему сведению: вот как я это делал:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS,
    DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT,
    DBHelper.KEY_IMAGEFILENAME  + ""};
int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong,
    R.id.gpslat, R.id.imagefilename };
notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);

Где R.id.imagefilename - это ButtonImage.

Вот мой LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process.
01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes
01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception
01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.resolveUri(ImageView.java:484)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ImageView.setImageURI(ImageView.java:281)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.CursorAdapter.getView(CursorAdapter.java:150)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.obtainView(AbsListView.java:1057)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.makeAndAddView(ListView.java:1616)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.fillSpecific(ListView.java:1177)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.ListView.layoutChildren(ListView.java:1454)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.AbsListView.onLayout(AbsListView.java:937)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:922)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.LinearLayout.onLayout(LinearLayout.java:920)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.widget.FrameLayout.onLayout(FrameLayout.java:294)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.View.layout(View.java:5611)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.performTraversals(ViewRoot.java:771)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1103)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Handler.dispatchMessage(Handler.java:88)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.os.Looper.loop(Looper.java:123)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at android.app.ActivityThread.main(ActivityThread.java:3742)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invokeNative(Native Method)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at java.lang.reflect.Method.invoke(Method.java:515)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497)
01-25 05:05:49.917: ERROR/AndroidRuntime(3896):     at dalvik.system.NativeStart.main(Native Method)
01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

Еще у меня новая ошибка при отображении изображения:

22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed
Источник
Fraggle
19 августа 2011 в 01:56
9

Я решил это, избегая Bitmap.decodeStream или decodeFile и используя метод BitmapFactory.decodeFileDescriptor.

Shailendra Singh Rajawat
12 июня 2014 в 10:53
2

Я также столкнулся с подобной проблемой пару недель назад, и я решил ее, уменьшив изображение до оптимальной точки. Я написал полный подход в своем блоге codingjunkiesforum.wordpress.com/2014/06/12/… и загрузил полный образец проекта с кодом, подверженным OOM, и кодом OOM Proof по адресу https://github.com/shailendra123/ BitmapHandlingDemo

Fattie
29 июля 2014 в 11:29
2

Полное решение .. coderhelper.com/a/24135283/294884

rene
7 сентября 2015 в 08:44
6

Принятый ответ на этот вопрос обсуждается на мета

Developine
5 июля 2016 в 16:23
2

Прочтите этот блогPOst codingaffairs.blogspot.com/2016/07/…

Access Denied
14 марта 2018 в 11:13
4

Это происходит из-за плохой архитектуры андроида. Он должен изменять размер самих изображений, как это делает ios и UWP. Мне не нужно делать это самому. Разработчики Android привыкают к этому аду и думают, что он работает так, как должен.

Ответы (44)

avatar
Sazid
20 мая 2021 в 05:20
680

Класс Android Training, «Эффективное отображение растровых изображений», предлагает полезную информацию для понимания и устранения исключения `java.lang.OutOfMemoryError: размер растрового изображения превышает бюджет виртуальной машины загрузка растровых изображений.


Чтение размеров и типа растрового изображения

Класс BitmapFactory предоставляет несколько методов декодирования (decodeByteArray(), decodeFile(), decodeResource() и т. Д.) Для создания Bitmap из различных источников. Выберите наиболее подходящий метод декодирования на основе вашего источника данных изображения. Эти методы пытаются выделить память для созданного растрового изображения и поэтому могут легко привести к исключению OutOfMemory. Каждый тип метода декодирования имеет дополнительные сигнатуры, позволяющие указать параметры декодирования через класс BitmapFactory.Options. Установка для свойства inJustDecodeBounds значения true при декодировании позволяет избежать выделения памяти, возвращая null для объекта растрового изображения, но устанавливая outWidth, outHeight и outMimeType. Этот метод позволяет вам считывать размеры и тип данных изображения до построения (и выделения памяти) растрового изображения.

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;

Чтобы избежать исключений java.lang.OutOfMemory, проверяйте размеры растрового изображения перед его декодированием, если только вы не полностью доверяете источнику предоставить вам данные изображения предсказуемого размера, которые удобно помещаются в доступной памяти.


Загрузить уменьшенную версию в память

Теперь, когда размеры изображения известны, их можно использовать, чтобы решить, следует ли загружать в память полное изображение или вместо этого следует загружать субдискретизированную версию. Вот некоторые факторы, которые следует учитывать:

  • Расчетное использование памяти для загрузки полного изображения в память.
  • Объем памяти, который вы готовы выделить для загрузки этого изображения с учетом любых других требований к памяти вашего приложения.
  • Размеры целевого ImageView или компонента пользовательского интерфейса, в который должно быть загружено изображение.
  • Размер экрана и плотность экрана текущего устройства.

Например, не стоит загружать изображение 1024x768 пикселей в память, если оно в конечном итоге будет отображаться в виде эскиза 128x96 пикселей в ImageView.

Чтобы декодер использовал субдискретизацию изображения, загружая уменьшенную версию в память, установите для inSampleSize значение true в своем объекте BitmapFactory.Options. Например, изображение с разрешением 2048x1536, декодированное с inSampleSize из 4, дает растровое изображение приблизительно 512x384. Загрузка этого в память использует 0,75 МБ вместо 12 МБ для полного изображения (при условии, что конфигурация растрового изображения составляет ARGB_8888). Вот метод вычисления значения размера выборки, которое представляет собой степень двойки на основе целевой ширины и высоты:

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;
}

Примечание : вычисляется степень двойки, потому что декодер использует окончательное значение путем округления до ближайшей степени двойки в соответствии с inSampleSize документация.

Чтобы использовать этот метод, сначала декодируйте с inJustDecodeBounds, установленным на true, pass the options through and then decode again using the new inSampleSize value and inJustDecodeBounds set to false`:

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);
}

Этот метод упрощает загрузку растрового изображения произвольно большого размера в ImageView, который отображает миниатюру 100x100 пикселей, как показано в следующем примере кода:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));

Вы можете выполнить аналогичный процесс для декодирования растровых изображений из других источников, при необходимости заменив соответствующий метод BitmapFactory.decode*.

rene
7 сентября 2015 в 08:44
24

Этот ответ обсуждается на мета

FallenAngel
7 сентября 2015 в 10:02
10

Этот ответ (за исключением информации, полученной по ссылке) не предлагает большого решения в качестве ответа. Важные части ссылки следует объединить в вопрос.

Martijn Pieters
8 сентября 2015 в 18:44
8

Этот ответ, как и вопрос, и другие ответы - это вики сообщества, так что это то, что сообщество может исправить, отредактировав, что не требует вмешательства модератора.

avatar
28 июля 2020 в 23:43
0

Мне нужно было загрузить изображение большого размера в Bitmap, и я использовал Glide для решения этой проблемы. Сначала проверьте размер изображения с помощью BitmapFactory.Options, используя для inJustDecodeBounds значение true, затем используйте Glide для получения объекта Bitmap. Я использовал профилировщик для проверки использования памяти, но я не заметил всплеска памяти, как когда я использовал BitmapFactory.decodeFile (). Я пишу на C #, поскольку использую Xamarin, поэтому мне нужно немного подправить для использования в Java. Документация библиотеки Glide

private Bitmap DecodeFile(File file) {
        // Decode image size
        BitmapFactory.Options options = new BitmapFactory.Options();
        
        // setting inJustDecodeBounds to true won't load the file into memory, 
        // but gives you the actual file size.
        options.InJustDecodeBounds = true;
        BitmapFactory.decodeStream(new FileInputStream(file), null, options);
        int actualWidth = options.OutWidth;
        int actualHeight = options.OutHeight;
                
        var ratio = (double)actualHeight / actualWidth;

        // Default to 800 x 600. changed the size whatever you need.
        var desiredWidth = 800;
        var desiredHeight = 600;

        if(actualHeight > actualWidth)
        {
            var ratio = (double)actualWidth / actualHeight;
            var futureTarget = Glide.With(Application.Context)
                .AsBitmap()
                .Load(file)
                .SetDiskCacheStrategy(DiskCacheStrategy.None)
                .SkipMemoryCache(true)
                .Submit((int)(desiredWidth * ratio), desiredWidth);
            bitmap = futureTarget.Get() as Bitmap;
        }
        else
        {
            var ratio = (double)actualHeight / actualWidth;
            var futureTarget = Glide.With(Application.Context)
                .AsBitmap()
                .Load(file)
                .SetDiskCacheStrategy(DiskCacheStrategy.None)
                .SkipMemoryCache(true)
                .Submit(desiredWidth, (int)(desiredWidth * ratio));
            bitmap = futureTarget.Get() as Bitmap;
        }return bitmap;}
avatar
Prakash
16 июля 2020 в 12:39
2

Если вы ленивы, как я, вы можете начать использовать библиотеку Picasso для загрузки изображений.

Picasso.with(context).load(R.drawable.landing_screen).into(imageView1);
Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);
avatar
Deepya
29 августа 2019 в 14:48
-1

Добавьте следующие строки в свой файл manifest.xml:

<application

    android:hardwareAccelerated="false"
    android:largeHeap="true">

    <activity>
    </activity>

</application>
Vaibhav Vishal
1 ноября 2018 в 06:26
0

пожалуйста, отформатируйте свой код правильно, также это не похоже на действительный xml

avatar
25 июня 2019 в 05:33
0

Рекомендации по предотвращению утечек памяти или OOM для растрового изображения

  1. Не сохранять ссылки на растровые изображения в течение длительного времени на контекст / действие.
  2. Если вы используете большое растровое изображение в качестве фона или что-то в своем приложении, не загружайте полное изображение в основную память. Вы можете использовать свойство insample size растрового изображения, чтобы указать размер, необходимый вашему экрану.
  3. Очистить ссылку на растровое изображение, когда оно больше не используется.
avatar
8 марта 2019 в 13:45
2

Это позволит получить соответствующее растровое изображение и снизить потребление памяти

JAVA

Bitmap bm = null;

BitmapFactory.Options bmpOption = new BitmapFactory.Options();
bmpOption.inJustDecodeBounds = true;

FileInputStream fis = new FileInputStream(file);
BitmapFactory.decodeStream(fis, null, bmpOption);
fis.close();

int scale = 1;

if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE) {
    scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
       (double) Math.max(bmpOption.outHeight, bmpOption.outWidth)) / Math.log(0.5)));
}

BitmapFactory.Options bmpOption2 = new BitmapFactory.Options();
bmpOption2.inSampleSize = scale;
fis = new FileInputStream(file);
bm = BitmapFactory.decodeStream(fis, null, bmpOption2);
fis.close();

Котлин

val bm:Bitmap = null
val bmpOption = BitmapFactory.Options()
bmpOption.inJustDecodeBounds = true
val fis = FileInputStream(file)
BitmapFactory.decodeStream(fis, null, bmpOption)
fis.close()
val scale = 1
if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE)
{
  scale = Math.pow(2.0, Math.ceil((Math.log((IMAGE_MAX_SIZE / Math.max(bmpOption.outHeight, bmpOption.outWidth) as Double)) / Math.log(0.5))).toInt().toDouble()).toInt()
}
val bmpOption2 = BitmapFactory.Options()
bmpOption2.inSampleSize = scale
fis = FileInputStream(file)
bm = BitmapFactory.decodeStream(fis, null, bmpOption2)
fis.close()
avatar
Fraggle
7 марта 2019 в 16:24
81

У меня была такая же проблема, и я решил ее, отказавшись от функций BitmapFactory.decodeStream или decodeFile и вместо этого использовал BitmapFactory.decodeFileDescriptor

decodeFileDescriptor похоже, что он вызывает другие собственные методы, чем decodeStream / decodeFile.

В любом случае, сработало вот это (обратите внимание, что я добавил некоторые параметры, как некоторые из них были выше, но не это имело значение. Важен вызов BitmapFactory.decodeFileDescriptor вместо decodeStream или decodeFile ):

private void showImage(String path)   {

    Log.i("showImage","loading:"+path);
    BitmapFactory.Options bfOptions=new BitmapFactory.Options();
    bfOptions.inDither=false;                     //Disable Dithering mode
    bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
    bfOptions.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
    bfOptions.inTempStorage=new byte[32 * 1024]; 

    File file=new File(path);
    FileInputStream fs=null;
    try {
        fs = new FileInputStream(file);
    } catch (FileNotFoundException e) {
        //TODO do something intelligent
        e.printStackTrace();
    }

    try {
        if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
    } catch (IOException e) {
        //TODO do something intelligent
        e.printStackTrace();
    } finally{ 
        if(fs!=null) {
            try {
                fs.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget

    im.setImageBitmap(bm);
    //bm.recycle();
    bm=null;                        
}

Я думаю, что есть проблема с собственной функцией, используемой в decodeStream / decodeFile. Я подтвердил, что при использовании decodeFileDescriptor вызывается другой собственный метод. Также я прочитал, что «изображения (растровые изображения) распределяются не стандартным способом Java, а через собственные вызовы; выделения выполняются вне виртуальной кучи, но являются учитывается! "

PiyushMishra
11 октября 2011 в 12:32
2

тот же результат из памяти, на самом деле не имеет значения, какой метод вы используете, это зависит от количества байтов, которые вы держите для чтения данных, которые выдают из памяти.

avatar
vineet
10 сентября 2018 в 09:26
13

Этот код поможет загрузить большое растровое изображение из вытягиваемого

public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> {

    Context context;

    public BitmapUtilsTask(Context context) {
        this.context = context;
    }

    /**
     * Loads a bitmap from the specified url.
     * 
     * @param url The location of the bitmap asset
     * @return The bitmap, or null if it could not be loaded
     * @throws IOException
     * @throws MalformedURLException
     */
    public Bitmap getBitmap() throws MalformedURLException, IOException {       

        // Get the source image's dimensions
        int desiredWidth = 1000;
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;

        BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        int srcWidth = options.outWidth;
        int srcHeight = options.outHeight;

        // Only scale if the source is big enough. This code is just trying
        // to fit a image into a certain width.
        if (desiredWidth > srcWidth)
            desiredWidth = srcWidth;

        // Calculate the correct inSampleSize/scale value. This helps reduce
        // memory use. It should be a power of 2
        int inSampleSize = 1;
        while (srcWidth / 2 > desiredWidth) {
            srcWidth /= 2;
            srcHeight /= 2;
            inSampleSize *= 2;
        }
        // Decode with inSampleSize
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = inSampleSize;
        options.inScaled = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        options.inPurgeable = true;
        Bitmap sampledSrcBitmap;

        sampledSrcBitmap =  BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options);

        return sampledSrcBitmap;
    }

    /**
     * The system calls this to perform work in a worker thread and delivers
     * it the parameters given to AsyncTask.execute()
     */
    @Override
    protected Bitmap doInBackground(Object... item) {
        try { 
          return getBitmap();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
avatar
Rohit Sharma
9 марта 2018 в 11:42
14

Обычно размер кучи устройства Android составляет всего 16 МБ (зависит от устройства / ОС, см. «Размеры кучи»), если вы загружаете изображения, и они превышают размер 16 МБ, вместо использования растрового изображения будет выброшено исключение из памяти. для загрузки изображений с SD-карты, ресурсов или даже из сети попробуйте использовать getImageUri , для загрузки растрового изображения потребуется больше памяти, или вы можете установить растровое изображение равным нулю, если ваша работа была выполнена с этим растровым изображением.

Mahesh
13 марта 2013 в 04:44
1

И если setImageURI все еще получает исключение, обратитесь к этому coderhelper.com/questions/15377186/…

avatar
15 декабря 2017 в 18:38
3

Просмотрев все ответы, я был удивлен, увидев, что никто не упомянул API Glide для обработки изображений. Отличная библиотека, которая абстрагирует всю сложность управления растровыми изображениями. Вы можете быстро загружать и изменять размер изображений с помощью этой библиотеки и одной строки кода.

     Glide.with(this).load(yourImageResource).into(imageview);

Вы можете получить репозиторий здесь: https://github.com/bumptech/glide

Ezio
24 апреля 2018 в 13:13
2

Он не обрабатывает все сценарии. Glide - это не универсальное решение, мы используем Glide, но все еще сталкиваемся со многими сбоями OOM

CBMurphy
2 мая 2018 в 17:32
1

Я не говорил, что это универсальное решение. Я добавлял его в набор инструментов для тех, кто о нем не слышал.

avatar
20 июля 2017 в 15:46
2

Я использовал дескриптор файла декодирования, который у меня сработал:

 FileInputStream  fileInputStream = null;
        try {
            fileInputStream  = new FileInputStream(file);
             FileDescriptor fd = fileInputStream.getFD();
            Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612,
                    816);
            imageView.setImageBitmap(imageBitmap);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(fileInputStream != null){
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

Код для декодирования дискретного растрового изображения из файлового дескриптора:

 /**
     * Decode and sample down a bitmap from a file input stream to the requested width and height.
     *
     * @param fileDescriptor The file descriptor to read from
     * @param reqWidth       The requested width of the resulting bitmap
     * @param reqHeight      The requested height of the resulting bitmap
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
     * that are equal to or greater than the requested width and height
     */
    public static Bitmap decodeSampledBitmapFromDescriptor(
            FileDescriptor fileDescriptor, int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
    }

    /**
     * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding
     * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates
     * the closest inSampleSize that will result in the final decoded bitmap having a width and
     * height equal to or larger than the requested width and height. This implementation does not
     * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but
     * results in a larger bitmap which isn't as useful for caching purposes.
     *
     * @param options   An options object with out* params already populated (run through a decode*
     *                  method with inJustDecodeBounds==true
     * @param reqWidth  The requested width of the resulting bitmap
     * @param reqHeight The requested height of the resulting bitmap
     * @return The value to be used for 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) {

            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee a final image
            // with both dimensions larger than or equal to the requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

            // This offers some additional logic in case the image has a strange
            // aspect ratio. For example, a panorama may have a much larger
            // width than height. In these cases the total pixels might still
            // end up being too large to fit comfortably in memory, so we should
            // be more aggressive with sample down the image (=larger inSampleSize).

            final float totalPixels = width * height;

            // Anything more than 2x the requested pixels we'll sample down further
            final float totalReqPixelsCap = reqWidth * reqHeight * 2;

            while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) {
                inSampleSize++;
            }
        }
        return inSampleSize;
    }
avatar
hackjutsu
10 июня 2017 в 00:53
15

Эта проблема возникает только в эмуляторах Android. Я также столкнулся с этой проблемой в эмуляторе, но когда я зарегистрировал устройство, он работал нормально.

Пожалуйста, проверьте устройство. Его можно запустить на устройстве.

avatar
Torid
23 мая 2017 в 12:26
29

Здесь есть две проблемы ....

  • Растровая память находится не в куче виртуальной машины, а в собственной куче - см. BitmapFactory OOM, сводящий меня с ума
  • Сборка мусора для собственной кучи более ленивая, чем для кучи виртуальной машины, поэтому вам нужно быть довольно агрессивным в отношении выполнения bitmap.recycle и bitmap = null каждый раз, когда вы проходите через Activity onPause или onDestroy
avatar
RayHaque
23 мая 2017 в 10:31
35

Похоже, что это очень давняя проблема с множеством разных объяснений. Я воспользовался советом из двух наиболее распространенных представленных здесь ответов, но ни один из них не решил мои проблемы с виртуальной машиной, утверждающей, что она не может позволить себе байты для выполнения части процесса декодирования . Покопавшись, я узнал, что настоящая проблема здесь в том, что процесс декодирования уходит из кучи NATIVE .

См. Здесь: BitmapFactory OOM сводит меня с ума

Это привело меня к другому обсуждению, где я нашел еще пару решений этой проблемы. Один из них - позвонить по номеру System.gc(); вручную после отображения вашего изображения. Но на самом деле это заставляет ваше приложение использовать БОЛЬШЕ памяти, чтобы уменьшить внутреннюю кучу. Лучшим решением для выпуска 2.0 (Donut) является использование параметра BitmapFactory «inPurgeable». Поэтому я просто добавил o2.inPurgeable=true; сразу после o2.inSampleSize=scale;.

Подробнее по этой теме здесь: Ограничение кучи памяти только 6M?

Теперь, сказав все это, я тоже полный болван с Java и Android. Так что, если вы думаете, что это ужасный способ решить эту проблему, вы, вероятно, правы. ;-) Но это творит чудеса для меня, и я обнаружил, что сейчас невозможно запустить виртуальную машину из кеш-памяти. Единственный недостаток, который я могу найти, заключается в том, что вы уничтожаете кешированное нарисованное изображение. Это означает, что если вы вернетесь ВПРАВО к этому изображению, вы будете его каждый раз перерисовывать. Что касается того, как работает мое приложение, это не проблема. Ваш пробег может отличаться.

Artem Russakovskii
4 октября 2011 в 20:10
0

inPurgeable исправил OOM для меня.

avatar
Raju Gujarati
27 сентября 2016 в 09:32
13

Такой OutofMemoryException нельзя полностью решить, позвонив по телефону System.gc() и т. Д.

Ссылаясь на Жизненный цикл активности

Состояния активности определяются самой ОС в зависимости от использования памяти для каждого процесса и приоритета каждого процесса.

Вы можете учитывать размер и разрешение для каждого из используемых растровых изображений. Рекомендую уменьшить размер, пересчитать разрешение в меньшее, обратиться к оформлению галерей (одна маленькая картинка PNG и одна оригинальная картинка).

avatar
Ephraim
2 июня 2016 в 12:03
236

Я пришел из опыта работы с iOS и был разочарован, обнаружив проблему с такой простой задачей, как загрузка и отображение изображения. В конце концов, все, у кого есть эта проблема, пытаются отображать изображения разумного размера. В любом случае, вот два изменения, которые устранили мою проблему (и сделали мое приложение очень отзывчивым).

1) Каждый раз, когда вы выполняете BitmapFactory.decodeXYZ(), не забудьте передать BitmapFactory.Options с inPurgeable, установленным на true (и предпочтительно с inInputShareable, также установленным на <true890>).

2) НИКОГДА не используйте Bitmap.createBitmap(width, height, Config.ARGB_8888). Я имею в виду НИКОГДА! У меня никогда не было такой штуки, которая не вызывала бы ошибку памяти после нескольких проходов. Никакие суммы recycle(), System.gc(), что угодно, не помогло. Это всегда вызывает исключение. Еще один способ, который на самом деле работает, - это иметь фиктивное изображение в ваших чертежах (или другое растровое изображение, которое вы декодировали с помощью шага 1 выше), масштабировать его так, как вы хотите, а затем манипулировать полученным растровым изображением (например, передавать его на холст). для большего удовольствия). Итак, вместо этого вы должны использовать: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Если по какой-либо причине вы ДОЛЖНЫ использовать метод создания грубой силы, то хотя бы передайте Config.ARGB_4444.

Это почти гарантированно сэкономит вам часы, если не дни. Все, что говорят о масштабировании изображения и т. Д., На самом деле не работает (если только вы не считаете неправильным размером или ухудшенным изображением решение).

Jan-Terje Sørensen
10 февраля 2012 в 21:01
23

BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; и Bitmap.createScaledBitmap(srcBitmap, width, height, false); решили мою проблему с исключением нехватки памяти на android 4.0.0. Спасибо друг!

rOrlig
1 марта 2012 в 02:26
6

В вызове Bitmap.createScaledBitmap () вам, вероятно, следует использовать true в качестве параметра флага. В противном случае качество изображения не будет плавным при увеличении масштаба. Проверьте эту ветку coderhelper.com/questions/2895065/…

Yevgeny Simkin
3 сентября 2012 в 09:47
12

Это действительно потрясающий совет. Хотел бы я дать вам дополнительный +1 за то, что вы привлекли Google к ответственности за эту удивительно безумную ошибку. Я имею в виду ... если это не ошибка, тогда в документации действительно должны быть несколько серьезно мигающих неоновых вывесок с надписью «ЭТО КАК ВЫ ОБРАБАТЫВАЕТЕ ФОТОГРАФИИ», потому что я боролся с этим в течение 2 лет и только сейчас нашел этот пост. Отличная находка.

Denis Kniazhev
31 декабря 2014 в 12:53
11

Начиная с Lollipop, BitmapFactory.Options.inPurgeable и BitmapFactory.Options.inInputShareable устарели developer.android.com/reference/android/graphics/…

avatar
Himanshu Mori
22 января 2016 в 12:18
38

к сожалению , если ничего из вышеперечисленного не работает, добавьте это в свой файл манифеста . Внутри приложения тег

 <application
         android:largeHeap="true"
Stealth Rabbi
28 марта 2016 в 15:49
2

Вы можете объяснить, что это на самом деле? Простое указание людям добавить это не помогает.

Prakash
12 июля 2017 в 14:42
3

Это очень плохое решение. По сути, вы не пытаетесь решить проблему. Вместо этого просите систему Android выделить больше места в куче для вашего приложения. Это будет иметь очень плохие последствия для вашего приложения, например, ваше приложение потребляет много энергии от батареи, поскольку GC должен проходить через большое пространство кучи для очистки памяти, а также производительность вашего приложения будет медленнее.

Himanshu Mori
13 июля 2017 в 13:00
3

тогда почему android позволяет нам добавить этот android: largeHeap = "true" в наш манифест? Теперь вы бросаете вызов Android.

abhiTronix
29 июля 2020 в 04:16
0

@HimanshuMori Вы можете пересмотреть свое решение об использовании android: largeHeap = "true". См. Этот ответ coderhelper.com/a/30930239/10158117 или любой другой ответ в этой теме. Это может помочь вам понять, что вы делаете неправильно.

avatar
Fedor
28 апреля 2015 в 21:54
905

Чтобы исправить ошибку OutOfMemory, вы должны сделать что-то вроде этого:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap preview_bitmap = BitmapFactory.decodeStream(is, null, options);

Эта опция inSampleSize снижает потребление памяти.

Вот полный метод. Сначала он считывает размер изображения без декодирования самого содержимого. Затем он находит наилучшее значение inSampleSize, оно должно быть степенью 2, и, наконец, изображение декодируется.

// Decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
    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_SIZE=70;

        // Find the correct scale value. It should be the power of 2.
        int scale = 1;
        while(o.outWidth / scale / 2 >= REQUIRED_SIZE && 
              o.outHeight / scale / 2 >= REQUIRED_SIZE) {
            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;
}
Mirko N.
28 мая 2010 в 11:59
32

Обратите внимание, что 10 может быть не лучшим значением для inSampleSize, однако в документации предлагается использовать степени 2.

Flynn81
8 июля 2010 в 15:19
71

Я столкнулся с той же проблемой, что и Chrispix, но я не думаю, что решение здесь действительно решает проблему, а скорее обходит ее. Изменение размера выборки уменьшает объем используемой памяти (за счет качества изображения, что, вероятно, нормально для предварительного просмотра изображения), но это не предотвратит исключение, если декодируется достаточно большой поток изображения, или если несколько потоков изображений расшифровано. Если я найду лучшее решение (а его может и не быть), я опубликую здесь ответ.

stealthcopter
8 августа 2010 в 22:43
5

Вам нужен только соответствующий размер, соответствующий экрану по плотности пикселей, для увеличения и, таким образом, вы можете взять образец изображения с более высокой плотностью.

sgarman
6 апреля 2011 в 18:27
2

Кто-нибудь может объяснить, чему соответствует REQUIRED_SIZE? Это пиксели с одной стороны, провалы? Спасибо!

Fedor
7 апреля 2011 в 00:35
5

REQUIRED_SIZE - новый размер, до которого нужно масштабировать.

Dave
19 апреля 2012 в 15:46
2

Поскольку вы используете степень двойки, вместо scale * = 2, вы должны использовать scale >> 2. Для разделения вы можете использовать scale << 2.

Stefan Anca
26 августа 2012 в 21:46
3

Отличный ответ! Но как получить REQUIRED_SIZE динамически во время выполнения (обслуживая дисплеи разного размера)? Это необходимо сделать после того, как View будет отрисован, но до того, как растровое изображение будет развернуто.

user1106888
8 июля 2013 в 20:21
9

это решение помогло мне, но качество изображения ужасное. Я использую viewfilpper для отображения изображений какие-либо предложения?

Nguyen Minh Binh
11 октября 2013 в 03:20
2

Я обычно ставлю options.inPreferredConfig = Bitmap.Config.ALPHA_8;. С этой настройкой каждый пиксель будет сохранен по 1 байту вместо 4 байтов по умолчанию.

Travis
2 января 2014 в 15:53
3

@Dopyiii FYI * = 2 эквивалентно >> 1, а не >> 2

avatar
Exceptional
24 апреля 2015 в 20:36
7
BitmapFactory.Options options = new Options();
options.inSampleSize = 32;
//img = BitmapFactory.decodeFile(imageids[position], options);

Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options);
Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true);
theImage.recycle();
theImage = null;
System.gc();
//ivlogdp.setImageBitmap(img);
Runtime.getRuntime().gc();
avatar
Gaurav Pansheriya
24 апреля 2015 в 20:36
14

используйте этот код для каждого изображения при выборе из SdCard или рисования для преобразования растрового объекта.

Resources res = getResources();
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
@SuppressWarnings("deprecation")
int width = display.getWidth();
@SuppressWarnings("deprecation")
int height = display.getHeight();
try {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    bitmap = Bitmap.createScaledBitmap(BitmapFactory
        .decodeFile(ImageData_Path.get(img_pos).getPath()),
        width, height, true);
} catch (OutOfMemoryError e) {
    if (bitmap != null) {
        bitmap.recycle();
        bitmap = null;
        System.gc();
    }
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inPreferredConfig = Config.RGB_565;
    options.inSampleSize = 1;
    options.inPurgeable = true;
    bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos)
        .getPath().toString(), options), width, height,true);
}
return bitmap;

используйте путь к изображению в ImageData_Path.get (img_pos) .getPath () .

avatar
Prerna
24 апреля 2015 в 20:34
30

Я решил ту же проблему следующим образом.

Bitmap b = null;
Drawable d;
ImageView i = new ImageView(mContext);
try {
    b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565);
    b.eraseColor(0xFFFFFFFF);
    Rect r = new Rect(0, 0,320 , 424);
    Canvas c = new Canvas(b);
    Paint p = new Paint();
    p.setColor(0xFFC0C0C0);
    c.drawRect(r, p);
    d = mContext.getResources().getDrawable(mImageIds[position]);
    d.setBounds(r);
    d.draw(c);

    /*   
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inTempStorage = new byte[128*1024];
        b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2);
        o2.inSampleSize=16;
        o2.inPurgeable = true;
    */
} catch (Exception e) {

}
i.setImageBitmap(b);
avatar
Luke Taylor
24 апреля 2015 в 20:32
29

У меня это сработало!

public Bitmap readAssetsBitmap(String filename) throws IOException {
    try {
        BitmapFactory.Options options = new BitmapFactory.Options(); 
        options.inPurgeable = true;
        Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
        if(bitmap == null) {
            throw new IOException("File cannot be opened: It's value is null");
        } else {
            return bitmap;
        }
    } catch (IOException e) {
        throw new IOException("File cannot be opened: " + e.getMessage());
    }
}
avatar
Chrispix
24 апреля 2015 в 20:31
44

Я сделал следующее, чтобы на лету взять изображение и изменить его размер. Надеюсь, это поможет

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    
Fedor
5 января 2010 в 06:51
27

Этот подход масштабирует растровое изображение. Но это не решает проблему OutOfMemory, потому что полное растровое изображение все равно декодируется.

Chrispix
22 сентября 2010 в 17:19
6

Я посмотрю, смогу ли я взглянуть на свой старый код, но я думаю, что он решил мои проблемы с нехваткой памяти. Дважды проверим мой старый код.

NoBugs
21 мая 2013 в 06:21
3

По крайней мере, в этом примере похоже, что вы не сохраняете ссылку на полное растровое изображение, тем самым экономя память.

avatar
Arsalan
21 апреля 2015 в 19:29
33

Используйте этот bitmap.recycle(); Это помогает без каких-либо проблем с качеством изображения.

Artem Russakovskii
28 сентября 2011 в 01:01
12

Согласно API, вызов recycle () не требуется.

avatar
18 декабря 2014 в 09:40
7

Похоже, изображения, которые вы использовали, имеют очень большой размер. Поэтому некоторые старые устройства сбои из-за переполнения памяти кучи. файл манифеста в теге приложения или уменьшите размер растрового изображения, используя приведенный ниже код.

Bitmap bMap;
BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InSampleSize = 8;
bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

вы также можете указать 4 или 12 или 16, чтобы уменьшить размер растрового изображения

avatar
Anto Binish Kaspar
15 сентября 2014 в 10:05
95

Это известная ошибка, а не из-за больших файлов. Поскольку Android кэширует чертежи, ему не хватает памяти после использования нескольких изображений. Но я нашел альтернативный способ, пропустив систему кеширования по умолчанию для Android.

Решение : Переместите изображения в папку «assets» и используйте следующую функцию, чтобы получить BitmapDrawable:

public static Drawable getAssetImage(Context context, String filename) throws IOException {
    AssetManager assets = context.getResources().getAssets();
    InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png")));
    Bitmap bitmap = BitmapFactory.decodeStream(buffer);
    return new BitmapDrawable(context.getResources(), bitmap);
}
avatar
Wroclai
1 августа 2014 в 20:25
66

В последнее время я видел много вопросов об исключениях OOM и кешировании. В руководстве разработчика есть действительно хорошая статья по этому поводу, но некоторые имеют тенденцию терпеть неудачу при ее реализации подходящим образом.

Поэтому я написал пример приложения, демонстрирующего кэширование в среде Android. Эта реализация еще не получила OOM.

Посмотрите в конце этого ответа ссылку на исходный код.

Требования:

  • Android API 2.1 или выше (мне просто не удалось получить доступную память для приложения в API 1.6 - это единственный фрагмент кода, который не работает в API 1.6)
  • Пакет поддержки Android

Screenshot

Особенности:

  • Сохраняет кэш, если есть изменение ориентации , используя одноэлементный
  • Используйте одну восьмую назначенной памяти приложения для кеша (измените, если хотите)
  • Большие растровые изображения масштабируются (вы можете определить максимальное количество пикселей, которое вы хотите разрешить)
  • Управляет доступным интернет-соединением перед загрузкой растровых изображений
  • Убедитесь, что вы создаете только одну задачу для каждой строки
  • Если вы выбрасываете ListView, он просто не будет загружать растровые изображения между

Сюда не входят:

  • Кэширование диска. В любом случае это должно быть легко реализовать - просто укажите на другую задачу, которая захватывает растровые изображения с диска

Пример кода:

Загружаемые изображения являются изображениями (75x75) с Flickr. Однако укажите любые URL-адреса изображений, которые вы хотите обработать, и приложение уменьшит его, если он превысит максимум. В этом приложении URL-адреса просто находятся в массиве String.

LruCache имеет хороший способ работы с растровыми изображениями. Однако в этом приложении я помещаю экземпляр LruCache в другой класс кеша, который я создал, чтобы сделать приложение более выполнимым.

Критический материал Cache.java (наиболее важным является метод loadBitmap()):

public Cache(int size, int maxWidth, int maxHeight) {
    // Into the constructor you add the maximum pixels
    // that you want to allow in order to not scale images.
    mMaxWidth = maxWidth;
    mMaxHeight = maxHeight;

    mBitmapCache = new LruCache<String, Bitmap>(size) {
        protected int sizeOf(String key, Bitmap b) {
            // Assuming that one pixel contains four bytes.
            return b.getHeight() * b.getWidth() * 4;
        }
    };

    mCurrentTasks = new ArrayList<String>();    
}

/**
 * Gets a bitmap from cache. 
 * If it is not in cache, this method will:
 * 
 * 1: check if the bitmap url is currently being processed in the
 * BitmapLoaderTask and cancel if it is already in a task (a control to see
 * if it's inside the currentTasks list).
 * 
 * 2: check if an internet connection is available and continue if so.
 * 
 * 3: download the bitmap, scale the bitmap if necessary and put it into
 * the memory cache.
 * 
 * 4: Remove the bitmap url from the currentTasks list.
 * 
 * 5: Notify the ListAdapter.
 * 
 * @param mainActivity - Reference to activity object, in order to
 * call notifyDataSetChanged() on the ListAdapter.
 * @param imageKey - The bitmap url (will be the key).
 * @param imageView - The ImageView that should get an
 * available bitmap or a placeholder image.
 * @param isScrolling - If set to true, we skip executing more tasks since
 * the user probably has flinged away the view.
 */
public void loadBitmap(MainActivity mainActivity, 
        String imageKey, ImageView imageView,
        boolean isScrolling) {
    final Bitmap bitmap = getBitmapFromCache(imageKey); 

    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);
        if (!isScrolling && !mCurrentTasks.contains(imageKey) && 
                mainActivity.internetIsAvailable()) {
            BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
                    mainActivity.getAdapter());
            task.execute();
        }
    } 
}

Вам не нужно ничего редактировать в файле Cache.java, если вы не хотите реализовать кэширование диска.

Важный материал MainActivity.java:

public void onScrollStateChanged(AbsListView view, int scrollState) {
    if (view.getId() == android.R.id.list) {
        // Set scrolling to true only if the user has flinged the       
        // ListView away, hence we skip downloading a series
        // of unnecessary bitmaps that the user probably
        // just want to skip anyways. If we scroll slowly it
        // will still download bitmaps - that means
        // that the application won't wait for the user
        // to lift its finger off the screen in order to
        // download.
        if (scrollState == SCROLL_STATE_FLING) {
            mIsScrolling = true;
        } else {
            mIsScrolling = false;
            mListAdapter.notifyDataSetChanged();
        }
    } 
}

// Inside ListAdapter...
@Override
public View getView(final int position, View convertView, ViewGroup parent) {           
    View row = convertView;
    final ViewHolder holder;

    if (row == null) {
        LayoutInflater inflater = getLayoutInflater();
        row = inflater.inflate(R.layout.main_listview_row, parent, false);  
        holder = new ViewHolder(row);
        row.setTag(holder);
    } else {
        holder = (ViewHolder) row.getTag();
    }   

    final Row rowObject = getItem(position);

    // Look at the loadBitmap() method description...
    holder.mTextView.setText(rowObject.mText);      
    mCache.loadBitmap(MainActivity.this,
            rowObject.mBitmapUrl, holder.mImageView,
            mIsScrolling);  

    return row;
}

getView() звонят очень часто. Обычно не рекомендуется загружать изображения туда, если мы не реализовали проверку, гарантирующую, что мы не будем запускать бесконечное количество потоков на строку. Cache.java проверяет, присутствует ли уже rowObject.mBitmapUrl в задаче, и если да, то другую запускать не будет. Следовательно, мы, скорее всего, не превышаем ограничение рабочей очереди из пула AsyncTask.

Загрузить:

Вы можете загрузить исходный код со страницы https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip.


Последние слова:

Я тестировал это в течение нескольких недель, я еще не получил ни одного исключения OOM. Я тестировал это на эмуляторе, на моем Nexus One и на моем Nexus S. Я тестировал URL-адреса изображений, которые содержат изображения с качеством HD. Единственное узкое место - это то, что для загрузки требуется больше времени.

Есть только один возможный сценарий, при котором я могу представить, что OOM появится, и это если мы загрузим много действительно больших изображений, и до того, как они будут масштабированы и помещены в кеш, они одновременно займут больше памяти и вызовут ООМ. Но в любом случае это даже не идеальная ситуация, и ее, скорее всего, не удастся решить более реальным способом.

Об ошибках сообщайте в комментариях! :-)

avatar
Thomas Vervest
20 марта 2014 в 06:18
378

Я немного улучшил код Федора. Он в основном делает то же самое, но без (на мой взгляд) уродливого цикла while, и он всегда приводит к степени двойки. Престижность Федору за оригинальное решение, я застрял, пока не нашел его, а потом смог сделать это :)

 private Bitmap decodeFile(File f){
    Bitmap b = null;

        //Decode image size
    BitmapFactory.Options o = new BitmapFactory.Options();
    o.inJustDecodeBounds = true;

    FileInputStream fis = new FileInputStream(f);
    BitmapFactory.decodeStream(fis, null, o);
    fis.close();

    int scale = 1;
    if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) {
        scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / 
           (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5)));
    }

    //Decode with inSampleSize
    BitmapFactory.Options o2 = new BitmapFactory.Options();
    o2.inSampleSize = scale;
    fis = new FileInputStream(f);
    b = BitmapFactory.decodeStream(fis, null, o2);
    fis.close();

    return b;
}
Fedor
24 августа 2010 в 01:32
41

Да ты прав пока не так красиво. Я просто постарался дать понять всем. Спасибо за ваш код.

DougW
13 октября 2010 в 07:42
12

@Thomas Vervest - С этим кодом большая проблема. ^ не возводит 2 в степень, а возвращает 2 к результату. Вам нужен Math.pow (2.0, ...). В остальном это выглядит хорошо.

Thomas Vervest
26 октября 2010 в 09:20
7

О, это очень хорошо! Моя беда, сразу поправлю, спасибо за ответ!

matsev
15 февраля 2011 в 08:21
9

Вы создаете два новых FileInputStreams, по одному для каждого вызова BitmapFactory.decodeStream(). Разве вам не нужно сохранять ссылку на каждый из них, чтобы их можно было закрыть в блоке finally?

Thomas Vervest
12 мая 2013 в 21:32
2

@Babibu В документации не указано, что поток закрыт для вас, поэтому я предполагаю, что он все равно должен быть закрыт. Интересное и связанное с этим обсуждение можно найти здесь. Обратите внимание на комментарий Адриана Смита, который имеет прямое отношение к нашей дискуссии.

avatar
21 декабря 2013 в 11:10
4

Чтобы исправить OutOfMemory, вы должны сделать что-то подобное, попробуйте этот код

public Bitmap loadBitmap(String URL, BitmapFactory.Options options) {
                Bitmap bitmap = null;
                InputStream in = null;
                options.inSampleSize=4;
                try {
                    in = OpenHttpConnection(URL);
                    Log.e("In====>", in+"");
                    bitmap = BitmapFactory.decodeStream(in, null, options);
                    Log.e("URL====>", bitmap+"");

                    in.close();
                } catch (IOException e1) {
                }
                return bitmap;
            }

и

try {
                    BitmapFactory.Options bmOptions;
                    bmOptions = new BitmapFactory.Options();
                    bmOptions.inSampleSize = 1;
                    if(studentImage != null){
                        galleryThumbnail= loadBitmap(IMAGE_URL+studentImage, bmOptions);    
                    }

                    galleryThumbnail=getResizedBitmap(galleryThumbnail, imgEditStudentPhoto.getHeight(), imgEditStudentPhoto.getWidth());
                    Log.e("Image_Url==>",IMAGE_URL+studentImage+"");

                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
avatar
5 декабря 2013 в 10:22
4

Привет, перейдите по ссылке http://developer.android.com/training/displaying-bitmaps/index.html

или просто попробуйте получить растровое изображение с помощью данной функции

private Bitmap decodeBitmapFile (File f) {
    Bitmap bitmap = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options ();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream (f);
        try {
            BitmapFactory.decodeStream (fis, null, o);
        } finally {
            fis.close ();
        }

        int scale = 1;
        for (int size = Math.max (o.outHeight, o.outWidth); 
            (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);

        // Decode with input-stram SampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options ();
        o2.inSampleSize = scale;
        fis = new FileInputStream (f);
        try {
            bitmap  = BitmapFactory.decodeStream (fis, null, o2);
        } finally {
            fis.close ();
        }
    } catch (IOException e) {
    }
    return bitmap ;
}
avatar
31 августа 2013 в 03:04
14

Все решения здесь требуют установки IMAGE_MAX_SIZE. Это ограничивает устройства с более мощным оборудованием, и если размер изображения слишком мал, он выглядит некрасиво на экране HD.

Я разработал решение, которое работает с моим Samsung Galaxy S3 и несколькими другими устройствами, в том числе менее мощными, с лучшим качеством изображения при использовании более мощного устройства.

Суть в том, чтобы вычислить максимальный объем памяти, выделенной для приложения на определенном устройстве, а затем установить минимально возможный масштаб без превышения этой памяти. Вот код:

public static Bitmap decodeFile(File f)
{
    Bitmap b = null;
    try
    {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream(f);
        try
        {
            BitmapFactory.decodeStream(fis, null, o);
        }
        finally
        {
            fis.close();
        }

        // In Samsung Galaxy S3, typically max memory is 64mb
        // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap
        // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb
        // We try use 25% memory which equals to 16mb maximum for one bitmap
        long maxMemory = Runtime.getRuntime().maxMemory();
        int maxMemoryForImage = (int) (maxMemory / 100 * 25);

        // Refer to
        // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
        // A full screen GridView filled with images on a device with
        // 800x480 resolution would use around 1.5MB (800*480*4 bytes)
        // When bitmap option's inSampleSize doubled, pixel height and
        // weight both reduce in half
        int scale = 1;
        while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage)
        scale *= 2;

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        fis = new FileInputStream(f);
        try
        {
            b = BitmapFactory.decodeStream(fis, null, o2);
        }
        finally
        {
            fis.close();
        }
    }
    catch (IOException e)
    {
    }
    return b;
}

Я установил максимальную память, используемую этим растровым изображением, равной 25% от максимальной выделенной памяти, вам может потребоваться настроить это в соответствии с вашими потребностями и убедиться, что это растровое изображение очищено и не остается в памяти, когда вы закончите используй это. Обычно я использую этот код для выполнения поворота изображения (исходный и целевой растровые изображения), поэтому моему приложению необходимо одновременно загружать в память 2 растровых изображения, а 25% дает мне хороший буфер без нехватки памяти при выполнении поворота изображения.

Надеюсь, это поможет кому-то там ..

avatar
sunil
29 августа 2013 в 06:40
4

используйте эту концепцию, это поможет вам. После этого установите растровое изображение в виде изображения

public static Bitmap convertBitmap(String path)   {

        Bitmap bitmap=null;
        BitmapFactory.Options bfOptions=new BitmapFactory.Options();
        bfOptions.inDither=false;                     //Disable Dithering mode
        bfOptions.inPurgeable=true;                   //Tell to gc that whether it needs free memory, the Bitmap can be cleared
        bfOptions.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
        bfOptions.inTempStorage=new byte[32 * 1024]; 


        File file=new File(path);
        FileInputStream fs=null;
        try {
            fs = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        try {
            if(fs!=null)
            {
                bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions);
            }
            } catch (IOException e) {

            e.printStackTrace();
        } finally{ 
            if(fs!=null) {
                try {
                    fs.close();
                } catch (IOException e) {

                    e.printStackTrace();
                }
            }
        }

        return bitmap;
    }

Если вы хотите создать небольшое изображение из большого изображения с высотой и шириной, например 60 и 60, и быстро прокрутить список, используйте эту концепцию

public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth,
            int reqHeight) {

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth,
                reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        Bitmap bmp = BitmapFactory.decodeFile(path, options);
        return bmp;
        }

    public static int calculateInSampleSize(BitmapFactory.Options options,
            int reqWidth, int reqHeight) {

        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;
        }

Надеюсь, это вам очень поможет.

Вы можете получить помощь с сайта разработчика Здесь

avatar
14 июля 2013 в 09:08
13

Мои 2 цента: я решил свои ошибки OOM с растровыми изображениями:

а) масштабирование моих изображений в 2 раза

б) с использованием библиотеки Picasso в моем настраиваемом адаптере для ListView с одним вызовом в getView, например: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

avatar
29 марта 2013 в 06:56
-10

После установки растрового изображения для просмотра изображения переработайте его следующим образом:

bitmap.recycle();
bitmap=null;
avatar
Pascal
13 февраля 2013 в 06:43
22

Отличные ответы здесь, но я хотел полностью пригодный для использования класс для решения этой проблемы ... поэтому я сделал один.

Вот мой класс BitmapHelper , который является доказательством OutOfMemoryError :-)

import java.io.File;
import java.io.FileInputStream;

import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;

public class BitmapHelper
{

    //decodes image and scales it to reduce memory consumption
    public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty)
    {
        try
        {
            //Decode image size
            BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options();
            bitmapSizeOptions.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions);

            // load image using inSampleSize adapted to required image size
            BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options();
            bitmapDecodeOptions.inTempStorage = new byte[16 * 1024];
            bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false);
            bitmapDecodeOptions.inPurgeable = true;
            bitmapDecodeOptions.inDither = !quickAndDirty;
            bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888;

            Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions);

            // scale bitmap to mathc required size (and keep aspect ratio)

            float srcWidth = (float) bitmapDecodeOptions.outWidth;
            float srcHeight = (float) bitmapDecodeOptions.outHeight;

            float dstWidth = (float) requiredWidth;
            float dstHeight = (float) requiredHeight;

            float srcAspectRatio = srcWidth / srcHeight;
            float dstAspectRatio = dstWidth / dstHeight;

            // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap'
            // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid
            // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@416ee7d8
            // I do not excatly understand why, but this way it's OK

            boolean recycleDecodedBitmap = false;

            Bitmap scaledBitmap = decodedBitmap;
            if (srcAspectRatio < dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth)));
                // will recycle recycleDecodedBitmap
                recycleDecodedBitmap = true;
            }
            else if (srcAspectRatio > dstAspectRatio)
            {
                scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight);
                recycleDecodedBitmap = true;
            }

            // crop image to match required image size

            int scaledBitmapWidth = scaledBitmap.getWidth();
            int scaledBitmapHeight = scaledBitmap.getHeight();

            Bitmap croppedBitmap = scaledBitmap;

            if (scaledBitmapWidth > requiredWidth)
            {
                int xOffset = (scaledBitmapWidth - requiredWidth) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }
            else if (scaledBitmapHeight > requiredHeight)
            {
                int yOffset = (scaledBitmapHeight - requiredHeight) / 2;
                croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight);
                scaledBitmap.recycle();
            }

            if (recycleDecodedBitmap)
            {
                decodedBitmap.recycle();
            }
            decodedBitmap = null;

            scaledBitmap = null;
            return croppedBitmap;
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return null;
    }

    /**
     * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling)
     * 
     * @param requiredWidth
     * @param requiredHeight
     * @param powerOf2
     *            weither we want a power of 2 sclae or not
     * @return
     */
    public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2)
    {
        int inSampleSize = 1;

        // Raw height and width of image
        final int srcHeight = options.outHeight;
        final int srcWidth = options.outWidth;

        if (powerOf2)
        {
            //Find the correct scale value. It should be the power of 2.

            int tmpWidth = srcWidth, tmpHeight = srcHeight;
            while (true)
            {
                if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight)
                    break;
                tmpWidth /= 2;
                tmpHeight /= 2;
                inSampleSize *= 2;
            }
        }
        else
        {
            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) srcHeight / (float) dstHeight);
            final int widthRatio = Math.round((float) srcWidth / (float) dstWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }

        return inSampleSize;
    }

    public static Bitmap drawableToBitmap(Drawable drawable)
    {
        if (drawable instanceof BitmapDrawable)
        {
            return ((BitmapDrawable) drawable).getBitmap();
        }

        Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);

        return bitmap;
    }

    public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight)
    {
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;

        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // RECREATE THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
        return resizedBitmap;
    }

}
avatar
6 февраля 2013 в 14:53
17

В одном из моих приложений мне нужно сделать снимок из Camera/Gallery. Если пользователь щелкает изображение с камеры (может быть 2MP, 5MP или 8MP), размер изображения изменяется от kB с до MB с. Если размер изображения меньше (или до 1-2 МБ), указанный выше код работает нормально, но если у меня изображение размером более 4 МБ или 5 МБ, тогда OOM входит в кадр :(

затем я работал над решением этой проблемы и, наконец, я сделал следующее улучшение кода Федора (вся заслуга Федора за такое хорошее решение) :)

private Bitmap decodeFile(String fPath) {
    // Decode image size
    BitmapFactory.Options opts = new BitmapFactory.Options();
    /*
     * If set to true, the decoder will return null (no bitmap), but the
     * out... fields will still be set, allowing the caller to query the
     * bitmap without having to allocate the memory for its pixels.
     */
    opts.inJustDecodeBounds = true;
    opts.inDither = false; // Disable Dithering mode
    opts.inPurgeable = true; // Tell to gc that whether it needs free
                                // memory, the Bitmap can be cleared
    opts.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

    BitmapFactory.decodeFile(fPath, opts);

    // The new size we want to scale to
    final int REQUIRED_SIZE = 70;

    // Find the correct scale value. 
    int scale = 1;

    if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) opts.outHeight
                / (float) REQUIRED_SIZE);
        final int widthRatio = Math.round((float) opts.outWidth
                / (float) REQUIRED_SIZE);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        scale = heightRatio < widthRatio ? heightRatio : widthRatio;//
    }

    // Decode bitmap with inSampleSize set
    opts.inJustDecodeBounds = false;

    opts.inSampleSize = scale;

    Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy(
            Bitmap.Config.RGB_565, false);

    return bm;

}

Надеюсь, это поможет друзьям, столкнувшимся с той же проблемой!

для получения дополнительной информации см. этот

avatar
coocood
9 января 2013 в 12:45
73

Я думаю, что лучший способ избежать OutOfMemoryError - это посмотреть ему в лицо и понять его.

Я создал приложение , чтобы намеренно вызвать OutOfMemoryError и контролировать использование памяти.

Проведя множество экспериментов с этим приложением, я сделал следующие выводы:

Сначала я расскажу о версиях SDK до Honey Comb.

  1. Растровое изображение хранится в собственной куче, но оно будет собирать мусор автоматически, вызов recycle () не нужен.

  2. Если {размер кучи виртуальной машины} + {выделенная внутренняя память кучи}> = {ограничение размера кучи виртуальной машины для устройства} и вы пытаетесь создать растровое изображение, будет выдано OOM.

    ВНИМАНИЕ: подсчитывается размер кучи виртуальной машины, а не выделенная память виртуальной машины.

  3. Размер кучи виртуальной машины никогда не будет уменьшаться после увеличения, даже если уменьшена выделенная память виртуальной машины.

  4. Таким образом, вы должны поддерживать пиковую память виртуальной машины как можно меньше, чтобы размер кучи виртуальной машины не увеличивался слишком большим для сохранения доступной памяти для растровых изображений.

  5. Ручной вызов System.gc () бессмысленен, система вызовет его сначала, прежде чем пытаться увеличить размер кучи.

  6. Собственный размер кучи тоже никогда не будет уменьшаться, но он не учитывается для OOM, поэтому не нужно об этом беспокоиться.

Затем давайте поговорим о SDK, начинающемся из Honey Comb.

  1. Растровое изображение хранится в куче виртуальной машины, собственная память не учитывается для OOM.

  2. Условие для OOM намного проще: {размер кучи виртуальной машины}> = {ограничение размера кучи виртуальной машины для устройства}.

  3. Таким образом, у вас есть больше доступной памяти для создания растрового изображения с тем же ограничением размера кучи, вероятность выброса OOM снижается.

Вот некоторые из моих наблюдений о сборке мусора и утечке памяти.

Вы можете сами это увидеть в Приложении. Если Activity выполнила AsyncTask, которая все еще выполнялась после того, как Activity была уничтожена, Activity не будет собирать мусор до завершения AsyncTask.

Это связано с тем, что AsyncTask является экземпляром анонимного внутреннего класса, он содержит ссылку на Activity.

Вызов AsyncTask.cancel (true) не остановит выполнение, если задача заблокирована в операции ввода-вывода в фоновом потоке.

Обратные вызовы также являются анонимными внутренними классами, поэтому, если статический экземпляр в вашем проекте удерживает их и не освобождает их, произойдет утечка памяти.

Если вы запланировали повторяющуюся или отложенную задачу, например таймер, и не вызываете cancel () и purge () в onPause (), произойдет утечка памяти.

avatar
9 декабря 2012 в 21:45
5

Я пробовал подход Томаса Вервеста, но он возвращает масштаб 1 для размера изображения 2592x1944, когда IMAGE_MAX_SIZE равно 2048.

Эта версия работала для меня на основе всех других комментариев, предоставленных другими:

private Bitmap decodeFile (File f) {
    Bitmap b = null;
    try {
        // Decode image size
        BitmapFactory.Options o = new BitmapFactory.Options ();
        o.inJustDecodeBounds = true;

        FileInputStream fis = new FileInputStream (f);
        try {
            BitmapFactory.decodeStream (fis, null, o);
        } finally {
            fis.close ();
        }

        int scale = 1;
        for (int size = Math.max (o.outHeight, o.outWidth); 
            (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale);

        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options ();
        o2.inSampleSize = scale;
        fis = new FileInputStream (f);
        try {
            b = BitmapFactory.decodeStream (fis, null, o2);
        } finally {
            fis.close ();
        }
    } catch (IOException e) {
    }
    return b;
}
avatar
2 ноября 2012 в 23:41
17

Это похоже на подходящее место, чтобы поделиться своим служебным классом для загрузки и обработки изображений с сообществом, вы можете использовать его и свободно изменять.

package com.emil;

import java.io.IOException;
import java.io.InputStream;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * A class to load and process images of various sizes from input streams and file paths.
 * 
 * @author Emil http://coderhelper.com/users/220710/emil
 *
 */
public class ImageProcessing {

    public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig);
        Bitmap bm = BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return bm;
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    public static Dimensions getDimensions(InputStream stream) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeStream(stream,null,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using stream.");
        }
    }

    public static Dimensions getDimensions(String imgPath) throws IOException{
        BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions();
        BitmapFactory.decodeFile(imgPath,options);
        if(ImageProcessing.checkDecode(options)){
            return new ImageProcessing.Dimensions(options.outWidth,options.outHeight);
        }else{
            throw new IOException("Image decoding failed, using file path.");
        }
    }

    private static boolean checkDecode(BitmapFactory.Options options){
        // Did decode work?
        if( options.outWidth<0 || options.outHeight<0 ){
            return false;
        }else{
            return true;
        }
    }

    /**
     * Creates a Bitmap that is of the minimum dimensions necessary
     * @param bm
     * @param min
     * @return
     */
    public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){
        int newWidth, newHeight;
        switch(min.type){
        case WIDTH:
            if(bm.getWidth()>min.minWidth){
                newWidth=min.minWidth;
                newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case HEIGHT:
            if(bm.getHeight()>min.minHeight){
                newHeight=min.minHeight;
                newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
            }else{
                // No resize
                newWidth=bm.getWidth();
                newHeight=bm.getHeight();
            }
            break;
        case BOTH: // minimize to the maximum dimension
        case MAX:
            if(bm.getHeight()>bm.getWidth()){
                // Height needs to minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minHeight;
                if(bm.getHeight()>min.minDim){
                    newHeight=min.minDim;
                    newWidth=ImageProcessing.getScaledWidth(newHeight, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }else{
                // Width needs to be minimized
                min.minDim=min.minDim!=null ? min.minDim : min.minWidth;
                if(bm.getWidth()>min.minDim){
                    newWidth=min.minDim;
                    newHeight=ImageProcessing.getScaledHeight(newWidth, bm);
                }else{
                    // No resize
                    newWidth=bm.getWidth();
                    newHeight=bm.getHeight();
                }
            }
            break;
        default:
            // No resize
            newWidth=bm.getWidth();
            newHeight=bm.getHeight();
        }
        return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true);
    }

    public static int getScaledWidth(int height, Bitmap bm){
        return (int)(((double)bm.getWidth()/bm.getHeight())*height);
    }

    public static int getScaledHeight(int width, Bitmap bm){
        return (int)(((double)bm.getHeight()/bm.getWidth())*width);
    }

    /**
     * Get the proper sample size to meet minimization restraints
     * @param dim
     * @param min
     * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2
     * @return
     */
    public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){
        switch(min.type){
        case WIDTH:
            return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
        case HEIGHT:
            return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
        case BOTH:
            int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2);
            int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2);
            // Return the smaller of the two
            if(widthMaxSampleSize<heightMaxSampleSize){
                return widthMaxSampleSize;
            }else{
                return heightMaxSampleSize;
            }
        case MAX:
            // Find the larger dimension and go bases on that
            if(dim.width>dim.height){
                return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2);
            }else{
                return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2);
            }
        }
        return 1;
    }

    public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){
        int add=multipleOf2 ? 2 : 1;
        int size=0;
        while(min<(dim/(size+add))){
            size+=add;
        }
        size = size==0 ? 1 : size;
        return size;        
    }

    public static class Dimensions {
        int width;
        int height;

        public Dimensions(int width, int height) {
            super();
            this.width = width;
            this.height = height;
        }

        @Override
        public String toString() {
            return width+" x "+height;
        }
    }

    public static class Minimize {
        public enum Type {
            WIDTH,HEIGHT,BOTH,MAX
        }
        Integer minWidth;
        Integer minHeight;
        Integer minDim;
        Type type;

        public Minimize(int min, Type type) {
            super();
            this.type = type;
            switch(type){
            case WIDTH:
                this.minWidth=min;
                break;
            case HEIGHT:
                this.minHeight=min;
                break;
            case BOTH:
                this.minWidth=min;
                this.minHeight=min;
                break;
            case MAX:
                this.minDim=min;
                break;
            }
        }

        public Minimize(int minWidth, int minHeight) {
            super();
            this.type=Type.BOTH;
            this.minWidth = minWidth;
            this.minHeight = minHeight;
        }

    }

    /**
     * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config
     * @param width
     * @param height
     * @param config
     * @return
     */
    public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){
        long pixels=width*height;
        switch(config){
        case ALPHA_8: // 1 byte per pixel
            return pixels;
        case ARGB_4444: // 2 bytes per pixel, but depreciated
            return pixels*2;
        case ARGB_8888: // 4 bytes per pixel
            return pixels*4;
        case RGB_565: // 2 bytes per pixel
            return pixels*2;
        default:
            return pixels;
        }
    }

    private static BitmapFactory.Options getOptionsForDimensions(){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds=true;
        return options;
    }

    private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = false;
        options.inDither = false;
        options.inSampleSize = sampleSize;
        options.inScaled = false;
        options.inPreferredConfig = bitmapConfig;
        return options;
    }
}
avatar
2 октября 2012 в 10:33
20

У меня это работает.

Bitmap myBitmap;

BitmapFactory.Options options = new BitmapFactory.Options(); 
options.InPurgeable = true;
options.OutHeight = 50;
options.OutWidth = 50;
options.InSampleSize = 4;

File imgFile = new File(filepath);
myBitmap = BitmapFactory.DecodeFile(imgFile.AbsolutePath, options);

и это на монодроиде C #. вы можете легко изменить путь изображения. здесь важны устанавливаемые параметры.

avatar
27 марта 2012 в 19:33
14

Я потратил весь день на тестирование этих решений, и единственное, что сработало для меня, - это описанные выше подходы для получения изображения и ручного вызова GC, который, как я знаю, необязателен, но это единственный вещь, которая сработала, когда я подверг свое приложение тестированию под большой нагрузкой, переключаясь между действиями. В моем приложении есть список миниатюрных изображений в виде списка в (допустим, действие A), и когда вы нажимаете на одно из этих изображений, оно переводит вас к другому действию (скажем, действию B), которое показывает основное изображение для этого элемента. Когда я переключался между двумя действиями, я в конечном итоге получал ошибку OOM, и приложение принудительно закрывалось.

Когда я опускаюсь до половины списка, он вылетает.

Теперь, когда я реализую следующее в упражнении B, я могу без проблем пройти через весь список и продолжать движение, продолжать и продолжать ... и это достаточно быстро.

@Override
public void onDestroy()
{   
    Cleanup();
    super.onDestroy();
}

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
avatar
24 февраля 2012 в 19:08
15

Я столкнулся с этой проблемой пару минут назад. Я решил это, улучшив управление своим адаптером listview. Я думал, что это проблема с сотнями изображений размером 50x50 пикселей, которые я использовал, оказалось, что я пытался раздуть свое пользовательское представление каждый раз, когда показывалась строка. Просто проверив, не завышена ли строка, я устранил эту ошибку и использую сотни растровых изображений. На самом деле это для Spinner, но базовый адаптер работает точно так же для ListView. Это простое исправление также значительно улучшило производительность адаптера.

@Override
public View getView(final int position, View convertView, final ViewGroup parent) {

    if(convertView == null){
        LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = inflater.inflate(R.layout.spinner_row, null);
    }
...
PeteH
29 июля 2013 в 04:43
3

Я не могу отблагодарить вас за это! Я искал не ту проблему, прежде чем увидел это. Вопрос к вам: поскольку каждая или моя строка списка имеет уникальное имя и фотографию, мне пришлось использовать массив convertView для сохранения значений каждой из строк. Я не мог понять, как использование одной переменной позволит вам это сделать. Я что-то упускаю?

avatar
7 декабря 2011 в 12:32
21

Ни один из приведенных выше ответов не помог мне, но я придумал ужасно уродливый обходной путь, который решил проблему. Я добавил в свой проект очень маленькое изображение размером 1x1 пиксель в качестве ресурса и загрузил его в свой ImageView перед вызовом сборки мусора. Я думаю, что, возможно, ImageView не выпускал Bitmap, поэтому GC никогда его не использовал. Это некрасиво, но пока вроде работает.

if (bitmap != null)
{
  bitmap.recycle();
  bitmap = null;
}
if (imageView != null)
{
  imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png.
}
System.gc();

imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want.
avatar
2 марта 2011 в 18:17
29

У меня есть гораздо более эффективное решение, которое не требует никакого масштабирования. Просто декодируйте свое растровое изображение только один раз, а затем кэшируйте его на карте с указанием его имени. Затем просто извлеките растровое изображение по имени и установите его в ImageView. Больше ничего делать не нужно.

Это будет работать, потому что фактические двоичные данные декодированного растрового изображения не хранятся в куче виртуальной машины dalvik. Хранится внешне. Таким образом, каждый раз, когда вы декодируете растровое изображение, оно выделяет память вне кучи виртуальной машины, которая никогда не восстанавливается GC

Чтобы лучше понять это, представьте, что вы сохранили свое изображение в папке с возможностью рисования. Вы просто получаете изображение, выполнив getResources (). GetDrwable (R.drawable.). Это НЕ будет декодировать ваше изображение каждый раз, а будет повторно использовать уже декодированный экземпляр каждый раз, когда вы его вызываете. По сути, он кэшируется.

Теперь, поскольку ваше изображение находится где-то в файле (или даже может быть получено с внешнего сервера), ВАША ответственность за кэширование экземпляра декодированного растрового изображения для повторного использования в любом месте, где это необходимо.

Надеюсь, это поможет.

Vincent
27 апреля 2011 в 15:38
4

"а затем кэшировать его на карте с указанием его имени". Как именно вы кэшируете свои изображения?

ErikR
10 июня 2011 в 19:45
3

Вы действительно пробовали это? Несмотря на то, что данные пикселей на самом деле не хранятся в куче Dalvik, их размер в собственной памяти сообщается виртуальной машине и учитывается в доступной памяти.

Rafael T
29 июня 2011 в 11:58
4

@Vincent Я думаю, их несложно хранить на карте. Я бы предложил что-то вроде карты HashMap <KEY, Bitmap>, где Key может быть строкой источника или чем-либо, что имеет для вас смысл. Предположим, вы выбрали путь как KEY, вы сохраняете его как map.put (Path, Bitmap) и получаете его через map.get (Path)

Dori
1 июля 2011 в 08:54
4

вы, вероятно, захотите использовать HashMap <String, SoftReference <Bitmap>>, если вы реализуете кэш изображений, иначе у вас все равно может закончиться память - также я не думаю, что «он выделяет память вне кучи виртуальной машины, которая никогда не восстанавливается GC. "верно, память восстанавливается, насколько я понимаю, это может быть задержка, для которой предназначен bitmap.recycle (), как подсказка, чтобы вернуть память раньше ...