Много мусора в списке
У меня есть 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
Что кажется неправильным?
EDIT @12: 47 GMT:
На самом деле это немного сложнее, чем это. Мой интерфейс приложения основан на 2 частях. Один из них - мозг экрана, создание представлений, обработка пользовательского ввода и т.д. Другим является Fragment
, если устройство имеет андроид 3.0, в противном случае это Activity
.
GC произошел на моем устройстве Nexus One 2.3.3, поэтому используйте Activity
. У меня нет моего Xoom со мной, чтобы проверить поведение с помощью Fragment
.
Я мог бы отправить источник, если потребуется, но позвольте мне попытаться объяснить это:
-
RootList
- это мозг пользовательского интерфейса. Это содержит:
- a
List<>
элементов, которые будут помещены в ListView
.
- метод, который создает этот список из SQLite db
- пользовательский BaseAdapter, который содержит в основном только метод getView, вставленный выше
-
RootListActivity
является ListActivity
, который:
- использует XML-макет
- У макета, конечно, есть список с id
android.id.list
- обратные вызовы
Activity
перенаправляются в класс RootList
с использованием экземпляра RootList, созданного при создании действия (конструктор, а не onCreate
)
- в
onCreate
, я вызываю RootList
методы, которые создадут список элементов, и установите данные списка в новый экземпляр моего пользовательского класса, полученного из BaseAdapter
EDIT on may 17th @9:36 PM GMT:
Здесь приведен код действия и класс, который делает вещи. http://pastebin.com/EgHKRr4r
Ответы
Ответ 1
Я нашел проблему. Мой 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-макет.
СПАСИБО за вашу поддержку, я очень ценю вашу помощь.
Ответ 2
У меня также была проблема с android:cacheColorHint="#00000000"
.
Мне нужно было использовать его, хотя, поскольку у меня есть фиксированное фоновое изображение.
Я обнаружил, что установка следующих свойств отключает кеширование списка виджетов Android, и список прокручивается гладко (GC не вызывается так часто):
android:scrollingCache="false"
android:animationCache="false"
Также, если вы хотите избавиться от цвета выбора по умолчанию, используйте это:
android:listSelector="#00000000"
Ответ 3
Вы должны использовать DDMS и Tracking Allocation Tracker, чтобы точно определить, что генерирует так много объектов. Обратите внимание, однако, что String.format() хорошо известен для создания больших объемов мусора.
http://developer.android.com/resources/articles/track-mem.html
Ответ 4
Я действительно просмотрел ваш код и немного взломал его, чтобы он работал на моих устройствах.
Я могу подтвердить, что ваш 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());
и наслаждайтесь быстрым списком! Это разрешило это для меня на htc hero (2.1) и Xoom (3.1), используя ListActivity.
Как я бы это сделал в первую очередь, но я не совсем уверен, как это объясняет вялое поведение с вашей первоначальной реализацией.
Я нашел это, потому что для того, чтобы воспроизвести вашу проблему, мне пришлось заменить способ создания списка, просто создал список из 10k случайных меток и наблюдал, как он прокручивается очень быстро. Затем я заметил, что мы не использовали точно такой же цикл, вы добавляли одну и ту же ссылку снова и снова, когда добавляли 10k разных ссылок. Я переключился на вашу реализацию и воспроизвел ошибку.
Ответ 5
this
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);
удаp >
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;
}
Ответ 6
попробуйте holder.icon.setImageBitmap();
вместо holder.icon.setImageResource();
Ответ 7
Строковый текст = 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 байт. Похоже, что растровое изображение heave не дублируется в этом случае, просто извлекаемый контейнер, но этого может быть достаточно, чтобы вызвать периодические вызовы GC.
Ответ 8
Это начинается с 4.4.4 обновления KitKat. Повторить код уже нормальный код на нем, и эта проблема появилась. ListView теперь полностью недоступен из-за очень медленного прокрутки. (Nexus 4)
И я не использую какие-либо форматиры на нем.
Когда прокручивается окно, появляется большое сообщение GC.
Ответ 9
У меня возник подобный вопрос, в моем списке будут отображаться изображения из Интернета и их сохранение на SD-карте. При запуске приложения позже будет использовано локальное изображение, а затем, когда я прокручу список, на логарифмическом экране появится много GC, и представление немного ошеломит.
Я заметил, что когда приложение было запущено впервые, с изображениями из Интернета, список выглядит нормально.
Кстати, я использовал android-imagedownloader из http://code.google.com/p/android-imagedownloader/ и добавил код локального кэша файлов.
Итак, вот мое решение, когда оно загружает изображение из локального файла, я добавлю объект растрового изображения в кеш памяти (статический HashMap), после этого, когда прокрутка списка вверх и вниз, он получит изображение из кэша памяти, и партии GC больше не повторялись.