Дивна проблема з пам'яттю під час завантаження зображення на об'єкт Bitmap


1288

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

Попередній перегляд зображення у вікні списку проводиться за допомогою курсору та ListAdapter. Це робить його досить простим, але я не впевнений, як я можу розмістити зменшене зображення (тобто менший розмір бітів, не піксель, як srcкнопка зображення на льоту, тому я просто змінив розмір зображення, яке зійшло з камери телефону.

Проблема полягає в тому, що я виймаю помилку пам’яті, коли намагається повернутися назад і повторно запустити другу активність.

  • Чи є спосіб я легко скласти адаптер списку рядок за рядком, де я можу змінити розмір під час руху ( трохи розумно )?

Це було б кращим, оскільки мені також потрібно внести деякі зміни у властивості віджетів / елементів у кожному рядку, оскільки я не в змозі вибрати рядок із сенсорним екраном через проблему фокусування. ( Я можу використовувати кульковий ролик. )

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

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

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

8
Я вирішив це, уникаючи Bitmap.decodeStream або decodeFile та використовуючи метод BitmapFactory.decodeFileDescriptor.
Fraggle

1
Я також зіткнувся з подібною проблемою пару тижнів тому, і я вирішив це, зменшивши зображення до оптимальної точки. Я написав повний підхід у своєму блозі codingjunkiesforum.wordpress.com/2014/06/12/… та завантажив повний зразок проекту зі схильним кодом OOM проти коду доказу OOM atttps: //github.com/shailendra123/BitmapHandlingDemo
Shailendra Singh Rajawat

5
Прийнята відповідь на це питання обговорюється мета
повторювання

4
Це трапляється, коли ви не читаєте посібники для розробників Android
Педро Варела

2
Це відбувається через погану архітектуру Android. Він повинен змінити розмір самих зображень, таких як ios, і UWP це робить. Мені не потрібно займатися цим самим. Розробники Android звикають до цього пекла і думають, що він працює як слід.
Доступ заборонено

Відповіді:


650

Android навчання клас, « Відображення растрових зображень ефективні », пропонує деяку велику інформацію для розуміння і вирішення , за винятком java.lang.OutOfMemoryError: bitmap size exceeds VM budgetпри завантаженні Bitmaps.


Прочитайте розміри та тип Bitmap

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, яке декодується на inSampleSize4, створює растрову карту приблизно 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, передайте параметри через, а потім знову декодуйте, використовуючи нове inSampleSizeзначення та 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*метод у міру необхідності.


21
Ця відповідь обговорюється в мета
повторі

9
Ця відповідь (за винятком інформації, отриманої за посиланням) не пропонує багато рішення, як для відповіді. Важливі частини посилання слід об'єднати у питання.
FallenAngel

7
Ця відповідь, як і запитання, та інші відповіді - це Wiki Wiki, тому спільнота може виправити, редагуючи щось, що не потребує втручання модератора.
Martijn Pieters

Поточне посилання на вміст та підтримку Kotlin можна знайти за адресою: developer.android.com/topic/performance/graphics/load-bitmap
Panos Gr

890

Щоб виправити помилку 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;
}

31
Зауважте, що 10 може бути не найкращим значенням для inSampleSize, однак, документація пропонує використовувати повноваження 2.
Мирко Н.

70
Я зіткнувся з тією самою проблемою, що і у Chrispix, але я не думаю, що рішення тут справді вирішує проблему, а скоріше її уникає. Зміна розміру вибірки зменшує об'єм використовуваної пам'яті (ціною якості зображення, що, мабуть, добре для попереднього перегляду зображення), але це не завадить винятку, якщо декодований достатньо великий потік зображення, якщо декілька потоків зображень є розшифровано. Якщо я знайду краще рішення (а його може бути і не), я опублікую відповідь тут.
Flynn81

4
Вам потрібен лише відповідний розмір, щоб відповідати екрану за щільністю пікселів, щоб збільшити масштаб, і ви можете взяти зразок зображення з більшою щільністю.
stealthcopter

4
REQUIRED_SIZE - це новий розмір, до якого потрібно масштабувати.
Федір

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

372

Я трохи вдосконалив код Федора. Це в основному робить те ж саме, але без (на мій погляд) негарного циклу, але це завжди призводить до сили двох. Кудо Федору за прийняття оригінального рішення, я застряг, поки не знайшов його, і тоді я зміг зробити це :)

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

