Много мусора в списке - PullRequest
21 голосов
/ 12 мая 2011

У меня есть ListView, который использует пользовательский адаптер. Пользовательский адаптер getView использует все рекомендуемые методы:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    SuscriptionsViewsHolder holder;
    ItemInRootList item = mItemsInList.get(position);

    if (convertView == null) {
         convertView = mInflater.inflate(R.layout.label, null);

         holder = new SuscriptionsViewsHolder();
         holder.label = (TextView) convertView.findViewById(R.id.label_label);
         holder.icon = (ImageView) convertView.findViewById(R.id.label_icon);

        convertView.setTag(holder);
    } else {
        holder = (SuscriptionsViewsHolder) convertView.getTag();
    }

    String text = String.format("%1$s (%2$s)", item.title, item.unreadCount);
    holder.label.setText(text);
    holder.icon.setImageResource(item.isLabel ? R.drawable.folder : R.drawable.file );

    return convertView;
}

Однако, когда я прокручиваю, это вяло из-за тяжелой сборки мусора:

GC_EXTERNAL_ALLOC freed 87K, 48% free 2873K/5447K, external 516K/519K, paused 30ms
GC_EXTERNAL_ALLOC freed 7K, 48% free 2866K/5447K, external 1056K/1208K, paused 29ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2866K/5447K, external 1416K/1568K, paused 28ms
GC_EXTERNAL_ALLOC freed 5K, 48% free 2865K/5447K, external 1600K/1748K, paused 27ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2865K/5447K, external 1780K/1932K, paused 30ms
GC_EXTERNAL_ALLOC freed 2K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms
GC_EXTERNAL_ALLOC freed 2K, 48% free 2870K/5447K, external 1780K/1932K, paused 25ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms
GC_EXTERNAL_ALLOC freed 3K, 48% free 2870K/5447K, external 1780K/1932K, paused 25ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2871K/5447K, external 1780K/1932K, paused 28ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2871K/5447K, external 1780K/1932K, paused 26ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 27ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 29ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 26ms
GC_EXTERNAL_ALLOC freed <1K, 48% free 2870K/5447K, external 1780K/1932K, paused 34ms

Что, похоже, не так?

РЕДАКТИРОВАТЬ @ 12: 47 GMT:

На самом деле это немного сложнее, чем это. Пользовательский интерфейс моего приложения состоит из 2 частей. Один - мозг экрана, создающий представления, обрабатывающий пользовательский ввод и т. Д. Другой - Fragment, если устройство имеет Android 3.0, в противном случае это Activity.

Сборщик мусора произошел на моем устройстве Nexus One 2.3.3, поэтому использовался Activity. У меня нет моего Xoom, чтобы проверить поведение с Fragment.

Я мог бы опубликовать источник, если потребуется, но позвольте мне попытаться объяснить это:

  • RootList - это мозг интерфейса пользователя. Это содержит :
    • a List<> предметов, которые будут помещены в ListView.
    • метод, который строит этот список из базы данных SQLite
    • пользовательский BaseAdapter, который содержит в основном только метод getView, вставленный выше
  • RootListActivity - это ListActivity, который:
    • использует макет XML
    • макет, конечно, имеет список с идентификатором android.id.list
    • обратные вызовы Activity перенаправляются в класс RootList с использованием экземпляра RootList, созданного при создании действия (конструктор, а не onCreate)
    • в onCreate я вызываю RootList методы, которые создадут список элементов, и устанавливаю данные списка в новый экземпляр моего пользовательского класса, полученный из BaseAdapter

РЕДАКТИРОВАТЬ 17 мая @ 21:36 по Гринвичу:

Вот код Деятельности и класс, который делает вещи. http://pastebin.com/EgHKRr4r

Ответы [ 9 ]

21 голосов
/ 21 мая 2011

Я нашел проблему.Мой XML-макет для действия был:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <include android:id="@+id/rootlist_header" layout="@layout/pre_honeycomb_action_bar" />

    <ListView android:id="@android:id/list"
        android:layout_below="@id/rootlist_header"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:textColor="#444444"
        android:divider="@drawable/list_divider"
        android:dividerHeight="1px"
        android:cacheColorHint="#00000000" />

</RelativeLayout>

Если я удаляю android:cacheColorHint="#00000000", тяжелый GC отсутствует, и прокрутка составляет плавный !:)

Я действительно не знаю, почему был установлен этот параметр, потому что он мне не нужен.Возможно, я слишком много копировал вместо того, чтобы на самом деле создать свой макет XML.

СПАСИБО за поддержку, я очень ценю вашу помощь.

11 голосов
/ 20 мая 2012

У меня также была проблема с android:cacheColorHint="#00000000". Мне нужно было его использовать, потому что у меня фиксированное фоновое изображение.

Я обнаружил, что установка следующих свойств отключает кеширование представления списка Android, и список прокручивается плавно (GC больше не вызывается так часто):

android:scrollingCache="false"
android:animationCache="false"

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

android:listSelector="#00000000"
8 голосов
/ 20 мая 2011

Вы должны использовать DDMS и его Allocation Tracker, чтобы точно выяснить, что генерирует так много объектов.Однако обратите внимание, что String.format () хорошо известен для генерации большого количества мусора.

http://developer.android.com/resources/articles/track-mem.html

1 голос
/ 17 декабря 2012

это

 String text = String.format("%1$s (%2$s)", item.title, item.unreadCount);

вызовет GC много раз.

String.format() выделяет некоторую временную память в дополнение к строке, которую она в итоге производит.

другой способ сделать это.

используя класс StringBuilder, вот так.

StringBuilder builder = new StringBuilder(128);
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    SuscriptionsViewsHolder holder;
    ItemInRootList item = mItemsInList.get(position);

    if (convertView == null) {
         convertView = mInflater.inflate(R.layout.label, null);

         holder = new SuscriptionsViewsHolder();
         holder.label = (TextView) convertView.findViewById(R.id.label_label);
         holder.icon = (ImageView) convertView.findViewById(R.id.label_icon);

        convertView.setTag(holder);
    } else {
        holder = (SuscriptionsViewsHolder) convertView.getTag();
    }    

String text = String.format("%1$s (%2$s)", item.title, item.unreadCount);

    builder.setLength(0);
    builder.append(item.title).append(" (").append(item.unreadCount).append(")");
    holder.label.setText(builder.toString());
    holder.icon.setImageResource(item.isLabel ? R.drawable.folder : R.drawable.file );

    return convertView;
}
1 голос
/ 21 мая 2011

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

Это то, что вы делаете в методе createDefaultLabelsList ().

mItemsInList.clear();
ItemInRootList item;

do {
    item = new ItemInRootList();
    item.isLabel = true;
    item.id = c.getString(c.getColumnIndex(Labels.KEY_ID));
    item.title = Labels.label(item.id);
    //TODO: fix this label.label = c.getString(c.getColumnIndex(Labels.KEY_LABEL));
    item.unreadCount = c.getString(c.getColumnIndex(Labels.KEY_UNREAD_COUNT));
    mItemsInList.add(item);
} while (c.moveToNext());

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

mItemsInList.clear();

do {
    ItemInRootList item = new ItemInRootList();
    item.isLabel = true;
    item.id = c.getString(c.getColumnIndex(Labels.KEY_ID));
    item.title = Labels.label(item.id);
    //TODO: fix this label.label = c.getString(c.getColumnIndex(Labels.KEY_LABEL));
    item.unreadCount = c.getString(c.getColumnIndex(Labels.KEY_UNREAD_COUNT));
    mItemsInList.add(item);
} while (c.moveToNext());

и наслаждайтесь своим быстрым быстрым списком!Это решило это для меня на htro hero (2.1) и Xoom (3.1) с использованием ListActivity.

Вот как бы я это сделал в первую очередь - но я не совсем уверен, как это объясняет вялостьПоведение с вашей первоначальной реализацией.

Я узнал об этом, потому что для того, чтобы воспроизвести вашу проблему, мне пришлось заменить способ, которым вы построили список, просто создал список из 10 тыс. случайных меток и наблюдал, как он прокручивается очень быстро.Затем я заметил, что мы не использовали один и тот же цикл, вы добавляли одну и ту же ссылку снова и снова, пока я добавлял 10 000 различных ссылок.Я переключился на вашу реализацию и воспроизвел ошибку.

0 голосов
/ 29 июня 2014

Это начинается с 4.4.4 обновления KitKat.Повторите код уже нормальный код на нем, и эта проблема появилась.ListView теперь совершенно непригоден из-за очень медленной прокрутки.(Nexus 4)

И я не использую на нем никаких форматеров.

Когда прокручивается View, появляется большое сообщение GC.

0 голосов
/ 28 ноября 2012

У меня похожий вопрос, мой список покажет изображения из интернета и сохранит их на SD-карте.Когда приложение запустится позже, будет использовано локальное изображение, затем, когда я прокручиваю просмотр списка, на logcat появляется много GC, и представление слегка оглушается.

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

Кстати, я использовал загрузчик android-imagedownloader из http://code.google.com/p/android-imagedownloader/, и добавляю некоторый локальный код файлового кэша.

Так вот мойРешение, когда он загружает изображение из локального файла, я добавлю объект растрового изображения в кэш-память (статический HashMap), после этого, когда просмотр списка прокручивается вверх и вниз, он получит изображение из кеш-памяти, и много GC не произошлоеще раз.

0 голосов
/ 03 октября 2011

String text = String.format ("% 1 $ s (% 2 $ s)", item.title, item.unreadCount);Может быть String.format () создает много промежуточных строк, которые загрязняют GC.Попробуйте StringBuilder и попробуйте кэшировать строку, которую он строит.Также, как сказал @tmho, попробуйте holder.icon.setImageDrawable ();вместо holder.icon.setImageResource ();потому что setImageResource () создает Drawable объект каждый раз.И каждый Drawable составляет около 50-60 байт.Кажется, что в этом случае растровое изображение не дублируется, это просто отрисовываемый контейнер, но этого может быть достаточно для периодических вызовов GC.

0 голосов
/ 14 мая 2011

попробуйте holder.icon.setImageBitmap(); вместо holder.icon.setImageResource();

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...