Android: Як працює Bitmap recycle ()?


89

Скажімо, я завантажив зображення у растровий об’єкт типу

Bitmap myBitmap = BitmapFactory.decodeFile(myFile);

Тепер, що станеться, якщо я завантажу ще одне растрове зображення, як

myBitmap = BitmapFactory.decodeFile(myFile2);

Що відбувається з першим myBitmap? Чи збирає він сміття, чи мені доводиться збирати сміття вручну перед завантаженням іншого растрового зображення, наприклад. myBitmap.recycle()?

Крім того, чи є кращий спосіб завантажувати великі зображення та відображати їх одне за одним під час повторної переробки?

Відповіді:


79

Перше растрове зображення - це не сміття, зібране при декодуванні другого. Смітник буде робити це пізніше, коли він вирішить. Якщо ви хочете звільнити пам'ять якомога швидше, вам слід зателефонувати recycle()безпосередньо перед декодуванням другого растрового зображення.

Якщо ви хочете завантажити по-справжньому велике зображення, вам слід його пробувати. Ось приклад: Дивна проблема з втратою пам’яті під час завантаження зображення в об’єкт Bitmap .


23

Я думаю, що проблема полягає в наступному: у попередніх версіях стільникових версій Android фактичні вихідні дані растрових зображень зберігаються не в пам'яті віртуальної машини, а в власній пам’яті. Це рідна пам'ять буде звільнена , коли відповідний Java Bitmapоб'єкт GC'd.

Однак , коли у вас закінчується вбудована пам'ять, Dalvik GC не запускається, тому можливо, що ваш додаток використовує дуже мало Java-пам'яті, тому Dalvik GC ніколи не викликається, проте він використовує тонни вбудованої пам'яті для растрових зображень що врешті-решт спричиняє помилку OOM.

Принаймні це моє припущення. На щастя, у Honeycomb та пізніших версіях всі растрові дані зберігаються у віртуальній машині, тому вам взагалі не доведеться їх використовувати recycle(). Але для мільйонів користувачів 2,3 (фрагментація трясе кулак ), ви повинні використовувати, recycle()де це можливо (великі клопоти). Або ж ви можете замість цього викликати GC.


21

Вам потрібно буде зателефонувати до myBitmap.recycle () перед завантаженням наступного зображення.

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

if (myBitmap != null) {
    myBitmap.recycle();
    myBitmap = null;
}
Bitmap original = BitmapFactory.decodeFile(myFile);
myBitmap = Bitmap.createScaledBitmap(original, displayWidth, displayHeight, true);
if (original != myBitmap)
    original.recycle();
original = null;

Я кешу displayWidth & displayHeight у статиці, яку я ініціалізував на початку своєї діяльності.

Display display = getWindowManager().getDefaultDisplay();
displayWidth = display.getWidth();
displayHeight = display.getHeight();

3
Вам не потрібно викликати recycle (), це просто гарна ідея, якщо ви хочете відразу звільнити пам’ять.
Кару

13
Прийнята відповідь говорить: "Якщо ви хочете звільнити пам'ять якомога швидше, вам слід зателефонувати recycle ()". Ваша відповідь говорить "Вам потрібно буде зателефонувати до myBitmap.recycle ()". Існує різниця між "слід" і "потрібно", і останнє в цьому випадку є неправильним.
Кару

1
Важливий контекст. Питання було: "Також є кращий спосіб завантажувати великі зображення та відображати їх один за одним, переробляючи в дорозі".
djunod

3
Починаючи з Android 4.1, наведений вище приклад може зламатися, оскільки createScaledBitmap може повернути в деяких випадках той самий екземпляр, що й оригінальний. Це означає, що вам потрібно перевірити цей оригінал! = MyBitmap перед переробкою оригіналу.
Jeremyfa 12.03.13

1
@Jeremyfa Він повертає оригінальне зображення, лише якщо ви вказали ширину та висоту, які ідентичні оригіналу. У такому випадку масштабування є спірним, тому воно може також зберегти деякі процеси, пропустивши його та замість цього повернувши оригінальне зображення. Це не повинно нічого "зламати", хоча ...
Джабарі

11

Після того, як растрове зображення було завантажено в пам’ять, насправді воно було зроблене з двох частин даних. Перша частина включає деяку інформацію про растрове зображення, інша частина включає інформацію про пікселі растрового зображення (воно створюється байтовим масивом). Перша частина існує у використаній пам'яті Java, друга частина існує у використаній пам'яті C ++. Він може використовувати пам’ять один одного безпосередньо. Bitmap.recycle () використовується для звільнення пам'яті C ++. Якщо ви тільки це зробите, GC збере частину Java, і пам'ять C завжди використовується.


+1 за цікавий, але дуже хороший спосіб описати, чому пам’ять недоступна для негайного GC - приємний.
Richard Le Mesurier

8

Тимммм мав рацію.

згідно: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

Крім того, до Android 3.0 (рівень API 11) дані резервного копіювання растрових зображень зберігались у власній пам'яті, яка не випускається передбачуваним чином, що могло призвести до того, що програма ненадовго перевищить обмеження пам’яті та аварійно завершить роботу.

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