40
Так, ти маєш рацію, поки це не так красиво. Я просто намагався зробити це зрозумілим для всіх. Дякуємо за Ваш код.
Федір

10
@Thomas Vervest - Існує велика проблема з цим кодом. ^ не піднімає 2 до потужності, вона xors 2 з результатом. Ви хочете Math.pow (2.0, ...). Інакше це виглядає добре.
DougW

6
О, це дуже добре! Мій поганий, я його негайно виправлю, дякую за відповідь!
Томас Вервест

8
Ви створюєте два нових FileInputStreams, по одному для кожного виклику до BitmapFactory.decodeStream(). Чи не потрібно зберігати посилання на кожен з них, щоб їх можна було закрити в finallyблок?
матєв

1
@Babibu У документації не вказано, що потік для вас закритий, тому я припускаю, що його все одно слід закрити. Цікаву та пов’язану з цим дискусію можна знайти тут . Зверніть увагу на коментар Адріана Сміта, який стосується безпосередньо нашої дискусії.
Томас Вервест

232

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

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

2) НІКОЛИ не використовуйте Bitmap.createBitmap(width, height, Config.ARGB_8888) . Я маю на увазі НІКОЛИ! Я ніколи не мав, щоб ця річ не піднімала помилки пам'яті після декількох проходів. Ніяка кількість recycle(), System.gc()незалежно не допомогло. Це завжди було винятком. Інший спосіб, який насправді працює, - це мати фіктивне зображення у ваших малюнках (або іншу растрову карту, яку ви розшифрували, скориставшись кроком 1 вище), змінити масштаб до того, що вам завгодно, а потім маніпулювати отриманою бітовою картою (наприклад, передати її на Canvas для більшої розваги). Отже, що ви повинні використовувати замість цього: Bitmap.createScaledBitmap(srcBitmap, width, height, false). Якщо з якої-небудь причини ви ОБОВ'ЯЗКОВИ використати метод створення грубої сили, тоді принаймні пройдіть Config.ARGB_4444.

Це майже гарантовано допоможе вам заощадити години, якщо не дні. Все, що говорить про масштабування зображення тощо, насправді не працює (якщо ви не вважаєте рішення неправильного розміру чи погіршення зображення).


22
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true;і Bitmap.createScaledBitmap(srcBitmap, width, height, false);вирішив мою проблему у мене з виключенням пам'яті на android 4.0.0. Дякую, друже!
Jan-Terje Sørensen

5
У виклику Bitmap.createScaledBitmap () ви, ймовірно, повинні використовувати true як параметр прапора. Інакше якість зображення не буде однорідною при збільшенні масштабу. Перевірте цю тему stackoverflow.com/questions/2895065 / ...
rOrlig

11
Це дійсно казкова порада. Бажаю, я можу дати вам додатковий +1 за те, що ви взяли на себе Google для виконання цієї дивовижної помилкової помилки. Я маю на увазі ... якщо це не помилка, то в документації дійсно повинні бути серйозні миготливі неонові знаки, що говорять "ЦЕ ЯК ВИ ПРОЦЕСУЄТЬ ФОТО", тому що я боровся з цим вже 2 роки, і я зараз знайшов цю посаду. Чудова знахідка.
Євген Сімкін

Зменшення масштабів зображення, безумовно, допомагає, але це важливий крок і те, що врешті-решт вирішило для мене це питання. Проблема просто масштабування зображень полягає в тому, що їх у вас багато, або якщо вихідні зображення дуже великі, ви все ще можете зіткнутися з тією ж проблемою. +1 вам Єфреме.
Дейв

10
Станом на Lollipop BitmapFactory.Options.inPurgeableта BitmapFactory.Options.inInputShareableзастарілий developer.android.com/reference/android/graphics/…
Денис Княжев

93

Це відома помилка , це не через великі файли. Оскільки Android кешує Drawables, він втрачає пам'ять після використання декількох зображень. Але я знайшов альтернативний спосіб для цього, пропустивши систему кешу Android за замовчуванням.

Рішення : перемістіть зображення у папку "Активи" та скористайтеся наступною функцією, щоб отримати BitmapDravable:

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

79

У мене була ця сама проблема і вирішено її, уникаючи функцій 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, а за допомогою власних викликів; розподіли виконуються за межами віртуальної купи, але рахуються проти цього! "


1
той самий результат із пам’яті, насправді не має значення, який метод ви використовуєте, залежить від кількості байтів, які ви тримаєте, щоб прочитати дані, що видаються з пам'яті.
PiyushMishra

