working - work with bitmap android




Странная проблема с памятью при загрузке изображения в объект Bitmap (20)

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

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

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

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

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

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

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

FYI: Вот как я это делал:

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

Класс обучения Android « Отображение битмапов эффективно » предлагает отличную информацию для понимания и устранения исключения java.lang.OutOfMemoryError: bitmap size exceeds VM budget при загрузке Bitmaps.

Чтение растровых изображений Размеры и тип

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

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 декодирование с помощью параметра inJustDecodeBounds установленного в true , передайте параметры, а затем снова декодируйте, используя новое значение inJustDecodeBounds а inJustDecodeBounds - 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* мере необходимости.


У меня была эта же проблема и она была решена, избегая функций 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, а через собственные вызовы, выделения выполняются за пределами виртуальной кучи, но подсчитываются против этого ».


Это известная ошибка , это связано не с большими файлами. Так как Android кэширует Drawables, он выходит из памяти после использования нескольких изображений. Но я нашел альтернативный способ для этого, пропустив систему кэширования по умолчанию для 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);
}

Это работает для меня.

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 #. вы можете легко изменить путь изображения. что важно здесь.


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

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

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

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


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

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

к сожалению, если None of Above работает, то добавьте это в свой файл манифеста . Внутри тега приложения

 <application
         android:largeHeap="true"

Здесь есть два вопроса ...

  • Растровая память не находится в куче VM, а скорее в нативной куче - см. BitmapFactory OOM, управляющий меня орехами
  • Сбор мусора для родной кучи более ленив, чем куча VM, поэтому вам нужно быть достаточно агрессивным в том, чтобы делать bitmap.recycle и bitmap = null каждый раз, когда вы проходите через Activity onPause или onDestroy

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

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

Это сработало для меня!

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

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

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

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

Bitmap bm;
bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true);
mPicture = new ImageView(context);
mPicture.setImageBitmap(bm);    

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

Вот мой класс 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 [email protected]
            // 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;
    }

}

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

то я работал над решением этой проблемы, и, наконец, я сделал следующее усовершенствование для кода Fedor's (All Credit to Fedor для создания такого приятного решения) :)

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;

}

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

для более пожалуйста см. this


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


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


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

Смотрите здесь: BitmapFactory OOM управляет мной орехами

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

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

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


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

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

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

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

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


Это OutofMemoryExceptionневозможно полностью решить, позвонив System.gc()и так далее.

Обращаясь к жизненному циклу активности

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

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


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

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

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

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

private void Cleanup()
{    
    bitmap.recycle();
    System.gc();
    Runtime.getRuntime().gc();  
}




android-bitmap