异步任务加载图片RecyclerView

4
我正在尝试创建一个类似的应用程序: 在RecyclerView中带有一些图像和描述(卡片视图)的应用程序 首先,我从数据库中加载所有CardView的信息:图像的标题、描述和URL。当我将所有这些信息放入RecyclerView中时,(标题和描述)显示正确,但对于图像,我创建了一个AsyncTask类来从URL加载图像,让用户不必等待太长时间以获得响应。
如果用户缓慢滚动,则图像会正确加载,一切正常,但是如果我快速滚动,则会遇到一些问题,例如在第3个项目中显示的图像也会在最后一个项目中显示,直到加载并刷新了最后一个项目的图像为止。
以下是我适配器加载图像的一些代码:
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    if (holder instanceof EventosViewHolder) {
        ......
        //Load the image (getFoto() get drawable)
        if (eventoInfoAux.getFoto()==null){
            CargarImagen cargarImagen = new CargarImagen(((EventosViewHolder) holder).vRelativeLayout,eventoInfo,position);
            cargarImagen.execute();
        }else{
            ((EventosViewHolder) holder).vRelativeLayout.setBackground(eventoInfoAux.getFoto());
        }   
    }
}

以下是CargarImagen类的代码:

//Clase para cargar imagenes con una tarea asíncrona desde un url de la imagen
public class CargarImagen extends AsyncTask<String, String, Boolean>{

//RelativeLayout donde se introduce la imagen de fondo
RelativeLayout relativeLayout=null;
//EventoInfo para obtener la url de la imagen
List<EventoInfo> eventoInfo=null;
//Posición de la imagen clicada
int i;

//Se cargan todos los valores de las variables necesarias desde los datos introducidos
public CargarImagen(RelativeLayout relativeLayout,List<EventoInfo> eventoInfo,int i) {
    this.relativeLayout = relativeLayout;
    this.eventoInfo = eventoInfo;
    this.i = i;
}

//Pintamos el fondo de gris mientras se está cargando la imagen
@Override 
protected void onPreExecute() { 
    super.onPreExecute(); 
    relativeLayout.setBackgroundResource(R.color.fondo_card_view);
}

//Se realiza la carga de la imagen en segundo plano
@SuppressWarnings("deprecation")
@Override
protected Boolean doInBackground(String... params) {
    //Necesario para hacer la llamada a la red
    StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();      
    StrictMode.setThreadPolicy(policy);

    //obtenemos la imagen con el metodo getBitmapFromURL
    Bitmap myImage = getBitmapFromURL(eventoInfo.get(i).url);

    //Si se tiene imagen se pinta, si no no se hace nada
    if (myImage !=null){
        Drawable dr = new BitmapDrawable(myImage);
        eventoInfo.get(i).setFoto(dr);
        return true;
    }
    return false;
}

//Al finalizar la carga de la imagen se pinta el fondo del relative layout
protected void onPostExecute(Boolean result) {
    if(result){
        relativeLayout.setBackground(eventoInfo.get(i).foto);
    }   
}

//Metodo para obtener un bitmap desde una url
public Bitmap getBitmapFromURL(String imageUrl) {
    try {

        URL url = new URL(imageUrl);
        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
        connection.setDoInput(true);
        connection.connect();
        InputStream input = connection.getInputStream();
        Bitmap myBitmap = BitmapFactory.decodeStream(input);
        return myBitmap;

    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

}

我可以给你提供.apk文件,你可以查看问题所在。

提前感谢。 :)

这是我的适配器的完整代码。

 import java.util.List;

import com.abdevelopers.navarraongoing.R;
import com.abdevelopers.navarraongoing.detalle.DetalleActivity;

import android.content.Intent;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class EventosAdapter extends RecyclerView.Adapter<ViewHolder>{

    private static final int TYPE_EVENTO = 0;
    private static final int TYPE_FOOTER = 1;

//lista de eventos
private List<EventoInfo> eventoInfo;


private String usuario;
private EventoInfo  eventoInfoAux;
private PaginaInicioActivity paginaInicio;

//Se pasan los valores necesarios para obtener información de los eventos
public EventosAdapter(List<EventoInfo> eventoInfo, String usuario, PaginaInicioActivity paginaInicio ) {
    this.eventoInfo = eventoInfo;
    this.usuario = usuario;
    this.paginaInicio = paginaInicio;
}


//Metodo para obtener el numero de items en la lista que introducimos
@Override
public int getItemCount() {
    return eventoInfo.size();
}

@Override
public int getItemViewType(int position) {
    if (position + 1  == getItemCount()) {
        return TYPE_FOOTER;
    } else {
        return TYPE_EVENTO;
    }
}

//Se asignan los datos a cada uno de los elementos de la cardview
@Override
public void onBindViewHolder(ViewHolder holder, final int position) {
    if (holder instanceof EventosViewHolder) {
        //Obtenemos cada uno de los eventos
        eventoInfoAux = eventoInfo.get(position);
        //eventosViewHolder = holder;

        //Introducimos el título del evento
        ((EventosViewHolder) holder).vTitle.setText(eventoInfoAux.titulo);
        //Introdicumos la fecha y el lugar del evento
        ((EventosViewHolder) holder).vFechaLugar.setText(eventoInfoAux.fecha+", "+eventoInfoAux.lugar);
        //obtenemos el numero de asistentes al evento
        String asistentes = Integer.toString(eventoInfoAux.asistentes);
        ((EventosViewHolder) holder).vLikeButton.setText(asistentes);

        //Se pinta el boton de like dependiendo de si está o no like 
        if(eventoInfoAux.like){
            ((EventosViewHolder) holder).vLikeButton.setBackgroundResource(R.drawable.button_like_liked);
        }else{
            ((EventosViewHolder) holder).vLikeButton.setBackgroundResource(R.drawable.button_like);
        }

        //Se pinta la imagen del evento dependiendo de si está en la base de datos o no
        if (eventoInfoAux.foto==null){
            CargarImagen cargarImagen = new CargarImagen(((EventosViewHolder) holder).vRelativeLayout,eventoInfo,position);
            cargarImagen.execute();
        }else{
            ((EventosViewHolder) holder).vRelativeLayout.setBackground(eventoInfoAux.foto);
        }   
    }
}



@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == TYPE_EVENTO) {
        View view = LayoutInflater.
                from(parent.getContext()).
                inflate(R.layout.evento_card_view, parent, false);
        return new EventosViewHolder(view,eventoInfo,usuario,paginaInicio);
    } else if (viewType == TYPE_FOOTER) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.pie_carga_pagina_inicio, parent, false);
        view.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        return new FooterViewHolder(view);
    }
    return null;
}

