Проблема с нехваткой памяти при загрузке изображения в растровый объект - PullRequest
1203 голосов
/ 25 января 2009

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

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

Проблема в том, что я получаю сообщение об ошибке «Недостаточно памяти» при попытке вернуться назад и перезапустить второе действие.

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

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

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

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

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

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 

У меня также появляется новая ошибка при отображении изображения:

01-25 22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d
01-25 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 
01-25 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process.
01-25 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes
01-25 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed

Ответы [ 42 ]

14 голосов
/ 06 февраля 2013

В одном из моих приложений мне нужно сделать снимок либо с 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;

}

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

для более подробной информации, пожалуйста, обратитесь это

13 голосов
/ 24 февраля 2012

Я только что столкнулся с этой проблемой пару минут назад. Я решил это, сделав лучшую работу по управлению моим списком адаптера. Я подумал, что это проблема с сотнями изображений размером 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);
    }
...
12 голосов
/ 27 марта 2012

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

Когда я попаду на полпути вниз по списку, произойдет сбой.

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

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}
12 голосов
/ 13 августа 2012

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

Поэтому, пожалуйста, проверьте устройство. Может быть запущен на устройстве.

11 голосов
/ 14 июля 2013

Мои 2 цента: я исправил ошибки OOM с помощью растровых изображений:

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

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

11 голосов
/ 24 января 2014

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

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 () .

10 голосов
/ 08 августа 2012

Такие OutofMemoryException не могут быть полностью решены путем вызова System.gc() и т. Д.

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

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

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

10 голосов
/ 26 ноября 2012

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

10 голосов
/ 31 августа 2013

Все решения здесь требуют установки 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% дают мне хороший буфер без исчерпания памяти при выполнении поворота изображения.

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

10 голосов
/ 31 октября 2012

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

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