72

Я думаю, що найкращий спосіб уникнути цього OutOfMemoryError- це зіткнутися з ним і зрозуміти це.

Я зробив додаток, щоб навмисно викликати OutOfMemoryErrorта контролювати використання пам'яті.

Після того, як я зробив багато експериментів з цим додатком, я дістав наступні висновки:

Я буду говорити про версії SDK, перш ніж Honey Comb.

  1. Растрові карти зберігаються у рідній купі, але сміття збирається автоматично, викликаючи recycle () не потрібно.

  2. Якщо {VM heap size} + {виділена нативна пам’ять накопичувача}> = {обмеження розміру купи VM для пристрою}, і ви намагаєтесь створити растрову карту, OOM буде кинуто.

    ПОВІДОМЛЕННЯ: РОЗМІР VM HEAP враховується, а не VM ВЗНАЧАЛЬНА ПАМЯТЬ.

  3. Розмір VM Heap ніколи не зменшиться після вирощування, навіть якщо виділена пам'ять VM зменшена.

  4. Таким чином, ви повинні зберегти пік пам'яті VM якомога менше, щоб запобігти зростанню розміру VM Heap занадто великим, щоб зберегти доступну пам'ять для Bitmaps.

  5. Викликати вручну System.gc () безглуздо, система спочатку зателефонує перед тим, як спробувати збільшити розмір купи.

  6. Рідна купа розмірів ніколи теж не зменшиться, але вона не зараховується до OOM, тому не потрібно турбуватися про це.

Потім поговоримо про SDK Starts від Honey Comb.

  1. Растрові карти зберігаються у купі VM, вбудована пам'ять не враховується для OOM.

  2. Умова для OOM набагато простіша: {розмір купи VM}> = {обмеження розміру купи VM для пристрою}.

  3. Таким чином, у вас є більше доступної пам’яті для створення растрових зображень з тим же обмеженням розміру купи, OOM рідше буде кинуто.

Ось деякі мої спостереження щодо збору сміття та витоку пам’яті.

Ви можете бачити його самостійно в додатку. Якщо Активність виконала AsyncTask, який все ще виконувався після того, як Діяльність була знищена, Активність не буде збирати сміття до завершення роботи AsyncTask.

Це тому, що AsyncTask є екземпляром анонімного внутрішнього класу, він містить посилання на Activity.

Виклик AsyncTask.cancel (true) не зупинить виконання, якщо завдання заблоковано операцією вводу-виводу у фоновому потоці.

Зворотні виклики теж є анонімними внутрішніми класами, тож якщо статичний примірник у вашому проекті утримує їх і не відпускає їх, пам'ять буде просочена.

Якщо ви запланували повторне або відкладене завдання, наприклад, Таймер, і ви не викликаєте увімкнення () та очищення () в режимі OnPause (), пам'ять буде просочена.


AsyncTask не обов'язково повинен бути "екземпляром анонімного внутрішнього класу", і те саме стосується і Callbackks. Ви можете створити новий загальнодоступний клас у власному файлі, який розширює AsyncTask або навіть private static classу тому ж класі. Вони не матимуть жодних посилань на Активність (якщо ви, звичайно, не дасте їм жодного курсу)
Simon Forsberg

65

Останнім часом я бачив багато питань щодо винятків OOM та кешування. У посібнику розробника є дійсно гарна стаття з цього приводу, але деякі, як правило, не реалізують його відповідним чином.

Через це я написав приклад програми, яка демонструє кешування в середовищі Android. Ця реалізація ще не отримала ООМ.

Подивіться в кінці цієї відповіді про посилання на вихідний код.

Вимоги:

  • Android API 2.1 або новішої версії (мені просто не вдалося отримати доступну пам'ять для програми в API 1.6 - це єдиний фрагмент коду, який не працює в API 1.6)
  • Пакет підтримки Android

Знімок екрана

Особливості:

  • Зберігає кеш-пам'ять, якщо є зміна орієнтації , використовуючи одиночну кнопку
  • Використовуйте одну вісімку призначеної пам'яті програми в кеш-пам'яті (модифікуйте, якщо хочете)
  • Великі растрові карти зменшуються (ви можете визначити максимальний піксель, який ви хочете дозволити)
  • Перевіряє наявність підключення до Інтернету перед завантаженням растрових зображень
  • Переконайтеся, що ви створюєте лише одну задачу для кожного рядка
  • Якщо ви відкинувши в 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 .


Останні слова:

Я перевіряв це вже кілька тижнів, ще не отримав жодного винятку з ОМП. Я перевірив це на емуляторі, на моєму Nexus One та на Nexus S. Я протестував URL-адреси зображень, які містять зображення, які були в HD якості. Єдине вузьке місце полягає в тому, що для завантаження потрібно більше часу.

Є лише один можливий сценарій, коли я можу уявити, що OOM з'явиться, і це, якщо ми завантажимо багато, дійсно великих зображень, і перш ніж їх масштабувати і помістити в кеш, одночасно займе більше пам'яті і спричинить OOM. Але це навіть не ідеальна ситуація, і, швидше за все, це не вдасться вирішити більш здійсненним способом.

Повідомте про помилки в коментарях! :-)


43

Я зробив наступне, щоб зняти зображення та змінити його розміром під час руху. Сподіваюсь, це допомагає

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

26
Цей підхід масштабує растрову карту. Але це не вирішує проблему OutOfMemory, оскільки вся растрова карта все одно декодується.
Федір

5
Я побачу, чи зможу я переглянути старий код, але думаю, що він вирішив мої проблеми із пам'яттю. Перевірить мій старий код.
Кріспікс

2
Принаймні, у цьому прикладі схоже, що ви не зберігаєте посилання на повну растрову карту, таким чином, економте пам'ять.
NoBugs

Для мене це вирішило питання пам'яті, але знизило якість зображень.
Памела Сілла

35

Здається, що це дуже тривала проблема, що має багато різних пояснень. Я скористався порадою двох найпоширеніших представлених відповідей тут, але жодна з них не вирішила моїх проблем ВМ, стверджуючи, що вона не може дозволити собі байти для виконання декодування частини процесу. Після деякого копання я дізнався, що справжньою проблемою тут є процес розшифровки, відбираючи від NATIVE купи.

Дивіться тут: BitmapFactory OOM заганяє мене

Це призвело мене до ще однієї дискусійної теми, де я знайшов ще пару рішень цієї проблеми. Одне - дзвонитиSystem.gc(); вручну після відображення зображення. Але це фактично змушує ваш додаток використовувати БІЛЬШЕ пам’яті, намагаючись зменшити нативну купу. Кращим рішенням щодо випуску 2.0 (Donut) є використання параметра BitmapFactory "inPurgeable". Тому я просто додав o2.inPurgeable=true;лише післяo2.inSampleSize=scale; .

Більше про цю тему тут: Чи обмеження маси пам’яті лише 6 М?

Тепер, сказавши все це, я є повною дугою і з Java, і з Android. Тож якщо ви думаєте, що це жахливий спосіб вирішити цю проблему, ви, мабуть, праві. ;-) Але це для мене творить чудеса, і я вважаю, що зараз неможливо запустити VM з кеш-маси. Єдиний недолік, який я можу знайти, - це те, що ви знімаєте кешоване намальоване зображення. Що означає, що якщо ви повернетесь ПРАВО до цього зображення, ви перемальовуєте його кожен раз. У випадку, як працює моя програма, це насправді не проблема. Ваш пробіг може відрізнятися.


inPergeable зафіксований для мене.
Артем Русаковський

35

на жаль, якщо жодне з вищезгаданих не працює, додайте це у файл Manifest . Всередині тегу додатків

 <application
         android:largeHeap="true"

1
Чи можете ви пояснити, що це насправді робить? Просто сказати людям, щоб додати це, не допомагає.
Стелс-рабин

1
Це дуже погане рішення. В основному ви не намагаєтеся виправити проблему. Замість того, щоб попросити систему Android виділити більше місця для вашої програми. Це матиме дуже погані наслідки для вашої програми, як, наприклад, ваш додаток споживає багато енергії акумулятора, оскільки GC повинен працювати через великий простір для очищення пам'яті, а також продуктивність вашої програми буде повільнішою.
Пракаш

2
Тоді чому андроїд дозволяє нам додавати цей андроїд: LargeHeap = "true" у наш маніфест? Тепер ви кидаєте виклик Android.
Хіманшу Морі


28

У мене є набагато ефективніше рішення, яке не потребує будь-якого масштабування. Просто розшифруйте свою растрову карту лише один раз, а потім кешуйте її на карті проти її імені. Потім просто отримайте растрову карту проти імені та встановіть її у ImageView. Більше нічого не потрібно робити.

