异步图像加载,检查图像是否被回收

时间:2013-08-08 20:25:20

标签: android android-listview android-asynctask

这个问题在我阅读之后找到了我: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。我在想正确的方向吗?

4 个答案:

答案 0 :(得分:8)

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

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

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

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

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

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

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

这是否更清楚?

答案 1 :(得分:3)

当使用ViewHolderposition传递AsyncTask时,它的值为position(比如说​​5),值引用(不是一个副本)到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范围内,并且行的可见性无关紧要。

答案 2 :(得分:0)

据我所知,当视图被回收时,你正试图取消图像的异步加载任务,而不再在屏幕上显示。

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

在此侦听器中,您可以取消下载任务:

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

答案 3 :(得分:0)

接受的答案和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;
}