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

时间:2018-10-16 20:32:04

标签: android performance listview bitmap android-lru-cache

我有一个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 :(得分:2)

要使您的列表平滑,可以尝试以下方法,

  1. 您可以尝试使用流行的库,例如GlidePicasso或其他开源软件,而不是使用自己的位图缓存方式
  2. 尝试避免在getView中进行耗时的操作,例如-在构建模型对象时可以将日期转换为对象级别,每个对象一次。
  3. 您可以尝试使用Recyclerview而不是ListView

答案 1 :(得分:1)

您可以采取几项措施来提高性能。

弄清楚到底什么是慢的

了解配置文件,它可以告诉您哪些功能是 被称为最多和/或需要最长时间才能完成的任务。这样,您可以决定在哪里投入时间来修复或更改代码。

请参见https://developer.android.com/studio/profile/android-profilerhttps://developer.android.com/studio/profile/

ViewHolder模式

您滥用ViewHolder pattern。在您的代码中,适配器的ViewHolder字段中只有一个viewHolder实例。然后,您可以像常规的局部变量一样在getView()函数内部使用此字段。

然后,即使row.findViewById()不是convertView,您也会多次呼叫nullfindViewById()调用很慢,并且视图持有者的优点是,扩展后每个视图只需要调用一次(在if的convertView == null分支中)。

相反,每个行视图应该有1个视图持有者。请注意,您并不是在创建要与ViewHolder一起分配的新setTag(),而是要重用相同的对象。然后,应该使用descr的字段代替ttlcityViewHolder之类的变量,从而快速引用。

创建不必要的对象

内存分配也很慢。

每次调用getView()时,您也要创建对象,而您可以创建一个对象,而后只能重复使用。

一个这样的示例是SimpleDateFormat,它可以在适配器构造函数中创建一次,并仅用于生成文本。

研究如何避免创建太多String对象。使用字符串缓冲区或类似格式进行格式化。您没有显示Exhibition类的源代码,因此尚不清楚为什么需要创建一个Date对象并调用getStart()和{{1} }。

如果getEnd()对象的'start'和'end'字段从未用作Exhibition,请考虑在JSON解析期间将它们变成不可变的long s,而不是每个使用它们的时间。

UI线程中可能的慢速调用

未显示Date类的源代码,因此我们无法确定Exhibition函数的作用。如果有位图的下载和/或解码,将其移至后台线程(并在位图准备好后进行更新)将提高Exhitition.getHeader()的滚动性能。

不必要的通话

即使不需要,也有正在执行的呼叫。例如,ListView末尾的“单击时”侦听器的分配。由于所有行都使用相同的侦听器,因此在进行通货膨胀时(getView()convertView时,只能设置一次)。

避免填满内存

您提到每个null对象都有一个Exhibition字段,该字段是在解析JSON时设置的。这意味着所有位图始终都在内存中。这意味着在这种情况下,不需要LRU缓存,因为始终会强烈引用位图。

这还意味着随着列表中项目数量的增加,所需的内存也随之增加。随着使用更多内存,垃圾收集(GC)需要更频繁地发生,并且GC速度很慢,并且可能导致结结或冻结。分析可以告诉您您遇到的冻结是否是由于GC造成的。

如果一次内存中只有几个位图,列表中当前可见的项目所需要的位图,以及其他一些位图,则位图缓存将很有用。如果在缓存中找不到所需的位图,则应从磁盘加载或从网络下载。

P.S。

请记住,您有一个公共Bitmap函数,该函数仅将引用分配给该字段。如果您使用新的监听器进行调用,则当前代码将在所有未刷新并使用新引用进行更新的行上使用旧的监听器。