8

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

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。我在思考正确的方向吗?

4

4 回答 4

8

背景(您可能知道这一点,但以防万一):适配器包含对象集合并使用来自这些对象的信息来填充视图(每个视图都是列表中的一个行项)。列表视图负责显示这些视图。出于性能原因,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 知道不再需要它的数据。

这是否使它更清楚?

于 2013-08-08T21:20:14.400 回答
4

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

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

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

请注意,我们不会在任务的构造函数中复制 ViewHolder 对象,即使它看起来如此。这不是 Java 的工作方式 :)请参阅这篇文章以获得说明

保存位置似乎也不是一个好选择:我相信它不能保证是唯一的,并且在滚动后它会自行重置为零。这是真的?

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

于 2013-08-08T21:26:43.757 回答
0

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

为此,您可以设置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();
}

不要执行取消。


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

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

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


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

于 2013-08-08T21:09:25.010 回答
0

接受的答案和 Marcin 的帖子已经完美地描述了应该发生的事情。但是,链接的网页没有,并且该主题的google站点也很模糊,仅供已经知道“技巧”的人参考。因此,这是缺少的部分,以供将来参考,它显示了对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;
}
于 2015-03-06T17:42:56.117 回答