android中ViewHolder模式有啥好处?
Posted
技术标签:
【中文标题】android中ViewHolder模式有啥好处?【英文标题】:What is the benefit of ViewHolder pattern in android?android中ViewHolder模式有什么好处? 【发布时间】:2014-02-25 09:38:39 【问题描述】:当您开发 android 程序时;并且你想拥有一个 ArrayAdapter 你可以简单地拥有一个类(大多数时候带有 ViewHolder 后缀)或直接膨胀你的 convertView 并找到你的按 id 查看。那么使用 ViewHolder 有什么好处? 两者的例子都在这里:
@Override
public View getView(int position, View convertView, ViewGroup parent)
if (convertView == null)
convertView = ((Activity)getContext()).getLayoutInflater().inflate(R.layout.row_phrase, null);
((TextView) convertView.findViewById(R.id.txtPhrase)).setText("Phrase 01");
或者在 ArrayAdapter 中创建一个内部类,如下所示:
static class ViewHolder
ImageView leftIcon;
TextView upperLabel;
TextView lowerLabel;
最后在 getView 中:
@Override
public View getView(int position, View convertView, ViewGroup parent)
ViewHolder holder = null;
if (view == null)
view = LayoutInflater.from(context).inflate(R.layout.row_layout,
null, false);
holder = new ViewHolder();
holder.leftIcon = (ImageView) view.findViewById(R.id.leftIcon);
【问题讨论】:
【参考方案1】:了解列表视图回收的工作原理
How ListView's recycling mechanism works
您无法回收当前正在使用的行。上面的链接解释了listview回收机制是如何工作的
那么使用 ViewHolder 有什么好处呢?
引用文档
您的代码可能会在 ListView 滚动期间频繁调用 findViewById()
,这会降低性能。即使适配器返回一个膨胀的视图进行回收,您仍然需要查找元素并更新它们。避免重复使用findViewById()
的一种方法是使用“视图持有者”设计模式。
public View getView(int position, View convertView, ViewGroup parent)
ViewHolder holder;
if (convertView == null) // if convertView is null
convertView = mInflater.inflate(R.layout.mylayout,
parent, false);
holder = new ViewHolder();
// initialize views
convertView.setTag(holder); // set tag on view
else
holder = (ViewHolder) convertView.getTag();
// if not null get tag
// no need to initialize
//update views here
return convertView;
你错过了重要的部分convertView.setTag(holder)
和holder = (ViewHolder) ConvertView.getTag()
http://developer.android.com/training/improving-layouts/smooth-scrolling.html
【讨论】:
感谢您提供有用的信息。那么它对 notifyDataSetChanged 有影响吗? 当您想通过添加新项目或从中删除来刷新列表视图时,您将使用notifyDataSetChanged
。是的,您的 getView 被调用。但是使用 ViewHolder 可以提高性能。这与notfiyDatSetChanged()
无关
@mohammadjannesary developer.android.com/reference/android/widget/…
@Reghunandan 我更改了程序并使用了 ViewHolder;我有一个 400 行的列表视图;滚动速度增加了很多;谢谢
@mohammadjannesary 观众肯定有帮助。【参考方案2】:
当您浏览您的 ListView 时,在任何给定时间只会显示少数视图。这意味着您不必为适配器中的每个项目都实例化视图;当视图滚动到屏幕外时,它可以被重复使用或回收。
视图回收和 ViewHolder 模式不一样。 ViewHolder 模式只是为了减少您拨打的view.findViewById(int)
的数量。 ViewHolder 模式仅在您利用视图回收时才有效。
在getView(int position, View convertView, ViewGroup parent)
中,convertView
参数要么为空,要么是一个已被回收的视图:它仍然会绑定来自不同列表项的数据。
没有 ViewHolder 模式,您仍然可以利用视图回收(即不盲目地实例化视图):
public View getView(int position, View convertView, ViewGroup parent)
View view = convertView;
if (view == null)
view = // inflate new view
ImageView imageView = (ImageView) view.findViewById(R.id.listitem_image);
TextView textView = (TextView) view.findViewById(R.id.listitem_text);
TextView timestampView = (TextView) view.findViewById(R.id.listitem_timestamp);
ProgressBar progressSpinnerView = (ProgressBar) view.findViewById(R.id.progress_spinner);
// TODO: set correct data for this list item
// imageView.setImageDrawable(...)
// textView.setText(...)
// timestampView.setText(...)
// progressSpinnerView.setProgress(...)
return view;
上面是一个视图回收的例子——我们不会为每一行膨胀一个新的视图;如果我们没有得到一个可以重用的视图,我们只会夸大一个视图。 避免让视图膨胀是在滚动列表时绝对有助于提高性能的部分:利用视图回收。
那么,ViewHolder 有什么用呢?我们目前正在为 每个 项执行 4x findViewById(int)
,无论该行本身是否已经存在。当findViewById(int)
递归遍历 ViewGroup 直到找到具有给定 ID 的后代时,这对于我们回收的视图来说有点没有意义 - 我们正在重新查找我们已经引用过的视图。
在“找到”子视图后使用 ViewHolder 对象来保存对子视图的引用来避免这种情况:
private static class ViewHolder
final TextView text;
final TextView timestamp;
final ImageView icon;
final ProgressBar progress;
ViewHolder(TextView text, TextView timestamp, ImageView icon, ProgressBar progress)
this.text = text;
this.timestamp = timestamp;
this.icon = icon;
this.progress = progress;
View.setTag(Object)
允许您告诉视图保存任意对象。如果我们在调用 findViewById(int)
之后使用它来保存 ViewHolder 的实例,那么我们可以在回收的视图上使用 View.getTag()
以避免一次又一次地进行调用。
public View getView(int position, View convertView, ViewGroup parent)
View view = convertView;
if (view == null)
view = // inflate new view
ViewHolder holder = createViewHolderFrom(view);
view.setTag(holder);
ViewHolder holder = view.getTag();
// TODO: set correct data for this list item
// holder.icon.setImageDrawable(...)
// holder.text.setText(...)
// holder.timestamp.setText(...)
// holder.progress.setProgress(...)
return view;
private ViewHolder createViewHolderFrom(View view)
ImageView icon = (ImageView) view.findViewById(R.id.listitem_image);
TextView text = (TextView) view.findViewById(R.id.listitem_text);
TextView timestamp = (TextView) view.findViewById(R.id.listitem_timestamp);
ProgressBar progress = (ProgressBar) view.findViewById(R.id.progress_spinner);
return new ViewHolder(text, timestamp, icon, progress);
The performance benefits of this optimisation is questionable,但那是benefit of the ViewHolder。
【讨论】:
【参考方案3】:ViewHolder
设计模式用于加速ListView
的渲染 - 实际上为了使其工作顺利,每次渲染列表项时使用 findViewById 非常昂贵(它会进行 DOM 解析),它必须遍历您的布局层次结构并实例化对象。由于列表可以在滚动过程中非常频繁地重绘其项目,因此这样的开销可能会很大。
你可以找到很好的解释它是如何工作的:
http://www.youtube.com/watch?v=wDBM6wVEO70&feature=youtu.be&t=7m
从第 10 分钟开始,您已经通过谷歌专家解释了 ViewHolder 设计模式。
[编辑]
findViewById 不是实例化新对象,它只是遍历层次结构 - 这里是参考 http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/view/ViewGroup.java#3610
【讨论】:
【参考方案4】:首先:
在ListView
中,当您滚动ListView
时,您需要创建新项目并将其数据绑定在其上,因此如果ListView
中有很多项目,则可能会导致内存泄漏,因为您为项目创建了更多对象,但是Android 使用回收其大部分 API 的概念,这意味着您创建一个对象并使用它而不是销毁它并声明新对象,因此当您滚动 ListView
API 时使用您滚动的不可见项目并将其传递给 @ 987654325@ 方法是convertView
所以在这里你处理更多的项目ListView
其次:
如果您在ListView
中有自定义项,则需要将自定义布局附加到ListView
的每个项上,这样您每次ListView
使用findViewById
绑定新项以获取布局项的引用。此方法将以递归方式搜索您的项目,因此您 ViewHolder
将帮助您仅完成一次递归,然后它将为您保留布局项目的引用,直到您可以将其附加到 @987654333 @
希望这对您有所帮助,并在任何不明显的事情上给我反馈
【讨论】:
不利用视图回收机制并不是内存泄漏的原因。内存泄漏!= 高内存消耗以上是关于android中ViewHolder模式有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章