[英]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, 为了使您的列表顺畅,您可以尝试以下方法,
There are a couple of things you can do to improve performance. 您可以采取几项措施来提高性能。
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-profiler和https://developer.android.com/studio/profile/
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
字段来代替诸如descr
, ttl
, city
之类的ViewHolder
,从而可以快速进行引用。
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
。
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
的滚动性能。
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. 由于所有行都使用相同的侦听器,因此在进行通货膨胀(当
convertView
为null
)时,只能设置一次。
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.