Android - Laggy ListView с помощью ImageView, LRUcache и ViewHolder - PullRequest
0 голосов
/ 16 октября 2018

У меня есть ListView, в котором каждая строка имеет некоторый текст, а две ImageView: одна одинакова для каждой строки, другая зависит от текущего элемента.

Это мой адаптер:

mArrayAdapter(Context context, int layoutResourceId, ArrayList<Exhibition>  data) {
    super(context, layoutResourceId, data);
    this.context = context;
    this.layoutResourceId = layoutResourceId;
    this.list = data;
    this.originalList = data;
    viewHolder = new ViewHolder();
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            return bitmap.getByteCount() / 1024;
        }
    };

}

@Override
@NonNull
public View getView(final int position, View convertView, @NonNull final ViewGroup parent) {
    View row;
    final Exhibition ex;
    if(convertView==null){
        row = LayoutInflater.from(getContext()).inflate(R.layout.row,parent,false);

        viewHolder.expand = (ImageView)row.findViewById(R.id.expand);
        row.setTag(viewHolder);
    }
    else {
        row = convertView;
        viewHolder = (ViewHolder)row.getTag();
    }
    ex = list.get(position);


    descr = (TextView)row.findViewById(R.id.descr);
    ttl = (TextView)row.findViewById(R.id.title);
    city = (TextView)row.findViewById(R.id.city);
    dates = (TextView)row.findViewById(R.id.dates);
    museum = (TextView)row.findViewById(R.id.location);
    header = (ImageView)row.findViewById(R.id.hd);

    ttl.setText(ex.name);
    descr.setText(ex.longdescr);
    museum.setText(ex.museum);
    city.setText(ex.city);

    final Bitmap bitmap = getBitmapFromMemCache(ex.key);
    if (bitmap != null) {
        header.setImageBitmap(bitmap);
    } else {
        header.setImageBitmap(ex.getHeader());
        addBitmapToMemoryCache(ex.key,ex.header);
    }


    SimpleDateFormat myFormat = new SimpleDateFormat("dd/MM/yyyy", Locale.ITALY);
    Date start = new Date(ex.getStart()), end = new Date(ex.getEnd());

    String startEx = myFormat.format(start);
    String endEx = myFormat.format(end);

    String finalDate = getContext().getResources().getString(R.string.ex_date, startEx, endEx);

    dates.setText(finalDate);

    viewHolder.expand.setId(position);

    if(position == selectedId){
        descr.setVisibility(View.VISIBLE);
        ttl.setMaxLines(Integer.MAX_VALUE);
        dates.setMaxLines(Integer.MAX_VALUE);
        museum.setMaxLines(Integer.MAX_VALUE);
        city.setMaxLines(Integer.MAX_VALUE);
    }else{
        descr.setVisibility(View.GONE);
        ttl.setMaxLines(1);
        dates.setMaxLines(1);
        museum.setMaxLines(1);
        city.setMaxLines(1);
    }

    viewHolder.expand.setOnClickListener(this.onCustomClickListener);

    return row;
}

public void setDescr(int p){
    selectedId = p;
}

public void setOnCustomClickListener(final View.OnClickListener onClickListener) {
    this.onCustomClickListener = onClickListener;
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}


@Override
public int getCount()
{
    return list.size();
}

@Override
public boolean isEnabled(int position)
{
    return true;
}

@Override
public Exhibition getItem (int pos){
    return list.get(pos);
}

void resetData() {

    list = originalList;
}

private class ViewHolder {

    ImageView expand,header;

}

@Override
@NonNull
public Filter getFilter() {
    if (valueFilter == null) {
        Log.d("SEARCH1","New filter");
        valueFilter = new ValueFilter();
    }
    return valueFilter;
}

private class ValueFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {

        FilterResults results = new FilterResults();
        if(constraint == null || constraint.length() == 0){
            results.values = originalList;
            results.count = originalList.size();
        }
        else {

            List<Exhibition> nExhList = new ArrayList<>();

            for(Exhibition e : list){
                Log.d("NAMEE",e.name + " " + constraint.toString());
                if (e.getName().toUpperCase().contains(constraint.toString().toUpperCase()) || e.getCity().toUpperCase().contains(constraint.toString().toUpperCase())
                        ||e.getMuseum().toUpperCase().contains(constraint.toString().toUpperCase()) || e.getLongDescription().toUpperCase().contains(constraint.toString().toUpperCase())
                        || e.getDescription().toUpperCase().contains(constraint.toString().toUpperCase()) || e.getCategory().toUpperCase().contains(constraint.toString().toUpperCase())){
                    nExhList.add(e);
                }
            }
            results.values= nExhList;
            results.count=nExhList.size();
        }
        return results;
    }

    @Override
    protected void publishResults(CharSequence constraint,
                                  FilterResults results) {
        if(results.count==0){
            notifyDataSetInvalidated();
        }
        else{
            list = (ArrayList<Exhibition>)results.values;
            notifyDataSetChanged();
        }
    }
}

