【问题标题】:About ViewHolder pattern implementation optimisation in ListView关于ListView中ViewHolder模式的实现优化
【发布时间】:2013-04-16 08:26:41
【问题描述】:

所以众所周知的 ViewHolder 模式使用通常看起来像(ListAdapter):

    ...

    @Override
    public View getView(final int position, View convertView, final ViewGroup parent) {

        final Album album = albums.get(position);

        ViewHolder viewHolder = null;
        if (convertView==null){
            convertView = inflater.inflate(R.layout.albums_list_item, null);

            final ImageView albumImage = (ImageView) convertView.findViewById(R.id.album_icon);

            final TextView txtTitle = (TextView) convertView.findViewById(R.id.album_title);

            final TextView txtDescription = (TextView) convertView.findViewById(R.id.album_copyright);

            viewHolder = new ViewHolder();
            viewHolder.albumImage = albumImage;
            viewHolder.txtTitle = txtTitle;
            viewHolder.txtDescription = txtDescription;
            convertView.setTag(viewHolder);
        }
        else
            viewHolder = (ViewHolder)convertView.getTag();

        viewHolder.txtTitle.setText(album.getTitle(locale));
        viewHolder.txtDescription.setText(album.getCopyrightInfo(locale));
        ...
        return convertView;
    }

而 ViewHolder 类通常看起来像:

static class ViewHolder{
    public ImageView previewImage;
    public TextView txtTitle;
    public TextView txtDescription;
}

我的问题是关于 ViewHolder 的实施。
1) 为什么不使用构造函数而不是初始化每个字段?
2)为什么它使用默认访问类型而不是受保护(实际上它必须是私有的,但这会由于 JIT 创建的静态访问器而影响性能)?好吧,我猜它只是关于继承。
那么为什么以下模式不是更好(不包括“保护与默认”访问类型):

protected static class ViewHolder{
    public final ImageView previewImage;
    public final TextView txtTitle;
    public final TextView txtDescription;

    public ViewHolder (final ImageView previewImage,  final TextView txtTitle, final TextView txtDescription){
        this.previewImage = previewImage;
        this.txtTitle = txtTitle;
        this.txtDescription = txtDescription;
    }
}

ListAdapter 的唯一变化是:

...
final TextView txtDescription = (TextView) convertView.findViewById(R.id.album_copyright);
viewHolder = new ViewHolder(albumImage, txtTitle, txtDescription);
convertView.setTag(viewHolder);
...

无论如何它必须调用构造函数。这只是品味问题吗?或者这个版本是否以某种方式变慢了,还是以某种方式影响了性能?

【问题讨论】:

    标签: android performance listview


    【解决方案1】:

    我使用的方法与您的方法非常相似,但我更进一步,因为ViewHolder 是适配器类的私有,我通过将视图传递给它的构造函数并在那里设置值来将它与类紧密耦合例如

        private class ViewHolder
        {
          protected final ImageView image;
          protected final TextView  title;
          protected final TextView  status;
    
          public ViewHolder( final View root )
          {
             image = (ImageView) root.findViewById( R.id.artist_image );
             title = (TextView) root.findViewById( R.id.artist_title );
             status = (TextView) root.findViewById( R.id.artist_status );
          }
       }
    

    getView(...)

     View row = convertView;
    
      if ( null == row || null == row.getTag() )
      {
         row = inflater.inflate( R.layout.adapter_artists, null );
         holder = new ViewHolder( row );
         row.setTag( holder );
      }
      else
      {
         holder = (ViewHolder) row.getTag();
      }
    

    我喜欢这样做,因为它使getView(...) 中的适配器代码更简单,并且具有最终变量的好处。我可能会获得轻微的速度提升,使其受到保护,但我发现即使列表很大,性能也足够了。

    【讨论】:

    • 但是他们(像 Romain Guy 这样的 android.developer 家伙)说 ViewHolder 必须是静态类(性能问题),如果它的外部类用于适配器(并且由于它有 2b 静态它不能是内部适配器)它必须比私有更宽(它们使用我上面提到的默认访问类型)。
    • 是的,我记得读过一些关于它的东西,我还没有对性能进行过很好的实验。这是我想展示的构造函数模式,然后你会遇到确保一旦它是静态的就没有其他类可以访问它的问题。受静电保护的限制是否足以胜任这项工作?
    • 最接近私有的访问类型受到保护,所以是的。我喜欢您的方法,现在更改我的代码以使用它。只是代码的外观没有真正的区别。因为它只是发送 1 个参数而不是一堆参数。然而,它违反了耦合和内聚,但 ViewHolder 就像适配器的卫星级,所以它没什么大不了的。
    • 是的,我知道紧密耦合很糟糕,但我通过说不允许使用该视图类来解释它,但它使代码看起来更好!
    【解决方案2】:

    我认为这只是品味问题。至于我,它甚至看起来比标准的更好。此外,由于使用了最终变量,您的版本可能会更快。

    【讨论】:

    • 谢谢,我就是这么想的。
    【解决方案3】:

    在我看来,这是最好的方法,但是我会在您的代码中更改一些内容。您在 ViewHolder 中有一个构造函数,您可以在其中设置视图,但我可以看到您没有在代码中使用它。我会使用它或只是删除它。还有一件事,实际上有更好的方法来获得相同的效果,但它只适用于 Android 4+:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    
        ImageView mIcon;
        TextView mName;
        if (convertView == null) {
            convertView = LayoutInflater.from(context)
              .inflate(R.layout.my_contact_listing, parent, false);
            mIcon = (ImageView) convertView.findViewById(R.id.contact_icon);
            mName = (TextView) convertView.findViewById(R.id.contact_name);
            convertView.setTag(R.id.contact_icon, mIcon);
            convertView.setTag(R.id.contact_name, mName);
        } else {
            mIcon = (ImageView) convertView.getTag(R.id.contact_icon);
            mName = (TextView) convertView.getTag(R.id.contact_name);
        }
    
        Contact mContact = getItem(position);
        mName.setText(mContact.getName());
        mIcon.setImageResource(mContact.getIcon());
    
        return convertView;
    }
    

    【讨论】:

    • 谢谢。但它不使用 ViewHolder 视图吗? (“但我可以看到你没有在你的代码中使用它”):viewHolder.txtTitle.setText(album.getTitle(locale)); viewHolder.txtDescription.setText(album.getCopyrightInfo(locale));
    • 没错,你可以使用视图的 setTag 方法来实现:developer.android.com/reference/android/view/…, java.lang.Object) ,供参考。
    • 你的意思是代码行:convertView.setTag(viewHolder);?我没有显示这条线,因为与原始相比没有变化。 ListAdapter 的唯一变化是 ViewHolder 创建和它的字段初始化。
    • 我在谈论convertView.setTag(R.id.contact_name, mIcon);,在这里您可以阅读更多关于适配器的良好实践:piwai.info/android-adapter-good-practices
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-05
    • 2011-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-04
    相关资源
    最近更新 更多