Android: нехватка памяти в Галерее - PullRequest
21 голосов
/ 13 июля 2010

Мое приложение отображает список из 9 категорий, и в каждой категории отображается обложка на основе галереи (любезно предложенная Нилом Дэвисом здесь ) с изображениями выбранной категории.

Изображения извлекаются из Интернета, каждый размером от 300K до 500K, и хранятся в массиве списков Drawables. Эти данные привязываются к потоку при помощи BaseAdapter (код ниже).
Каждый раз, когда я выхожу из потока и возвращаюсь к списку категорий, я очищаю arrayList (опять же, код ниже).

В сценарии 1 мой arrayList содержит 5 Drawables. В этом сценарии я могу свободно просматривать все категории и показывать их изображения. Во время теста я перебирал все категории по 5 раз, и этого было достаточно, чтобы определить, что проблем нет.

В сценарии 2 мой arrayList содержит 10 рисованных объектов. В этом сценарии я получаю исключение OutOfMemoryError при просмотре изображений внутри 5-й или 6-й категории:

07-13 08:38:21.266: ERROR/dalvikvm-heap(2133): 819840-byte external allocation too large for this process.
07-13 08:38:21.266: ERROR/(2133): VM won't let us allocate 819840 bytes
07-13 08:38:21.277: DEBUG/skia(2133): --- decoder->decode returned false
07-13 08:38:21.287: WARN/dalvikvm(2133): threadid=25: thread exiting with uncaught exception (group=0x4001b188)
07-13 08:38:21.296: ERROR/AndroidRuntime(2133): Uncaught handler: thread Thread-64 exiting due to uncaught exception
07-13 08:38:21.308: ERROR/AndroidRuntime(2133): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:459)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:697)
07-13 08:38:21.308: ERROR/AndroidRuntime(2133):     at android.graphics.drawable.Drawable.createFromStream(Drawable.java:657)

Это не имеет смысла для меня. Если бы у меня была утечка памяти, я бы ожидал, что в какой-то момент сценария 1 произойдет сбой, но я прошел все категории значительное количество раз и не потерпел крах. Я также использовал плагин Memory Analyzer для Eclipse, который не представлял потенциальных виновников.

Если бы система не могла обработать 10 изображений, как в сценарии 2, я бы ожидал сбой в первой категории, но я потерпел крах только после 5 или 6 категорий.

Функции адаптера Coverflow:

public int getCount() {
     return DataManager.getInstance().getImageBufferInstance().getImageArraySize(); 
}

public Object getItem(int position) {    
     return DataManager.getInstance().getImagesBuffer().get(position);
}

public long getItemId(int position) {
     return position;
}

public View getView(int position, View convertView, ViewGroup parent) {      
         ImageView i;
         if (convertView == null)
             i = new ImageView(mContext);
         else
             i = (ImageView)convertView;
         Drawable bufferedImage = (Drawable)getItem(position);
         Log.v("getView", "position: " + position);
         i.setImageDrawable(bufferedImage);

         i.setLayoutParams(new CoverFlow.LayoutParams(Utils.getInstance().getScreenWidth() / 2,
                 Utils.getInstance().getScreenHeight() / 2));
         i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 

         try{
         //Make sure we set anti-aliasing otherwise we get jaggies
         BitmapDrawable drawable = (BitmapDrawable) i.getDrawable();
         drawable.setAntiAlias(true);
         }
         catch (Exception e)
         {
             Log.v("getView", "Exception: " + e.toString());
         }
         return i;      
     }

заполнение источника данных при входе в категорию:

for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++)  
{  
  String imageUrl = ImageBuffer.getInstance().getImageUrl(i);  
  Log.v("Initial", imageUrl);  
  Drawable fullImage = AsyncImageLoader.getInstance().loadImageByUrl(imageUrl);  
  ImageBuffer.getInstance().getImages().add(i, fullImage);  

}

очистка источника данных при выходе из категории (в finish ()):

for (int i = 0; i < ImageBuffer.getInstance().getImageArraySize(); i++)  
{  
  if (ImageBuffer.getInstance().images.get(i) != null)  
            {  
                ImageBuffer.getInstance().images.get(i).setCallback(null);  
                ImageBuffer.getInstance().images.set(i, null);  
            }    

}

EDIT:

