【问题标题】:Async image loading, check if an image is recycled异步图片加载,检查图片是否被回收
【发布时间】:2013-08-08 20:25:20
【问题描述】:

读完这篇文章后,我想到了这个问题:Performance tips(特别是名为“异步加载”的部分)。基本上,他正在尝试保存有关一行的信息以查看它是否已被回收,并且仅在该行仍然可见时才设置下载的图像。他就是这样保住位置的:

holder.position = position;

new ThumbnailTask(position, holder)
        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);

ThumbnailTask​​ 构造函数在哪里:

public ThumbnailTask(int position, ViewHolder holder) {
    mPosition = position;
    mHolder = holder;
}

在 onPostExecute() 中,他然后进行前面提到的检查:

    if (mHolder.position == mPosition) {
        mHolder.thumbnail.setImageBitmap(bitmap);
    }

我只是不明白这是如何产生任何结果的。 Holder 和 position 在构造函数中同时设置,为相同的值(holder 中的位置与 mPosition 中的位置相同)。它们在 AsyncTask 期间不会发生变化(确实,getView() 中的位置可能会发生变化,但作为私有成员存储在 AsyncTask 中的位置永远不会被操纵)。我在这里错过了什么?

首先,保存位置似乎不是一个好的选择:我相信它不能保证是唯一的,如果我没记错的话,它会在滚动后将自身重置为 0。我的想法是否正确?

