web-dev-qa-db-fra.com

La procédure de recyclage ralentit péniblement le chargement des images en cache de Picasso

J'ai mis en place un RecyclerView contenant principalement des images qui se chargent via Picasso. Mon problème est que dès que je fais défiler la vue vers le bas ou vers le haut, l'image de marque de réservation apparaît pendant env. une seconde avant qu'elle soit remplacée par l'image réelle. Il défile très facilement, mais est pratiquement inutilisable. Cela se produit chaque fois que quelque chose disparaît de l'écran.

Picasso met clairement les images en mémoire cache sur le disque, car elles se chargent plus rapidement que le temps de chargement initial, mais elles rechargent sans aucun doute instantanément à partir du cache. Qu'est-ce que j'ai mal implémenté?

Mon code d'adaptateur:

public class ExploreAdapter extends RecyclerView.Adapter<ExploreAdapter.ExploreViewHolder> {

    private List<ExploreItem> exploreItemList;
    private Context mContext;
    private int deviceWidth = PrefUtils.getDeviceWidth();

    public ExploreAdapter(Context context, List<ExploreItem> exploreItemList){
        this.mContext = context;
        this.exploreItemList = exploreItemList;
    }

    @Override
    public int getItemCount(){
        return exploreItemList.size();
    }

    @Override
    public void onBindViewHolder(ExploreViewHolder exploreViewHolder, int i){
        ExploreItem item = exploreItemList.get(i);
        exploreViewHolder.getvTitle().setText(item.getTitle());
        exploreViewHolder.getvUsername().setText(item.getUsername());
        exploreViewHolder.getvNumLikes().setText(item.getNbOfLikes());

        Picasso.with(mContext).load(item.getMediaLocation().trim())
                .resize(deviceWidth, deviceWidth)
                .centerCrop()
                .placeholder(R.drawable.profile_background)
                .into(exploreViewHolder.getvImage());

        Picasso.with(mContext).load(item.getUserProfilePicUrl().trim())
                .placeholder(R.drawable.profile_picture)
                .into(exploreViewHolder.getvProfilePic());
    }

    @Override
    public ExploreViewHolder onCreateViewHolder(ViewGroup viewGroup, int i){
        View itemView = LayoutInflater.
                from(viewGroup.getContext()).
                inflate(R.layout.explore_item, viewGroup, false);

        return new ExploreViewHolder(itemView);
    }

    public static class ExploreViewHolder extends RecyclerView.ViewHolder{
        private TextView  vTitle,
                          vUsername,
                          vNumLikes;

        private SquareImage vImage;
        private ImageView vProfilePic;

        public ExploreViewHolder(View v){
            super(v);
            this.vTitle = (TextView) v.findViewById(R.id.explore_item_title);
            this.vUsername = (TextView) v.findViewById(R.id.explore_item_username);
            this.vNumLikes = (TextView) v.findViewById(R.id.explore_item_number_likes);
            this.vImage = (SquareImage) v.findViewById(R.id.explore_item_image);
            this.vProfilePic = (ImageView) v.findViewById(R.id.explore_item_profile_picture);
        }

        public TextView getvTitle() {
            return vTitle;
        }

        public TextView getvUsername() {
            return vUsername;
        }

        public ImageView getvProfilePic() {
            return vProfilePic;
        }

        public SquareImage getvImage() {
            return vImage;
        }

        public TextView getvNumLikes() {
            return vNumLikes;
        }

        public void setvImage(SquareImage vImage) {
            this.vImage = vImage;
        }

        public void setvNumLikes(TextView vNumLikes) {
            this.vNumLikes = vNumLikes;
        }

        public void setvProfilePic(ImageView vProfilePic) {
            this.vProfilePic = vProfilePic;
        }

        public void setvTitle(TextView vTitle) {
            this.vTitle = vTitle;
        }

        public void setvUsername(TextView vUsername) {
            this.vUsername = vUsername;
        }
    }
}

Toute aide est appréciée.

50
charrondev

La définition des paramètres recyclerView comme ci-dessous a résolu mon problème de bégaiement:

recyclerView.setHasFixedSize(true);
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);

j'espère que cela aide aussi quelqu'un d'autre.

67
ergunkocak

Utilisez fit().

Picasso.with(context)
        .load(file)
        .fit()
        .centerCrop()
        .into(imageView);

La fit() redimensionne réellement l'image pour l'adapter aux limites de imageView. Cela ne charge pas l'image complète et lisse le défilement.

27
Mangesh

J'ai mélangé la réponse de @ergunkocak et @Mangesh Ghotage, en utilisant cela fonctionne très bien:

