简体   繁体   English

Android-具有ImageView,LRUcache和ViewHolder的滞后ListView

[英]Android - Laggy ListView with ImageView, LRUcache and ViewHolder

I have a ListView in which every row has some text and two ImageView : one is the same for every row, the other depends on the current item. 我有一个ListView ,其中每一行都有一些文本和两个ImageView :一个对于每一行都是相同的,另一行取决于当前项目。

This is my adapter: 这是我的适配器:

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();
        }
    }
}

The first ImageView is a Bitmap stored in a Exhibition variable. 第一个ImageView是存储在Exhibition变量中的Bitmap The second one changes the visibility of a text to get a expandable-like effect (because for now I can't convert the ListView to a ExpandableListView ). 第二个更改文本的可见性以获得类似可扩展的效果(因为现在我无法将ListView转换为ExpandableListView )。 I tried different things like the cache, an AsyncTask , removing the custom click listener, put everything in ViewHolder but the scrolling is full of microlags. 我尝试了其他操作,例如缓存, AsyncTask ,删除了自定义的单击侦听器,将所有内容都放入ViewHolder但是滚动充满了微滞。 Is there something wrong in the adapter that I don't get it? 适配器中有什么我不明白的地方吗?

To make your list smooth, you can try this following options, 为了使您的列表顺畅,您可以尝试以下方法,

  1. Instead of using your own way of Bitmap caching, you can try to use the popular library like Glide , Picasso or some other opensource 您可以尝试使用流行的库(例如GlidePicasso或其他一些开源库),而不是使用自己的位图缓存方式
  2. Try to avoid doing the time-consuming operation in getView, ex - Date conversion you can move to object level when you build the model object, It will be one time per object. 尝试避免在getView中进行耗时的操作,例如,在构建模型对象时可以将日期转换为对象级别,这将是每个对象一次。
  3. You can try out Recyclerview instead of ListView 您可以尝试使用Recyclerview而不是ListView

There are a couple of things you can do to improve performance. 您可以采取几项措施来提高性能。

Figure out exactly what is slow 弄清楚到底是什么慢

Learn about profiling which can tell you which functions are the ones that are being called the most and/or that take the longest to complete. 了解有关概要分析的信息,该概要分析可以告诉您哪些函数被调用最多和/或花费时间最长。 This way you can decide where to invest time fixing or changing code. 这样,您可以决定在哪里投入时间修复或更改代码。

See https://developer.android.com/studio/profile/android-profiler and https://developer.android.com/studio/profile/ 参见https://developer.android.com/studio/profile/android-profilerhttps://developer.android.com/studio/profile/

ViewHolder pattern ViewHolder模式

You're misusing the ViewHolder pattern . 您正在滥用ViewHolder模式 In your code you have a single instance of ViewHolder in the adapter's viewHolder field. 在您的代码中,适配器的viewHolder字段中只有一个ViewHolder实例。 You then use this field inside the getView() function like a regular local variable. 然后,您可以像常规的局部变量一样在getView()函数内部使用此字段。

You then call row.findViewById() multiple times, even if the convertView wasn't null . 然后,您多次调用row.findViewById() ,即使convertView不是null The findViewById() calls are slow, and the advantage of the view holder is that you only have to call it once per view after expansion (in the convertView==null branch of the if ). findViewById()调用很慢,并且视图持有者的优点是,扩展后每个视图只需要调用一次(在if的convertView == null分支中)。

Instead you should have 1 view holder per row-view. 取而代之的是,每个行视图应该有1个视图持有者。 Note that you're not creating a new ViewHolder to assign with setTag() , but you're reusing the same one. 请注意,您不是在创建要使用setTag()分配的新ViewHolder ,而是在重复使用相同的ViewHolder Then instead of the variables such as descr , ttl , city , should be fields of the ViewHolder and thus quick to reference. 然后,应该使用ViewHolder字段来代替诸如descrttlcity之类的ViewHolder ,从而可以快速进行引用。

Creating unnecessary objects 创建不必要的对象

Memory allocation is also slow. 内存分配也很慢。

You're also creating objects each time getView() gets called that you can instead create once total and just reuse. 每次调用getView()时,您也要创建对象,而您可以创建一个对象,而仅创建一次即可,并且可以重复使用。

One such example is the SimpleDateFormat that could be created once in the adapter constructor and simply used to produce the text. 这样的例子之一就是SimpleDateFormat ,它可以在适配器构造函数中创建一次,并仅用于生成文本。

Look into how you can avoid creating so many String objects as well. 研究如何避免创建太多String对象。 Formatting with a string buffer or something similar. 使用字符串缓冲区或类似格式进行格式化。 You don't show the source code for the Exhibition class, so it's not clear why there is a need to create a Date object with the result of calling getStart() and getEnd() . 您没有显示Exhibition类的源代码,因此尚不清楚为什么需要创建一个带有调用getStart()getEnd()的结果的Date对象。

If the 'start' and 'end' fields of the Exhibition objects are never used as long s, consider turning them into immutable Date s during the JSON parsing instead of every time they are used. 如果Exhibition对象的'start'和'end'字段从未使用过long的时间,请考虑在JSON解析期间而不是每次使用它们时将它们变成不可变的Date

Potential slow calls in UI thread UI线程中可能的缓慢调用

The source code for the Exhibition class is not shown, so we can't tell what the Exhitition.getHeader() function does. 没有显示Exhibition类的源代码,因此我们无法确定Exhitition.getHeader()函数的作用。 If there is bitmap download and/or decoding, moving that to a background thread (and updating after the bitmap is ready) will improve the ListView s scrolling performance. 如果有位图下载和/或解码,则将其移至后台线程(并在位图准备好后进行更新)将提高ListView的滚动性能。

Unnecessary Calls 不必要的电话

There are calls that are being performed even if not needed. 即使不需要,也有正在执行的呼叫。 For example the assignment of the On Click listener at the end of getView() . 例如,在getView()末尾的On Click侦听器的分配。 You can get away with setting it only once when you do the inflation (when convertView is null ) since all the rows use the same listener. 由于所有行都使用相同的侦听器,因此在进行通货膨胀(当convertViewnull )时,只能设置一次。

Avoid filling the memory 避免填满内存

You mentioned that each Exhibition object has a Bitmap field that is set when the JSON is parsed. 您提到每个Exhibition对象都有一个Bitmap字段,该字段在解析JSON时设置。 This means that all the bitmaps are in memory all the time. 这意味着所有位图始终都在内存中。 This means that in this case the LRU cache is not necessary, since there's always a strong reference to the bitmaps. 这意味着在这种情况下,LRU缓存不是必需的,因为始终强烈引用位图。

It also means that as the number of items in the list increases the memory needed grows. 这也意味着随着列表中项目数量的增加,所需的内存也随之增加。 As more memory is used then garbage collection (GC) needs to happens more often, and GC is slow and can cause stutter or freezes. 随着使用更多内存,垃圾收集(GC)需要更频繁地发生,并且GC速度很慢,并且可能导致结结或冻结。 Profiling can tell you if the freezes you're experiencing are due to GC. 分析可以告诉您您遇到的冻结是否是由于GC引起的。

The bitmap cache would be useful if there is only a few bitmaps in memory at a time, the ones needed for the items that are currently visible in the list, and a few more. 如果一次内存中只有几个位图,列表中当前可见的项目所需要的位图,以及其他几个位图,则位图缓存将很有用。 If the needed bitmap is not found in cache then it should be loaded from disk or downloaded from the network. 如果在缓存中找不到所需的位图,则应从磁盘加载或从网络下载。

PS 聚苯乙烯

Keep in mind that you have a public setOnCustomClickListener() function that only assigns the reference to the field. 请记住,您有一个公共的setOnCustomClickListener()函数,该函数仅将引用分配给该字段。 If you call that with a new listener your current code will use the old listener on all the rows that weren't refreshed and updated with the new reference. 如果使用新的侦听器进行调用,则当前代码将在所有未使用新引用刷新和更新的行上使用旧的侦听器。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM