Подтвердить что ты не робот

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

Ответ 1

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

Там есть библиотека, которая должна делать именно это, проверьте: https://github.com/davemorrissey/subsampling-scale-image-view

Ответ 2

Проблема кучи android заключается в том, что вы на самом деле не знаете, какую часть кучи вы можете использовать, потому что любое фоновое обслуживание может в любое время испортить вам все, если вы переступите ограничения памяти.

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

Ответ 3

Простым и понятным способом является использование свойства inJustDecodeBounds для параметров. Установите для этого свойства значение true для объекта объектов, который вы создали, а затем продолжайте декодировать поток.

Возвращаемое битмап будет равно null, что означает, что ему не выделена память, но вы можете прочитать размеры растрового изображения и таким образом определить его размер и отрегулировать соотношение inSampleSize.

Позже reset inJustDecodeBounds на false, теперь вы знаете коэффициент уменьшения масштаба, поэтому теперь можно сгенерировать растровое изображение требуемого размера.

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