recyclerView.setHasFixedSize(true);
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);

Et ceci sur chaque image:

Picasso.with(context)
        .load(file)
        .fit()
        .into(imageView);

de plus, vous pouvez configurer Picasso pour utiliser une seule instance comme celle-ci:

Picasso.setSingletonInstance(picasso);

Et enfin, l'activation des indicateurs de cache vous donnera des indices sur ce qui ne va pas:

Picasso  
    .with(context)
    .setIndicatorsEnabled(true);

Plus sur Picasso

8
lisandro101

J'ai rencontré ce problème récemment, et oui vous avez raison. Picasso est une bonne bibliothèque permettant d'afficher des images à partir d'une carte SD, mais il présente des problèmes de cache si vous récupérez des images en ligne. C'est pourquoi vous voyez une image de lieu réservé pendant le défilement. J'ai essayé différentes choses et le problème n'a pas été résolu.

Il existe une autre bibliothèque, "AQuery", qui est très utile pour récupérer et mettre en cache des images du réseau.

http://code.google.com/p/Android-query/

Les principaux avantages de AQuery/Android Query sont que vous pouvez vider le cache, aucun décalage lors du défilement et tout à fait efficace pour la mise en cache des images et ne pas afficher les images des espaces réservés lors du défilement.

J'ai résolu mon problème en supprimant picaaso et en utilisant Aquery. C'est juste un remplacement d'une ligne et vous avez terminé avec votre problème.

(new AQuery(mContext)).id(exploreViewHolder.getvProfilePic()).image(item.getUserProfilePicUrl().trim(), true, true, device_width, R.drawable.profile_background, aquery.getCachedImage(R.drawable.profile_background),0);

En outre, vous pouvez facilement passer de picasso à AQuery, car il n’existe pas de solution disponible dans picasso mais pas dans AQuery et la syntaxe est presque identique. Je vous recommande donc d’utiliser AQuery, jusqu’à ce que cela soit corrigé par picasso.

3
Faizan Tariq

Je ne peux pas vérifier l'exactitude de cette solution, mais je faisais également face à ce problème et je l'ai abordé en suivant cette idée:

@Override
public void onBindViewHolder(ViewHolder viewHolder, final int i) {

...

    if(mImageView.getDrawable() == null)
         Picasso.with(context)
            .load(path)
            .error(errorResId)
            .tag(tag)
            .into(mImageView);
...  
}

Il est destiné à interdire à Picasso de charger quoi que ce soit, à moins que ImageView n’ait rien à afficher.

Picasso met automatiquement les images en cache à deux niveaux:

  • cache disque (ce n'est pas réellement dans picasso mais dans la couche réseau que picasso utilise)
  • cache mémoire

Ils sont initialisés avec les valeurs par défaut qui suivent la plupart des applications.

Lorsque le cache mémoire devient trop volumineux, le bitmap utilisé le plus ancien est supprimé du cache mémoire (ce qui correspond par défaut à 1/7 de l'espace de mémoire disponible).

Je pense que ce qui se passe ici, c'est que vous êtes proche de la limite de mémoire du cache et que les images sont décodées à nouveau chaque fois que vous en avez besoin.

Voir ici: Android Picasso Configure LruCache Size

Mais je vous conseille de ne pas augmenter la taille de la mémoire cache, sauf si vous redimensionnez réellement les images à la taille réelle que vous utilisez.

Je pense que le problème avec votre code est le suivant:

.resize(deviceWidth, deviceWidth)

êtes-vous sûr d'avoir vraiment besoin d'une image de cette taille? Que stockez-vous dans PrefUtils? est-il mis à jour lorsque le périphérique tourne? (paysage vs portrait)

Si vous avez besoin d’images plus petites, essayez de redimensionner la taille réelle de l’image que vous allez utiliser. Si vous ne le savez pas (calculé au moment de l'exécution avec la présentation), utilisez une approximation ou écrivez votre code pour obtenir la taille réelle (j'écris généralement des classes personnalisées étendant ImageView pour ces éléments).

Si vous avez vraiment besoin d'images de cette taille, augmentez simplement la taille du cache (voir le lien ci-dessus) et vous devriez être prêt à partir.

1
Daniele Segato

La meilleure réponse à cette question est de ne pas utiliser la vue d'image ronde, mais d'utiliser la transformation avec la vue d'image simple, car l'application de vue ronde prend beaucoup de mémoire, utilisez simplement la vue d'image simple et définissez la transformation avec. tilisez cette transformation

0
Rana Asad