在自定义适配器(android)中使用回收视图时,图像不断重复更改

在自定义适配器(android)中使用回收视图时,图像不断重复更改,android,firebase,firebase-storage,custom-adapter,Android,Firebase,Firebase Storage,Custom Adapter,我的自定义适配器有以下代码,其中每一行由一个名称和一张从firebase存储中获取的图片组成: import android.content.Context; import android.net.Uri; import android.support.annotation.NonNull; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import and

我的自定义适配器有以下代码,其中每一行由一个名称和一张从firebase存储中获取的图片组成:

import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageException;
import com.google.firebase.storage.StorageReference;
import com.squareup.picasso.Picasso;

import java.util.List;

public class CustomAdapter extends BaseAdapter {

    Context context;
    List<RowItem> rowItems;


    CustomAdapter(Context context, List<RowItem> rowItems) {
        this.context = context;
        this.rowItems = rowItems;
    }

    @Override
    public int getCount() {
        return rowItems.size();
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    /* private view holder class */
    private class ViewHolder {
        ImageView profile_pic;
        TextView member_name;

    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder holder;
        final RowItem row_pos = rowItems.get(position);

        final StorageReference storageReference = FirebaseStorage.getInstance().getReference().child(row_pos.getFirebaseUserUid()+".jpg");




        //if (convertView == null) {
        LayoutInflater mInflater = LayoutInflater.from(context);
        convertView = mInflater.inflate(R.layout.list_item, parent, false);


        holder = new ViewHolder();

        holder.member_name = convertView
                .findViewById(R.id.member_name);
        holder.profile_pic = convertView
                .findViewById(R.id.profile_pic);





        //convertView.setTag(holder);


        /*} else {
        holder = (ViewHolder) convertView.getTag();

        }*/

        holder.member_name.setText(row_pos.getName());



        storageReference.getDownloadUrl().addOnSuccessListener(new 
OnSuccessListener<Uri>() {
            @Override
            public void onSuccess(Uri uri) {





 Glide.with(context).load(uri).apply(RequestOptions.circleCropTransform()).into(holder.profile_pic);
        }
    }).addOnFailureListener(new OnFailureListener() {
        @Override
        public void onFailure(@NonNull Exception exception) {
            int errorCode = ((StorageException) exception).getErrorCode();
            if (errorCode == StorageException.ERROR_OBJECT_NOT_FOUND) {
                Picasso.get()
                        .load(R.drawable.user)
                        .resize(70,70)
                        .into(holder.profile_pic);
            }
        }
    });


    return convertView;
    }

}
但当我这样做时,在列表项的profilePic视图中,它会一个接一个地快速显示许多图像

有人能帮忙发现问题并提出解决方案吗


下面是一个bug的视频:

我认为问题在于Eselfar指示的bug。 当您向firebase请求uri时,它应该在异步线程上完成。这意味着在检索迭代n的url的同时,适配器将继续在列表上迭代

这两个流是同时执行的,所以holder变量不断地被重新分配,可能直到迭代到达最后一个可见项(视图)

这导致了这种情况:

for each view {
   request image url       (for example 2 seconds)
   assign holder variable  (for example 0.0002 seconds)
}
在检索第一个图像url的同时,适配器结束了迭代。 所以holder变量现在具有相同的值,即整个迭代的最后一个值。 对于第一个图像url(对于剩余的图像url也是如此)2秒钟后,它进入
OnSuccessListener.onSuccess
方法,在
holder.profile\u pic
变量上设置图像滑动。但是,正如前面所说,这个变量现在属于最后一次迭代

所以,您的问题是您混合了同步和异步方法

一个可行的解决方案是首先检索所有图像URL,然后删除该异步任务(
storageReference.getDownloadUrl()
),并在同一个UIThread中执行所有操作

我认为最简单的解决方案(但可能不是最好的)是创建自定义OnSuccessListener和自定义OnFailureListener,如下所示:

public class MySuccessListener extends OnSuccessListener {


          Holder holder;

            public MySuccessListener(Holder holder) {
            this.holder = holder;
            super();
          }

            @Override
            public void onSuccess(Uri uri) {
                Glide.with(context).load(uri).apply(RequestOptions.circleCropTransform()).into(holder.profile_pic);
          }
}

public class MyFailureListener extends OnFailureListener {


          Holder holder;

            public MyFailureListener(Holder holder) {
            this.holder = holder;
            super();
          }

            @Override
            public void onFailure(@NonNull Exception exception)
                int errorCode = ((StorageException) exception).getErrorCode();
                 if (errorCode == StorageException.ERROR_OBJECT_NOT_FOUND) {
                    Picasso.get()
                        .load(R.drawable.user)
                        .resize(70,70)
                        .into(holder.profile_pic);
                 }
          }
}
请注意,您将当前的holder传递给侦听器构造函数,所以当异步任务结束时,您可以将正确的holder作为目标。 然后您可以像这样更新代码:

storageReference.getDownloadUrl().addOnSuccessListener(new MyFailureListener<Uri>(holder))
                            .addOnFailureListener(new MyFailureListener(holder));
storageReference.getDownloadUrl().addOnSuccessListener(新的MyFailureListener(持有者))
.addOnFailureListener(新的MyFailureListener(持有者));
现在我不能在IDE中测试它,我的代码可能不精确,但基本上你们应该理解建议的方法


请注意,您使用的是毕加索和格莱德。这两个lib的功能相同,我建议您使用其中一个并删除不必要的依赖项。

使用RecyclerView而不是ListView是否还有其他选项?我不想重写很多代码在适配器中请求URL的问题是,当视图(行)被重用时,您需要手动停止对Firebase的请求。这取决于您有多少项以及检索URL所需的时间,但您可以在检索URL时使用progressbar显示一个空列表,然后在拥有所有元素时显示该列表。在实现任何操作之前,您需要了解ListView的工作原理(尤其是它的适配器)。适配器具有一定数量的视图,并在滚动时重用这些视图以显示数据。例如,如果列表中有100个项目,但同时只能看到其中的10个,则listview将尝试仅管理10个视图。如果您没有正确编写适配器,并且在每次调用
getView
时都创建一个新视图,对未使用的视图执行操作,或者当内容发生更改时,您将以泄漏和缓慢的UI结束。这就是@Fire手套在谈到“回收过程”时的意思。因此,我建议您阅读一些关于实现ListView的最佳实践的文档。如前所述,无论如何最好使用回收视图谢谢您的回复。您的第一个解决方案-我没有找到任何方法来停止
storageReference.getDownloadUrl()
async任务,我尝试的任何方法似乎都不起作用。。。至于另一个解决方案,我真的看不出有什么办法。。。也许你有什么想法?提前感谢storageReference.getDownloadUrl()包含哪些内容?像这样的图片链接?是的,正是你说的
storageReference.getDownloadUrl().addOnSuccessListener(new MyFailureListener<Uri>(holder))
                            .addOnFailureListener(new MyFailureListener(holder));