Первый ImageView - это Bitmap, сохраненный в переменной Exhibition.Второй изменяет видимость текста, чтобы получить эффект, похожий на расширяемый (потому что пока я не могу преобразовать ListView в ExpandableListView).Я пробовал разные вещи, такие как кэш, AsyncTask, удаляя обработчик пользовательских кликов, помещал все в ViewHolder, но прокрутка полна микролагов.Что-то не так в адаптере, что я не понимаю?

Ответы [ 2 ]

0 голосов
/ 17 октября 2018

Есть несколько вещей, которые вы можете сделать для повышения производительности.

Выясните, что именно медленно

Узнайте о профилировании, которое может сказать вам, какие функции вызываютсясамый и / или который занимает больше всего времени для завершения.Таким образом, вы можете решить, куда тратить время на исправление или изменение кода.

См. https://developer.android.com/studio/profile/android-profiler и https://developer.android.com/studio/profile/

Шаблон ViewHolder

Вы неправильно используете Шаблон ViewHolder .В вашем коде у вас есть один экземпляр ViewHolder в поле viewHolder адаптера.Затем вы используете это поле внутри функции getView() как обычная локальная переменная.

Затем вы вызываете row.findViewById() несколько раз, даже если convertView не было null.Вызовы findViewById() являются медленными, и преимущество держателя представления состоит в том, что вам нужно вызывать его только один раз для представления после раскрытия (в нулевой ветви convertView == if).

Вместоу вас должно быть 1 держатель на просмотр строки.Обратите внимание, что вы не создаете новый ViewHolder для назначения с setTag(), но вы используете тот же самый.Тогда вместо переменных, таких как descr, ttl, city, должны быть поля ViewHolder и, следовательно, они могут быстро ссылаться.

Создание ненужных объектов

Распределение памятитакже медленный.

Вы также создаете объекты каждый раз, когда вызывается getView(), который вы можете создать один раз и просто использовать повторно.

Одним из таких примеров является SimpleDateFormat, который можетСоздайте его один раз в конструкторе адаптера и просто используйте для создания текста.

Посмотрите, как можно избежать создания стольких String объектов.Форматирование с помощью строкового буфера или чего-то подобного.Вы не показываете исходный код для класса Exhibition, поэтому неясно, почему существует необходимость создания Date объекта с результатом вызова getStart() и getEnd().

Если поля 'start' и 'end' объектов Exhibition никогда не используются как long s, рассмотрите возможность превращения их в неизменяемые Date s во время анализа JSON вместо каждого их использования.

Потенциальные медленные вызовы в потоке пользовательского интерфейса

Исходный код класса Exhibition не показан, поэтому мы не можем сказать, что делает функция Exhitition.getHeader().Если имеется загрузка и / или декодирование растрового изображения, перемещение его в фоновый поток (и обновление после того, как растровое изображение будет готово) улучшит производительность прокрутки ListView.

Ненужные вызовы

Тамзвонки, которые выполняются, даже если они не нужны.Например, назначение прослушивателя «По щелчку» в конце getView().Вы можете избежать установки только один раз, когда вы выполняете накачивание (когда convertView равно null), поскольку все строки используют один и тот же слушатель.

Избегайте заполнения памяти

Youупомянул, что каждый объект Exhibition имеет поле Bitmap, которое устанавливается при разборе JSON.Это означает, что все растровые изображения постоянно находятся в памяти.Это означает, что в этом случае кэш-память LRU не требуется, поскольку всегда существует сильная ссылка на растровые изображения.

Это также означает, что по мере увеличения количества элементов в списке необходимая память увеличивается.Поскольку используется больше памяти, сбор мусора (GC) должен происходить чаще, и GC работает медленно и может вызвать заикание или зависание.Профилирование может сказать вам, происходит ли зависание, которое вы испытываете из-за GC.

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

PS

Имейте в виду, что у вас есть общедоступная функция setOnCustomClickListener(), которая назначает толькоссылка на поле.Если вы вызываете это с новым слушателем, ваш текущий код будет использовать старый слушатель во всех строках, которые не были обновлены и обновлены с новой ссылкой.

0 голосов
/ 17 октября 2018

Чтобы сделать ваш список гладким, вы можете попробовать следующие варианты:

  1. Вместо того, чтобы использовать свой собственный способ кэширования растровых изображений, вы можете попробовать использовать популярную библиотеку, такую ​​как Glide , Пикассо или какой-либо другой ресурс с открытым исходным кодом
  2. Старайтесь избегать длительных операций в getView, например, при преобразовании даты вы можете перейти на уровень объекта при построении объекта модели.может быть один раз на объект.
  3. Вы можете попробовать Recyclerview вместо ListView
...