Мое приложение показывает список 9 категорий, и каждая категория отображает Находящийся в галерее coverflow (любезно предлагаемый Neil Davies здесь) с изображениями выбранной категории.
Изображения выбираются с сети, каждого в пределах от 300K к 500K в размере, и хранятся в arrayList Drawables. Эти данные связываются с coverflow использование BaseAdapter (код ниже).
Каждый раз, когда я выхожу из coverflow и возвращаюсь к списку категорий, я очищаю arrayList (снова, код ниже).
В сценарии 1 мой arrayList содержит 5 Drawables. В этом сценарии я могу свободно просмотреть все категории и показать их изображения. Во время моего теста я циклически повторился через все категории в течение 5 раз, который кажется достаточно, чтобы решить, что нет никакой проблемы.
В сценарии 2 мой arrayList содержит 10 drawables. В этом сценарии я получаю исключение OutOfMemoryError при прохождении через изображений в 5-м или 6-м categeory:
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, но я прошел все категории значительное количество раз и не отказал. Я также использовал Память плагин Анализатора для Eclipse, который не представил потенциальных преступников.
Если бы система не могла бы обработать 10 изображений, как в scenarion 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);
}
очистка источника данных при выходе из категории (в конце ()):
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);
}
}
Править:
Хорошо, я применил функцию LogHeap Mathias на свой coverflow и здесь являюсь некоторыми выводами. До загрузки первой галереи:
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
Кажется, что все больше памяти выделяется при вводе галереи, но очень мало выпущено после выхода. Разве я не очищаю свой drawables правильно? Для каждого элемента в моем arrayList drawables я называю setCallBack (пустой указатель) и устанавливаю элемент в NULL. Это недостаточно?
Отчаянно нуждающийся в любом понимании.
Спасибо
Изображения берутся из Интернета, каждый от 300К до 500К в размер и хранится в arrayList из Чертежи.
Размер файла изображения, загружаемого из Интернета, не имеет прямого значения. Поскольку они преобразуются в растровые изображения, вам необходимо рассчитать ширину * высоту * 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());
Вот несколько информативных ссылок - обычно здесь много веток по этой теме.
Вот также полезный слайд Ромена Гая (разработчика Android Framework) о мягких ссылках, слабых ссылках, простых кэшах, обработке изображений : http://docs.huihoo.com/google/io/2009/Th_0230_TurboChargeYourUI-HowtomakeyourAndroidUIfastandefficient.pdf
Вот несколько советов:
Используете ли вы опцию inSampleSize? Она уменьшает потребление памяти, если вы масштабируете изображения. Странная проблема нехватки памяти при загрузке изображения в объект Bitmap
Вы должны вызывать Bitmap.recycle(), когда изображения больше не нужны. Я думаю, это важно в вашем случае. Android: OutofMemoryError: bitmap size exceeds VM budget with no reason I can see
Изображение, которое вы загружаете в галерее 5 или 6, может быть слишком большим и превышать максимальный размер, разрешенный VM.