为什么偶尔会在ListView中加载错误的图像?

时间:2015-09-02 14:33:04

标签: android android-listview android-asynctask android-download-manager getview

我有一个listview,可以在异步中的每个单元格中加载图像。当我尝试慢慢向下滚动时(在加载当前视图中的所有图像之后),它可以完美地工作。 但是当我尝试在它们被加载并向上滚动之前向下滚动时,我面临这个问题。单元格开始显示与它们不对应的图像。

我的getView方法如下所示:

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

    // TODO Auto-generated method stub

    View rowView = null;

    if(convertView == null) {
        rowView = inflater.inflate(R.layout.list_posts_item, null);
        final Holder holder=new Holder();
        holder.tvTitle=(TextView) rowView.findViewById(R.id.tvTitleNamePost);
        holder.ivPrimaryImage=(ImageView) rowView.findViewById(R.id.ivPrimaryImage);
        holder.tvLocality=(TextView) rowView.findViewById(R.id.tvLocalityPosts);
        holder.tvDateCreated=(TextView) rowView.findViewById(R.id.tvDateCreated);
        rowView.setTag(holder);
    }else {
        rowView=convertView;
    }

    Holder holder = (Holder)rowView.getTag();
    holder.ivPrimaryImage.setId(position);
    holder.ivPrimaryImage.setTag(listOfPosts.get(position).getPostId());
    holder.ivPrimaryImage.setImageBitmap(null); // Added for flickering issue
    holder.tvTitle.setText(listOfPosts.get(position).getTitle());
    holder.tvLocality.setText(listOfPosts.get(position).getLocality());
    holder.tvDateCreated.setText(listOfPosts.get(position).getCreatedDate());
    postId = listOfPosts.get(position).getPostId();
    Image image = new Image();
    image.setImg(holder.ivPrimaryImage);

    if (!"N".equalsIgnoreCase(listOfPosts.get(position).getHasImage()) ) {
        if(!tagsCaching.containsKey(postId))
            new GetPrimaryImages().execute(image);
        else
             holder.ivPrimaryImage.setImageBitmap(tagsCaching.get(postId));
    }

    return rowView;
}

我的异步调用类看起来像这样:

public class GetPrimaryImages extends AsyncTask<Image, Void, Bitmap> {

ImageView imageView = null;
    protected Bitmap doInBackground(Image... images) {
    this.imageView=images[0].getImg();



        // Building Parameters
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("postid",(String)(this.imageView.getTag()) ));


             json = jsonParser.makeHttpRequest(CommonResources.getURL("get_primary_image"),
                    "POST", params);


        if(json == null){
            return null;
        }
        Log.d("Fetching Image",imageView.getTag()+ json.toString());
        tagsDownloaded.add((String)imageView.getTag());
        // check for success tag
        String TAG_SUCCESS = "success";
        try {
            int success = json.getInt(TAG_SUCCESS);

            if (success == 0) {
               image =  json.getString("primaryimage");

            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return getImage(image);

    }

    /**
     * After completing background task Dismiss the progress dialog
     * **/
    protected void onPostExecute(Bitmap result) {
        tagsCaching.put((String)imageView.getTag(), result);
        imageView.setImageBitmap(result);

    }

    public Bitmap getImage(String imageString) {
        if("null".equalsIgnoreCase(imageString)){
            return null;
        }else{
            byte[] decodedString = Base64.decode(imageString, Base64.DEFAULT);
            Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
            //image.setImageBitmap(decodedByte);
            return decodedByte;
        }

    }


}

编辑:

我向Holder添加了一个新的实例变量:

public class Holder
{
    TextView tvTitle;
    ImageView ivPrimaryImage;
    TextView tvLocality;
    TextView tvDateCreated;
    int position;
}

在getView中设置相同:     holder.position = position; 并将holder对象传递给Async任务:     新的GetPrimaryImages(职位,持有人).execute(image);

并修改了Async调用类,如下所示: 1.添加取消http呼叫 2.更改了onPostExecute方法

  public class GetPrimaryImages extends AsyncTask<Image, Void, Bitmap> {

    int mPosition;
    Holder mHolder;
    public GetPrimaryImages(int position, Holder holder){
        mPosition = position;
        mHolder = holder;
    }