Це спрацює, оскільки фактичні двійкові дані декодованої растрової карти не зберігаються в межах дальвіків VM купи. Він зберігається зовні. Тому кожен раз, коли ви декодуєте растрову карту, вона виділяє пам'ять поза VM купи, яка ніколи не відновлюється GC

Щоб допомогти вам краще оцінити це, уявіть, що ви зберегли зображення ур у папці, що малюється. Ви просто отримуєте зображення, виконавши getResources (). GetDrwable (R.drawable.). Це НЕ буде декодувати ваше зображення кожного разу, але повторно використовувати вже декодований екземпляр кожного разу, коли ви його зателефонуєте. Так що по суті це кешоване.

Оскільки ваше зображення десь знаходиться у файлі (або може навіть надходити із зовнішнього сервера), ВАША відповідальність за кешування декодованого екземпляра растрових зображень буде повторно використана там, де це потрібно.

Сподіваюсь, це допомагає.


4
", а потім кешуйте його на карті проти його імені." Як саме ви кешуєте свої зображення?
Вінсент

3
Ви насправді пробували це? Навіть незважаючи на те, що дані пікселів насправді не зберігаються в купі Dalvik, його розмір у рідній пам’яті повідомляється ВМ та відраховується від доступної пам’яті.
ЕрікР

3
@Vincent Я думаю, що їх не важко зберігати на карті. Я б запропонував щось на зразок карти HashMap <KEY, Bitmap>, де ключ може бути рядком джерела або будь-яким, що має для вас сенс. Припустимо, ви приймаєте шлях як KEY, ви зберігаєте його як map.put (Шлях, Bitmap) та отримуєте його через map.get (Шлях)
Rafael T

3
Ви, можливо, хочете використовувати HashMap <String, SoftReference <Bitmap>>, якщо Ви реалізуєте Кеш зображень, інакше у вас все одно може залишитися пам'яті - також я не думаю, що "він виділяє пам'ять поза купою VM, яку GC ніколи не відновлює. "це правда, пам'ять відтворена, як я розумію, може бути затримка. Це є те, що є bitmap.recycle (), як натяк на повернення пам’яті рано ...
Дорі

28

Я вирішив таке ж питання наступним чином.

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

все в порядку, але я використовую кілька растрових зображень для малювання кола в OnCreate та виклику діяльності 4-5 разів, так як очистити растрову карту та як видалити растрову карту та створити знову вдруге, коли активність 0nCreate ..
ckpatel

27

Тут є два питання….

  • Bitmap пам'яті не в купі VM, а в рідній купі - см BitmapFactory ООМ зводить мене з горіхами
  • Збір сміття для рідної купи лазерніше, ніж купа VM - тому вам потрібно бути дуже агресивним щодо того, щоб робити bitmap.recycle і bitmap = null щоразу, коли ви переходите через активність onPause або onDestroy

Це в купі VM з андроїд 2.3+ і більше
FindOut_Quran

27

Це працювало для мене!

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

20

Жодна з вищезазначених відповідей не працювала для мене, але я придумав жахливо негарне рішення, яке вирішило проблему. Я додав до свого проекту як ресурс дуже маленьке зображення 1х1 пікселя та завантажив його у свій 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.

схоже, що imageView насправді не переробляє растровий малюнок сам по собі. Допомогли мені, дякую
Дмитро Зайцев

@Mike чи можете ви додати повний код завантажувача зображень, або ви можете надіслати мені посилання завантаження растрових зображень. Якщо я використовую переробляти на растровій карті, відображається весь перегляд мого списку, але всі елементи відображаються порожніми
TNR

@Mike Ви можете сказати, чи я imageView = null перед тим, як зателефонувати у сміттєзбірник, це добре чи ні?
Юдд

@TNR Я думаю, що тут у вас пропущено те, що bitmapу наведеному вище коді є попереднє вже відображене зображення, вам потрібно переробити це, очистити будь-які посилання на нього, також imageViewзабути його, встановивши крихітну заміну gc(); і після всього цього: завантажте своє НОВЕ зображення bitmapта покажіть його, ...у наведеному вище коді.
TWiStErRob

Це неправильно. Ви завжди повинні очищати вміст imageView перед тим, як переробляти растрову карту (замість того, поки вона фактично відображається та використовується).
FindOut_Quran

20

