Android: как работает функция Bitmap recycle ()?

89

Допустим, я загрузил изображение в объект растрового изображения, например

Bitmap myBitmap = BitmapFactory.decodeFile(myFile);

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

myBitmap = BitmapFactory.decodeFile(myFile2);

Что происходит с первым myBitmap? Собирается ли мусор, или мне нужно вручную собирать мусор перед загрузкой другого растрового изображения, например. myBitmap.recycle()?

Кроме того, есть ли лучший способ загружать большие изображения и отображать их одно за другим, перерабатывая в пути?

Анудж Тенани
источник

Ответы:

79

Первое растровое изображение не собирается сборщиком мусора при декодировании второго. Сборщик мусора сделает это позже, когда решит. Если вы хотите освободить память как можно скорее, вы должны позвонить recycle()непосредственно перед декодированием второго растрового изображения.

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

Федор
источник
23

Я думаю, проблема в следующем: в версиях Android до Honeycomb фактические необработанные данные растрового изображения хранятся не в памяти виртуальной машины, а в собственной памяти. Это родная память будет освобождена , когда соответствующий JavaBitmap объект 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 в статике, которую я инициализировал в начале своей Activity.

Display display = getWindowManager().getDefaultDisplay();
displayWidth = display.getWidth();
displayHeight = display.getHeight();
Джунод
источник
3
Вам не нужно вызывать recycle (), это просто хорошая идея, если вы хотите сразу освободить память.
Кару
13
В принятом ответе говорится: «Если вы хотите как можно скорее освободить память, вам следует вызвать recycle ()». В вашем ответе говорится: «Вам нужно будет вызвать myBitmap.recycle ()». Есть разница между «следует» и «нужно», и последнее в данном случае неверно.
Кару
1
Контекст важен. Вопрос был в том, «А есть ли лучший способ загружать большие изображения и отображать их одно за другим, перерабатывая в пути».
djunod 01
3
Начиная с Android 4.1, приведенный выше пример может не работать, поскольку createScaledBitmap в некоторых случаях может возвращать тот же экземпляр, что и исходный. Это означает, что вы должны проверить этот оригинал! = MyBitmap перед переработкой оригинала.
Jeremyfa
1
@Jeremyfa Он возвращает исходное изображение только в том случае, если вы укажете ширину и высоту, идентичные оригиналу. В этом случае, масштабирование спорно, так что , возможно, также сохранить некоторые процессы, пропуская его и возвращает исходное изображение вместо этого. Хотя это не должно ничего "ломать" ...
Джабари
11

После того как растровое изображение было загружено в память, фактически оно состояло из двух частей. Первая часть включает некоторую информацию о растровом изображении, другая часть включает информацию о пикселях растрового изображения (она состоит из байтового массива). Первая часть существует в используемой памяти Java, вторая часть существует в используемой памяти C ++. Они могут напрямую использовать память друг друга. Bitmap.recycle () используется для освобождения памяти C ++. Если вы сделаете только это, GC будет собирать часть java, и всегда будет использоваться память C.

Аллен
источник
+1 за интересный, но очень хороший способ описания того, почему память недоступна для немедленной сборки мусора - хороший вариант.
Ричард Ле Мезурье
8

Тимммм был прав.

согласно: http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

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

Цзянь
источник