ОК, я применил функцию Mathias LogHeap к своему покрытию, и вот некоторые выводы. До загрузки первой галереи:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 6.20MB of 6.28MB (0.07MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (0.00MB free)
DEBUG/dalvikvm(5221): GC freed 4558 objects / 638152 bytes in 84ms
DEBUG/dalvikvm(5221): GC freed 17 objects / 808 bytes in 67ms

После входа в первую галерею:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 14.90MB of 16.89MB (0.07MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 357 objects / 50080 bytes in 68ms
DEBUG/dalvikvm(5221): GC freed 353 objects / 27312 bytes in 67ms

После создания первой галереи:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 14.83MB of 16.89MB (0.11MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 330 objects / 17920 bytes in 77ms
DEBUG/dalvikvm(5221): GC freed 13 objects / 760 bytes in 67ms

После входа в пятую галерею:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 16.80MB of 23.32MB (0.08MB free) in [com.example.Coverflow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 842 objects / 99256 bytes in 73ms
DEBUG/dalvikvm(5221): GC freed 306 objects / 24896 bytes in 69ms

После выхода из пятой галереи:

DEBUG/Application(5221): debug. =================================
DEBUG/Application(5221): debug.heap native: allocated 16.74MB of 23.32MB (0.11MB free) in [com.example.Coverlow]
DEBUG/Application(5221): debug.memory: allocated: 4.00MB of 24.00MB (1.00MB free)
DEBUG/dalvikvm(5221): GC freed 331 objects / 18184 bytes in 68ms
DEBUG/dalvikvm(5221): GC freed 60 objects / 3128 bytes in 68ms

Кажется, что все больше и больше памяти выделяется при входе в галерею, но очень мало освобождается после выхода. Разве я не очищаю свои чертежи должным образом? Для каждого элемента в моем массиве списков объектов я вызываю setCallBack (null) и устанавливаю для элемента значение null. Разве этого недостаточно?
Отчаянно нуждаюсь в понимании.
Спасибо

Ответы [ 4 ]

37 голосов
/ 13 июля 2010

Изображения извлекаются из Интернета, каждый размером от 300K до 500K, и сохраняются в arrayList of Drawables.

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

Растровые изображения используют собственную кучу, которая обычно не отображается в hprof.Hprof должен показывать только количество оставшихся объектов, то есть BitmapDrawables или Bitmaps.

Я использую этот код в своем приложении для вывода текущей используемой памяти, используемой приложением, и собственной кучи:

public static void logHeap(Class clazz) {
    Double allocated = new Double(Debug.getNativeHeapAllocatedSize())/new Double((1048576));
    Double available = new Double(Debug.getNativeHeapSize())/1048576.0);
    Double free = new Double(Debug.getNativeHeapFreeSize())/1048576.0);
    DecimalFormat df = new DecimalFormat();
    df.setMaximumFractionDigits(2);
    df.setMinimumFractionDigits(2);

    Log.d(APP, "debug. =================================");
    Log.d(APP, "debug.heap native: allocated " + df.format(allocated) + "MB of " + df.format(available) + "MB (" + df.format(free) + "MB free) in [" + clazz.getName().replaceAll("com.myapp.android.","") + "]");
    Log.d(APP, "debug.memory: allocated: " + df.format(new Double(Runtime.getRuntime().totalMemory()/1048576)) + "MB of " + df.format(new Double(Runtime.getRuntime().maxMemory()/1048576))+ "MB (" + df.format(new Double(Runtime.getRuntime().freeMemory()/1048576)) +"MB free)");
    System.gc();
    System.gc();

    // don't need to add the following lines, it's just an app specific handling in my app        
    if (allocated>=(new Double(Runtime.getRuntime().maxMemory())/new Double((1048576))-MEMORY_BUFFER_LIMIT_FOR_RESTART)) {
        android.os.Process.killProcess(android.os.Process.myPid());
    }
}

, который я называю при запуске или завершении действия во время разработки.

logHeap(this.getClass());

Вот несколько информативных ссылок - как правило, здесь много тем по этой теме.

Вот также полезныйслайд от Romain Guy (инженер Android Framework) о мягких ссылках, слабых ссылках, простых кэшах, обработке изображений: http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient.pdf

4 голосов
/ 14 июля 2010

Вот несколько советов:

  1. Используете ли вы параметр inSampleSize? Это уменьшает потребление памяти, если вы масштабируете изображения. Странно нехватка памяти при загрузке изображения в растровый объект

  2. Вы должны вызывать Bitmap.recycle (), когда вам больше не нужны изображения. Я думаю, что это важно в вашем случае. Android: OutofMemoryError: размер растрового изображения превышает бюджет виртуальной машины без видимой причины

0 голосов
/ 06 февраля 2013

Вам лучше знать, что convertView в списке параметров getView всегда равно null.То есть галерея не использует старый вид изнутри.

0 голосов
/ 13 июля 2010

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

...