Android: вопрос о растровых изображениях, использовании памяти и масштабировании
Для читаемости я разместил примеры кода, на которые ссылаются мои решения, и затем я перечислил объяснения своих решений в числовом списке.
Я боролся с этим какое-то время. Я много читал, задавал вопросы здесь и экспериментировал; но не придумали достойного решения. Мне нужно читать изображения различного размера из входных потоков и отображать их с таким высоким качеством, которое позволяют ограничить память. Ниже приведены варианты, которые я рассмотрел, ни один из которых не кажется мне большим. Любая помощь или ввод были бы весьма полезными.
public class NativeTest extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
double nativeUsage = Debug.getNativeHeapAllocatedSize();
Log.i("memory", nativeUsage+"");
}
}
double getAvailableMemory()
{
//current heap size
double heapSize = Runtime.getRuntime().totalMemory();
//amount available in heap
double heapRemaining = Runtime.getRuntime().freeMemory();
double nativeUsage = Debug.getNativeHeapAllocatedSize();
double memoryAvailable = Runtime.getRuntime().maxMemory() - (heapSize - heapRemaining) - nativeUsage;
return memoryAvailable;
}
Bitmap createImageTrialAndError(InputStream stream)
{
Bitmap image = null;
int dowsample = 1;
while(image == null)
{
try
{
Options opts = new Options();
opts.inSampleSize = downsample;
image = BitmapFactory.decodeStream(imgStream, null, opts);
}
catch (OutOfMemoryError ome)
{
downsample = downsample * 2;
Log.i("out of mem", "try using: " + downsample);
}
}
return image;
}
- Идеальное решение было бы, если Bitmap имел метод Bitmap.drawBitmap(входной поток...). Это позволило бы мне извлечь из входного потока без необходимости выделять память для битмапа. Увы, это не вариант.
- Масштабирование растрового изображения в соответствии с доступной памятью. Это включает в себя получение ширины и высоты битмапа, вычисление количества байтов, которое битмап требует в качестве
width * height * 4
, вычисления доступной памяти, а затем BitmapFactory.Options.inSampleSize
, так что битмап будет использовать меньше памяти, чем то, что доступно. Однако этот параметр выходит из строя, потому что я не смог найти удалённо надежный способ вычисления доступной памяти. Метод getAvailableMemory() выглядит так, как будто он должен работать: он вычисляет доступную память как максимальную память - память, используемую в куче java-памяти, используемой в нативной куче.
К сожалению, эта формула дает очень ненадежные результаты. В основном потому, что Debug.getNativeHeapAllocatedSize()
не является точным представлением использования памяти Bitmap. Одним из очевидных примеров его неточности является действие NativeTest, ниже. На моем планшете Samsung Galaxy выведено выражение журнала: 3759416.0. 3,75 мб собственного распределения для пустой активности, что явно не является надежным способом определения масштабирования растрового изображения.
- Начните с коэффициента масштабирования единицы и попытайтесь инициализировать Bitmap; если инициализация завершилась неудачей из-за памяти, удвойте масштабный коэффициент и повторите попытку; и повторите этот процесс до достижения успеха. Это иллюстрируется
createBitmapTrialAndError()
. Это на самом деле удивительно эффективно и не очень медленно. Однако это очень нежелательно, потому что я использую SoftReferences в другом месте в своем приложении, а нехватка памяти заставляет собирать эти SoftReferences, что оказывает значительное влияние на производительность. Было бы гораздо более желательно знать правильный коэффициент масштабирования изначально, что позволило бы избежать ненужной коллекции этих SoftReferences.
- Мое окончательное решение кажется немного сомнительным. В основном он объединяет 2 и 3, но не использует
Debug.getNativeAllocatedSize()
. Вместо этого я отслеживаю собственное распределение памяти Bitmap, отслеживая везде, где я выделяю растровые изображения и подсчитываю их использование памяти, а затем вычитаю использование памяти для любых растровых изображений, которые я перерабатываю. Я использую это значение вместо nativeUsage в getAvailableMemory(), чтобы рассчитать правильный коэффициент масштабирования для битмапов. И в случае исключения исключения из памяти при использовании этого метода я использую решение 3 как резервный способ вычисления приемлемого масштаба. Очевидная проблема с этим заключается в массивной отрывочности в попытке отслеживать использование моего собственного источника памяти, но для меня это кажется лучшим решением.
Ответы
Ответ 1
Правильный подход состоит в том, чтобы декодировать размер, необходимый для отображения и подвыражения изображения в плитки, когда вам нужно масштабировать.
Там есть библиотека, которая должна делать именно это, проверьте:
https://github.com/davemorrissey/subsampling-scale-image-view
Ответ 2
Проблема кучи android заключается в том, что вы на самом деле не знаете, какую часть кучи вы можете использовать, потому что любое фоновое обслуживание может в любое время испортить вам все, если вы переступите ограничения памяти.
Почему бы вам просто не оставить один битмап размером с холстом, на котором вы всегда рисуете, и стек с уменьшенными растровыми изображениями? Затем вы можете отобразить все изображения в собственном решении на вашем холсте и всегда рисовать уменьшенные растровые изображения для любых изменений. Как только изменение закончится или будет ясно, какое изображение наиболее важно, перерисуйте его в собственном разрешении на холст (снова обратившись к диску).
Ответ 3
Простым и понятным способом является использование свойства inJustDecodeBounds для параметров. Установите для этого свойства значение true для объекта объектов, который вы создали, а затем продолжайте декодировать поток.
Возвращаемое битмап будет равно null, что означает, что ему не выделена память, но вы можете прочитать размеры растрового изображения и таким образом определить его размер и отрегулировать соотношение inSampleSize.
Позже reset inJustDecodeBounds на false, теперь вы знаете коэффициент уменьшения масштаба, поэтому теперь можно сгенерировать растровое изображение требуемого размера.
Надеюсь, это поможет.