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模式有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中使用 Fragments 而不是 Views 有啥好处?

在 avro 中存储模式有啥好处?

桥接模式有啥好处[关闭]

在星型模式表设计中包含关系有啥好处?

工厂模式有啥好处? [复制]

Android-进一步封装ViewHolder