    ImageView imageView = null;
    protected Bitmap doInBackground(Image... images) {
    this.imageView=images[0].getImg();



        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("postid",(String)(this.imageView.getTag()) ));


        JSONObject json;
        if(mHolder.position == mPosition)
             json = jsonParser.makeHttpRequest(CommonResources.getURL("get_primary_image"),
                    "POST", params);

        else {
            json = null;
            cancel(true);
        }

        // check log cat fro response
        if(json == null){
            return null;
        }
        Log.d("Fetching Image",imageView.getTag()+ json.toString());
        tagsDownloaded.add((String)imageView.getTag());
        // check for success tag
        String TAG_SUCCESS = "success";
        try {
            int success = json.getInt(TAG_SUCCESS);

            if (success == 0) {
               image =  json.getString("primaryimage");

            }
        } catch (JSONException e) {
            e.printStackTrace();
        }

        return getImage(image);

    }


    protected void onPostExecute(Bitmap result) {
        if (mHolder.position == mPosition) {
            tagsCaching.put((String) imageView.getTag(), result);
            imageView.setImageBitmap(result);
        }

    }

    public Bitmap getImage(String imageString) {


        //needs to wait
        if("null".equalsIgnoreCase(imageString)){
            return null;
        }else{
            byte[] decodedString = Base64.decode(imageString, Base64.DEFAULT);
            Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
            //image.setImageBitmap(decodedByte);
            return decodedByte;
        }

    }


}

似乎工作正常。 :)

现在我怀疑是什么是缓存图像的最佳方法?应该将其写入文件?每次向上滚动时都要从中读取它?

3 个答案:

答案 0 :(得分:3)

问题是,当您的异步任务结束其后台操作时,它链接到的元素已被回收以容纳您的集合中的另一个元素。

让我们关注元素位置,让我们说你的列表视图最多可以显示4个元素。

listview第一次调用前4个元素的getview,并创建并运行4个asynctasks。 然后滚动到11-15位置,第一个元素(与位置1相关的元素)在异步任务结束之前被回收到位置11。

然后asynctask结束,你看到的是与post 11相关的图像,其中位图与post 1相关。

一种避免这种情况的方法是在asynctask中了解视图被回收,正如Lucas Rocha的这篇旧帖中所建议的那样。

Performance tips with listview

查看帖子以获取有关listview如何工作的见解:

enter image description here

答案 1 :(得分:0)

主要&#34;问题&#34;是ListViews实现重用视图和串行提供AsyncTasks。

1)在ListView的适配器中,您可以正确地重用项目。在ListView中,只渲染了几个项目(屏幕上可见的项目+上下几个)。如果您开始滚动屏幕上的项目将被销毁,并且他们的视图将asi参数作为convertView传递给public View getView(final int position, View convertView, ViewGroup parent)

这是第一个问题。列表是重复使用项目。

2)第二件事是AsyncTask是在另一个线程上执行的,但是在同一个线程上执行了更多的 asyncTask实例。这意味着它们是连续执行的。如果您足够快地滚动,那么您可以使用AsyncTask在加载图像时提出更多请求。有一次,只有少数(我认为5)AsyncTask的工作在队列中等待。另一个成功被忽略了。因此,如果您快速滑动,则会获得5个已满的队列,而另一个图像请求将被忽略

就是这样。滚动,开始加载少量图像,忽略新显示的图像。当你在所有AsyncTasks结束并且你得到一些&#34;随机&#34;之后停下来(优先显示)列表项中加载的图像。

<强> Sollution

这件事被讨论了曼尼时代并得到了解决。只需使用Picaso库就足够了:

https://futurestud.io/blog/picasso-adapter-use-for-listview-gridview-etc/

答案 2 :(得分:0)

要添加答案,我将实现图像缓存(例如,作为URL到WeakReference到图像的hashmap)。 getView()将访问该缓存,如果图像不存在,则保留请求。加载图像时,缓存将检查请求列表并通知发布请求的视图(将URL和图像都传递给它们)。视图会将通知中传递的URL与其当前URL进行比较,并使用该图像或忽略它(如果视图超出屏幕或被重用,则必须忽略该图像。)

为什么要求列表。有可能多个视图设法请求一些图像并在加载图像之前重新使用(特别是如果您多次向上和向下滚动列表)。