web-dev-qa-db-de.com

Recyclerview verlangsamt das Laden zwischengespeicherter Bilder von Picasso schmerzhaft

Ich habe einen RecyclerView implementiert, der hauptsächlich Bilder enthält, die über Picasso geladen werden. Mein Problem ist, dass sobald ich in der Ansicht nach unten oder oben scrolle, das Platzhalterbild für ca. eine Sekunde, bevor es durch das eigentliche Bild ersetzt wird. Es scrollt sehr flüssig, ist aber praktisch unbrauchbar. Dies geschieht jedes Mal, wenn etwas vom Bildschirm rollt.

Picasso zwischenspeichert die Bilder eindeutig auf der Festplatte, da sie schneller als die anfängliche Ladezeit geladen werden, aber sie werden definitiv sofort aus dem Cache neu geladen. Was habe ich falsch implementiert?

Mein Adaptercode:

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;
        }
    }
}

Jede Hilfe wird geschätzt.

50
charrondev

Das Einstellen der recyclerView-Parameter wie folgt löste mein Stotter-Problem:

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

ich hoffe, es hilft auch jemand anderem.

67
ergunkocak

Verwenden Sie fit().

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

Die Funktion fit() passt die Größe des Bildes an die Grenzen von imageView an. Dadurch wird nicht das gesamte Bild geladen und das Scrollen geglättet.

27
Mangesh

Ich habe die Antwort von @ergunkocak und @Mangesh Ghotage gemischt.

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

Und das auf jedem Bild:

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

außerdem könnten Sie Picasso so einstellen, dass es eine einzelne Instanz wie diese verwendet:

Picasso.setSingletonInstance(picasso);

Wenn Sie die Cache-Indikatoren aktivieren, erhalten Sie schließlich Hinweise darauf, was falsch läuft:

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

Mehr über Picasso

8
lisandro101

Ich bin vor kurzem auf dieses Problem gestoßen, und ja, Sie haben Recht. Picasso ist eine gute Bibliothek zum Anzeigen von Bildern von der SD-Karte, hat jedoch einige Probleme mit dem Cache, wenn Sie Bilder online abrufen. Aus diesem Grund wird beim Scrollen ein Platzhalterbild angezeigt. Ich habe verschiedene Dinge ausprobiert und das Problem wurde nicht gelöst.

Es gibt eine alternative Bibliothek, d. H. "AQuery", die sich sehr gut zum Abrufen und Zwischenspeichern von Bildern aus dem Netzwerk eignet.

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

Die Hauptvorteile von AQuery/Android Query sind, dass Sie den Cache leeren können, ohne Verzögerung beim Scrollen und sehr effektiv, um Bilder zwischenzuspeichern und Platzhalterbilder beim Scrollen nicht anzuzeigen.

Ich habe mein Problem behoben, indem ich picaaso entfernt und Aquery verwendet habe. Es ist nur eine Zeile Ersatz und Sie sind mit Ihrem Problem fertig.

(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);

Außerdem können Sie leicht von Picasso zu AQuery wechseln, da es in Picasso keine solche Funktion gibt, aber nicht in AQuery, und die Syntax ist fast gleich. Daher würde ich Ihnen empfehlen, AQuery zu verwenden, bis dies durch Picasso behoben ist.

3
Faizan Tariq

Ich kann die Richtigkeit dieser Lösung nicht überprüfen, aber ich habe mich auch diesem Problem gestellt und bin auf folgende Weise vorgegangen:

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

...

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

Es ist beabsichtigt, Picasso das Laden von Inhalten zu verbieten, es sei denn, ImageView hat nichts anzuzeigen.

Picasso speichert Bilder automatisch in zwei Ebenen:

  • festplatten-Cache (dies ist eigentlich NICHT in Picasso, sondern in der von Picasso verwendeten Netzwerkebene)
  • speicher-Cache

Sie werden mit den Standardeinstellungen initialisiert, die für die meisten Anwendungen geeignet sind.

Wenn der Speichercache zu groß wird, wird die älteste verwendete Bitmap aus dem Speichercache entfernt (standardmäßig 1/7 des verfügbaren Heapspeichers).

Ich denke, was hier passiert, ist, dass Sie nahe an der Speichergrenze des Caches sind und daher Bilder jedes Mal neu dekodiert werden, wenn Sie sie erneut benötigen.

Siehe hier: Android Picasso Configure LruCache Size

Ich rate jedoch, NICHT den Speicher-Cache zu vergrößern, es sei denn, Sie passen die Größe der Bilder wirklich an die tatsächliche Größe an, die Sie verwenden.

Ich denke, das Problem mit Ihrem Code ist das folgende:

.resize(deviceWidth, deviceWidth)

sind Sie sicher, dass Sie wirklich ein Bild dieser Größe benötigen? Was speichern Sie in PrefUtils? wird es aktualisiert, wenn sich das Gerät dreht? (Querformat vs. Hochformat)

Wenn Sie kleinere Bilder benötigen, versuchen Sie, die Größenänderung an die tatsächliche Größe des Bildes anzupassen, das Sie verwenden möchten. Wenn Sie nicht wissen (zur Laufzeit mit dem Layout berechnet), machen Sie eine Annäherung oder schreiben Sie Ihren Code, um die tatsächliche Größe zu erhalten (normalerweise schreibe ich benutzerdefinierte Klassen, die ImageView für diese Dinge erweitern).

Wenn Sie die Bilder in dieser Größe wirklich brauchen, erhöhen Sie einfach die Cache-Größe (siehe Link oben) und Sie sollten bereit sein, zu gehen.

1
Daniele Segato

Die beste Antwort auf diese Frage ist, nicht die runde Bildansicht zu verwenden, sondern die Transformation mit der einfachen Bildansicht zu verwenden, da die Anwendung aufgrund der runden Bildansicht viel Speicher benötigt. Verwenden Sie einfach die einfache Bildansicht und stellen Sie die Transformation damit ein. Verwende diese Transformation

0
Rana Asad