【问题讨论】:

    标签: android android-listview android-asynctask


    【解决方案1】:

    背景(您可能知道这一点,但以防万一):适配器包含对象集合并使用来自这些对象的信息来填充视图(每个视图都是列表中的一个项目)。列表视图负责显示这些视图。出于性能原因,ListView 将回收不再可见的视图,因为它们从列表的顶部或底部滚动。它是这样做的:

    当 ListView 需要一个新视图来显示时,它会调用 Adapter 的 getView 并使用整数参数“position”来指示它想要查看 Adapter 集合中的哪个对象(位置只是一个从 1 到 N -1 的数字)在哪里N 是适配器中的对象数。

    如果它有任何不再可见的视图,它也会将其中一个传递给适配器,因为“convertView”这表示“重用这个旧视图而不是创建一个新视图”。性能大获全胜。

    文章中的代码将一个 ViewHolder 对象附加到它创建的每个视图中,其中包含 ListView 请求的对象的位置。在文章的代码中,这个位置与指向视图中将包含图像的字段的指针一起隐藏在 ViewHolder 中。 ViewHolder 作为标签(单独的主题)附加到视图。

    如果视图被回收以保存不同的对象(在不同的位置),那么 ListView 将调用 Adapter.getView(newPosition, oldView...) 文章中的代码会将新位置存储到附加到 oldView 的 ViewHolder 中. {到目前为止有意义吗?)并开始加载这个新图像以放入视图中。

    现在在文章中,它正在启动一个 AsyncTask 来检索应该进入视图的数据)该任务具有位置(来自 getView 调用)和持有者(来自 oldView)。该位置告诉它请求了哪些数据。持有者告诉它当前应该在此视图中显示哪些数据,以及一旦显示将其放置在何处。

    如果视图在 AsyncTask 仍在运行时再次被回收,则持有者中的位置将被更改,因此这些数字将不匹配,并且 AsyncTask 知道不再需要它的数据。

    这样会更清楚吗?

    【讨论】:

    • 就位置的唯一性而言,如果Adapter中包含的对象集合不变,那么每个位置号将引用Adapter中的特定对象。即使视图滚动关闭,然后向后滚动并且意外地被要求包含相同的位置,它也将是同一个对象,因此 AsyncTask 可以安全地填充视图。另一方面,如果数据收集发生变化,则可能存在需要解决的问题。这也是一个可以解决的问题,但超出了您引用的文章的范围。
    • 我现在明白了。我忘记了 Holder 实际上是在 AsyncTask 之外更改的。位置不会被重置是有道理的。我会接受你的回答,因为你是第一个,尽管 Marcins 的回答更简单。
    • 在我的实现中,我使用了一个 uid,所以@Dale Wilson 的担忧不会影响它。
    【解决方案2】:

    当使用ViewHolderposition 传递AsyncTask 时,它被赋予position 的值(比如5)和reference 的值(不是副本)给ViewHolder 对象。他还将当前位置放在 ViewHolder 中(如5),但这里的整个“技巧”是对于回收的视图,旧的 ViewHolder 对象也被重新使用(在链接文章中):

    } else {
       holder = convertView.getTag();
    }
    

    因此,无论代码引用该特定 ViewHolder 对象,实际上都会在检查时检查其position 成员值,而不是在创建对象时检查。所以onPostExecute 检查是有意义的,因为传递给任务构造函数的position 值保持不变(在我们的例子中它的值是5),因为它是原始的,但是 ViewHolder 对象可以更改其属性,如果视图将被重用在我们到达onPostExecute之前。

    请注意,我们不会在任务的构造函数中复制 ViewHolder 对象,即使它看起来如此。这不是 Java 的工作方式 :) See this article for clarification

    同时保存职位似乎不是一个好的选择:我相信 不能保证它是唯一的,它会将自身重置为零 滚动后。这是真的吗?

    没有。此处的位置是指*源数据集中的索引,在屏幕上不可见。因此,如果您要显示 10 个项目,但您的屏幕当时只能显示 3 个,那么您的 position 将在 0-9 范围内,并且行的可见性无关紧要。

    【讨论】:

      【解决方案3】:

      据我了解,您正在尝试在视图回收时取消图像的异步加载任务,并且不再显示在屏幕上。

      要实现这一点,您可以为列表视图设置RecyclerListener。当列表视图不需要此视图时(不在屏幕上时),它将在将其作为回收视图传递给适配器之前调用。

      在这个监听器中你可以取消你的下载任务:

      theListView.setRecyclerListener(new RecyclerListener() {
          @Override
          public void onMovedToScrapHeap(View view) {
              for( ThumbnailTask task : listOfAllTasks )
                 task.viewRecycled(task);
          }
      });
      

      ThumbnailTask 内:

      public void viewRecycled(View v){
         if(mHolder.theWholeView == v)
            v.cancel();
      }
      

      不要为implement取消。


      请注意,这不是最好的方法,因为您应该跟踪所有 asynctask 任务。请注意,您还可以在适配器中取消任务,您还可以获得

      public View getDropDownView (int position, View recycledView, ViewGroup parent){
        //.. your logic
      }
      

      但请注意,这可能需要您在适配器中分配 ThumbnailTask,这不是一个好的做法。


      请注意,您还可以使用图像加载库来为您做任何事情,从异步下载到 chaching。例如:https://github.com/nostra13/Android-Universal-Image-Loader

      【讨论】:

      • 感谢您的回答。这绝对是解决问题的一种方法,我会研究它。我也赞成您冗长的答案,但这并不是我要问的。我从来没有说过我想解决这个问题。我只是想知道该文本的作者这样做的方式是否有效。
      【解决方案4】:

      接受的答案和 Marcin 的帖子已经完美地描述了应该发生的事情。但是,链接的网页没有,google site on this topic也很模糊,仅供已经知道“技巧”的人参考。所以这里是缺少的部分,以供将来参考,它显示了对 getView() 的必要补充。

      // The adapter's getView method
      public View getView(int position, View convertView, ViewGroup parent) {
          // Define View that is going to be returned by Adapter
          View newViewItem;
          ViewHolder holder;
      
          // Recycle View if possible
          if (convertView == null) {
              // No view recycled, create a new one
              LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              newViewItem = inflater.inflate(R.layout.image_grid_view_item, null);
      
              // Attach a new viewholder
              holder = new ViewHolder();
              holder.thumbnail = (ImageView) newViewItem.findViewById(R.id.imageGridViewItemThumbnail);
              holder.position = position;
              newViewItem.setTag(holder);
          } else {
              // Modify "recycled" viewHolder
              holder = (ViewHolder) convertView.getTag();
              holder.thumbnail = (ImageView) convertView.findViewById(R.id.imageGridViewItemThumbnail);
              holder.position = position;
      
              // Re-use convertView
              newViewItem = convertView;
          }
          // Execute AsyncTask for image operation (load, decode, whatever)
          new LoadThumbnailTask(position, holder).execute();
      
          // Return the ImageView
          return newViewItem;
      }
      
      // ViewHolder class, can be implemented inside adapter class
      static class ViewHolder {
          ImageView thumbnail;
          int position;
      }
      

      【讨论】:

        猜你喜欢
        • 2013-04-06
        • 1970-01-01
        • 1970-01-01
        • 2012-10-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多