Чудові відповіді тут, але я хотів, щоб цілком корисний клас вирішив цю проблему .. тому я зробив одну.

Ось мій клас 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;
    }

}

Для всіх, хто користується цим: я просто виправив помилку: "int scaledBitmapHeight = scaledBitmap.getWidth ();" помиляється (очевидно. Я замінив його на "int scaledBitmapHeight = scaledBitmap.getHeight ();"
Паскаль

19

Це працює для мене.

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 # монодороїді. ви можете легко змінити шлях зображення. що тут важливо - це варіанти, які потрібно встановити.


16

Це здається відповідним місцем для спільного використання мого класу корисності для завантаження та обробки зображень із спільнотою. Ви можете використовувати його та вільно змінювати їх.

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

16

В одному зі своїх додатків мені потрібно сфотографуватися або з Camera/Gallery. Якщо користувач натискає зображення з камери (може бути 2MP, 5MP або 8MP), розмір зображення залежить відkB s до MBs. Якщо розмір зображення менший (або до 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;

}

Я сподіваюся, що це допоможе приятелям, які стикаються з тією ж проблемою!

більш будь ласка , зверніться в цьому


14

Я просто зіткнувся з цим питанням пару хвилин тому. Я вирішив це, зробивши кращу роботу з управління своїм адаптером listview. Я думав, що це проблема із сотнями зображень 50x50px, які я використовую, виявляється, що я намагався завищити свій власний вигляд кожного разу, коли з'являвся рядок. Просто перевіривши, чи рядок був надутий, я усунув цю помилку, і я використовую сотні растрових зображень. Це насправді для 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);
    }
...

3
Я не можу вам подякувати достатньо за це! Я переслідував неправильну проблему, перш ніж побачити це. Питання для вас: Оскільки кожен або рядок мого списку має унікальне ім’я та фото, мені довелося використовувати масив convertView, щоб зберегти значення кожного рядка. Я не міг зрозуміти, як використання однієї змінної дозволить вам це зробити. Я щось пропускаю?
PeteH

13

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

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

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

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

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

Любіть своє рішення! Я також витрачав години на вирішення цієї помилки, так дратує! Редагувати: На жаль, проблема все ще існує, коли я міняю орієнтацію екрана в ландшафтному режимі ...
Xarialon

Це нарешті допомогло мені: параметри BitmapFactory.Options = новий BitmapFactory.Options (); options.InPurgeable = true; options.InSampleSize = 2;
користувач3833732

13

Ця проблема трапляється лише в емуляторах Android. Я також стикався з цим питанням в емуляторі, але коли я перевірив на пристрої, він працював нормально.

Тому будь ласка, перевірте пристрій. Це може бути запущено в пристрої.


12

Мої 2 копійки: я вирішив свої помилки OOM за допомогою растрових зображень:

а) масштабування моїх зображень коефіцієнтом 2

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


Я радий, що ви згадали про Пікассо, адже це значно спрощує завантаження зображень. Особливо віддалено зберігаються.
Кріспікс

12

використовувати цей код для кожного зображення у виборі з SdCard або drevable для перетворення растрового об'єкта.

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


12

Як правило, розмір купи Android-пристроїв становить лише 16 Мб (залежить від пристрою / ОС див. Розмір розміру Heap ), якщо ви завантажуєте зображення і вони перетинають розмір 16 Мб, він викине з пам'яті виключення, замість того, щоб використовувати Bitmap для завантаження зображення з SD-карти або з ресурсів або навіть з мережі намагаються використовувати getImageUri , для завантаження растрової карти потрібно більше пам’яті, або ви можете встановити растровий файл на нуль, якщо ваша робота виконана з цією растровою картою.


1
І якщо setImageURI все ще отримує виняток, тоді зверніться до цього stackoverflow.com/questions/15377186/…
Mahesh

11

Усі рішення тут вимагають встановити 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% дає мені гарний буфер, не вичерпавши пам'ять під час обертання зображення.

Сподіваюся, це допомагає комусь там.


11

Таке OutofMemoryExceptionне можна повністю вирішити, зателефонувавши у System.gc()тощо.

Посилаючись на життєвий цикл активності

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

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


11

Цей код допоможе завантажувати великі растрові карти з малюваного

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

Приємно, думаєш, було б краще використовувати навантажувач замість завдання Async?
Кріспікс

Як щодо Bitmap.Config.ARGB_565? Якщо висока якість не є критичною.
Hamzeh Soboh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.