//Clase para crear el footerview donde se cargan más eventos
class FooterViewHolder extends ViewHolder {

    public FooterViewHolder(View view) {
        super(view);
    }

}

//Clase para crear los eventos con su cardview
class EventosViewHolder extends ViewHolder implements View.OnClickListener{

    private TextView vTitle;
    private TextView vFechaLugar;
    private Button vLikeButton;
    private RelativeLayout vRelativeLayout;
    private List<EventoInfo> eventoInfo;
    private String usuario;
    private LikesDDBB likesManager;
    private PaginaInicioActivity paginaInicio;
    private CardView vCardView;

    public EventosViewHolder(View itemView,List<EventoInfo> eventoInfo,String usuario, 
            PaginaInicioActivity paginaInicio) {
        super(itemView);

        this.paginaInicio = paginaInicio;
        this.usuario = usuario;
        vTitle = (TextView)itemView.findViewById(R.id.titulo);
        vFechaLugar =  (TextView) itemView.findViewById(R.id.fecha_lugar);
        vLikeButton = (Button)  itemView.findViewById(R.id.like_button);
        vRelativeLayout = (RelativeLayout) itemView.findViewById(R.id.layout_cardview);
        vCardView = (CardView)itemView.findViewById(R.id.card_view);

        //Valores por defecto de los botones y del fondo en caso de no haber like ni foto
        vLikeButton.setBackgroundResource(R.drawable.button_like);
        vRelativeLayout.setBackgroundResource(R.color.fondo_card_view);
        vLikeButton.setOnClickListener(this);
        vCardView.setOnClickListener(this);
        vLikeButton.setTag("boton");
        vCardView.setTag("evento");

        this.eventoInfo = eventoInfo;
    }


    @SuppressWarnings("deprecation")
    @Override
    public void onClick(View v) {
        if(v.getTag().equals("boton")){
            likesManager = new LikesDDBB(this.eventoInfo.get(getPosition()).like, usuario,
                    vLikeButton, getPosition(), eventoInfo, paginaInicio);
            likesManager.execute();
        }else if(v.getTag().equals("evento")){
            Intent inicion = new Intent(paginaInicio,DetalleActivity.class);

            paginaInicio.startActivity(inicion);
        }

    }

}

//Para obtener la lista de eventos desde la clase PaginaInicioActivity
public List<EventoInfo> getEventoInfo() {
    return eventoInfo;
}

public void setEventoInfo(List<EventoInfo> eventoInfo) {
    this.eventoInfo = eventoInfo;
}

}

我在这里给你.apk文件 https://mega.co.nz/#!Wc1hlagS!XMOMauYpbdk_UZBIswBgcltbRpwAc7VqaJmlS4JZ9FE 在应用程序中,用户名是bruno,密码是1234。 - Bruno
您必须在适当的位置取消AsyncTask(每当不再需要结果时)。例如,如果您有自己的自定义视图,请在onDetachedFromWindow()方法中使用AsyncTask的cancel方法来取消它。 - S.M.Mousavi
2个回答

5
作为“RecyclerView”的名称所示,它会循环利用已创建的视图来显示您的项目/卡片。
因此,假设您的RecyclerView有3个CardViews,当您滚动时,它会将其回收并重复使用,而我们有4个要显示内容的项目。
最初,项目1的内容显示在CardView 1中,项目2的内容显示在CardView 2中,项目3的内容显示在CardView 3中。
现在,当您滚动时,CardView 1消失,被回收并显示项目4的内容在CardView 1中。
只要您不重置之前插入的内容,CardView 1将显示它们 - 在您的情况下,只要异步任务需要完成之前设置的项目内容,就会显示之前设置的项目内容。
要解决您的问题,您可能需要使用一个图像加载(和缓存)库,例如:
- picasso - Android-Universal-Image-Loader 您的解决方案也容易出现竞争条件(后续任务在先前任务之前完成)。

你好,感谢你的回答。在寻找解决方案后,我下载了Picasso库,并使用它代替AsicTask,但问题仍然存在。 - Bruno
嗨,看了你的评论,我认为问题应该是这样的: http://stackoverflow.com/questions/26591390/recycler-view-with-volley-image-request-cancel-request - Bruno
是的,相似的问题 - Picasso 和 UIL 会为您处理这个问题,如果您想自己解决它,您必须跟踪针对相同(已回收的)视图的图像下载。 - Thomas S.E.

0

我认为你应该使用第三方库,比如Picasso,从url异步下载图片。该库提供的远不止这些功能,你可以更专注于你的应用程序的功能而不是纠结于设置自己的AsynkTask。


谢谢你的回答,但我尝试了使用异步任务来解决问题,但问题仍然存在。 - Bruno
也许你应该发布一些代码来查看问题。 - SebastianGreen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接