web-dev-qa-db-fra.com

Performance de défilement RecyclerView

J'ai créé l'exemple RecyclerView en me basant sur Création de listes et de cartes guide. Mon adaptateur a une implémentation de modèle uniquement pour gonfler la mise en page. 

Le problème est la faible performance de défilement. Ceci dans un RecycleView avec seulement 8 éléments.

Lors de certains tests, j’ai vérifié que ce problème ne se produisait pas sous Android L. Mais dans la version KitKat, la diminution des performances est évidente.

49
falvojr
12
waclaw

J'ai découvert au moins un motif qui peut nuire à votre performance. Rappelez-vous que onBindViewHolder() est appelé fréquemment . Donc, tout ce que vous faites dans ce code a le potentiel de ralentir votre performance. Si votre RecyclerView effectue une personnalisation, il est très facile de mettre accidentellement du code lent dans cette méthode.

Je changeais les images d'arrière-plan de chaque RecyclerView en fonction de la position. Cependant, le chargement d'images prend un peu de travail, ce qui rend mon RecyclerView lent et saccadé.

Créer un cache pour les images a fonctionné à merveille. onBindViewHolder() modifie maintenant simplement une référence à une image mise en cache au lieu de la charger à partir de zéro. Maintenant, le RecyclerView glisse le long.

Je sais que tout le monde n’aura pas ce problème, je ne vais donc pas prendre la peine de charger du code. Cependant, considérez tout travail effectué dans votre onBindViewHolder() comme un goulot d'étranglement pour une mauvaise performance de RecyclerView.

9
Scott Biggs

En plus de la réponse détaillée de @ Galya, je tiens à préciser que même s'il peut s'agir d'un problème d'optimisation, il est également vrai que l'activation du débogueur peut ralentir considérablement les choses. 

Si vous faites tout pour optimiser votre RecyclerView et que cela ne fonctionne toujours pas correctement, essayez de basculer votre variante de construction sur release et vérifiez son fonctionnement dans un environnement sans développement (avec le débogueur désactivé).

Il m'est arrivé de constater que mon application fonctionnait lentement dans la variante de construction debug, mais dès que je suis passée à la variante release, tout s'est bien déroulé. Cela ne signifie pas que vous devriez développer avec la variante de construction release, mais il est bon de savoir que chaque fois que vous êtes prêt à envoyer votre application, cela fonctionnera parfaitement.

7
blastervla

Je ne sais pas vraiment si l'utilisation de l'indicateur setHasStableId corrigera votre problème. Selon les informations que vous fournissez, votre problème de performance pourrait être lié à un problème de mémoire. Les performances de votre application en termes d’interface utilisateur et de mémoire sont très liées.

La semaine dernière, j'ai découvert que mon application perdait de la mémoire. J'ai découvert cela parce qu'après 20 minutes d'utilisation de mon application, j'ai remarqué que l'interface utilisateur fonctionnait très lentement. Fermer/ouvrir une activité ou faire défiler un RecyclerView avec un tas d’éléments était vraiment lent. Après avoir surveillé certains de mes utilisateurs en production avec http://flowup.io/ j'ai trouvé ceci:

 enter image description here

Le temps de prise de vue était vraiment très élevé et le nombre d'images par seconde vraiment très bas. Vous pouvez voir que certaines images ont nécessité environ 2 secondes pour le rendu: S.

En essayant de comprendre la cause de ce mauvais temps/fps, j'ai découvert que j'avais un problème de mémoire, comme vous pouvez le voir ici:

 enter image description here

Même lorsque la consommation moyenne de mémoire avoisinait les 15 Mo, l'application perdait des images.

C'est comme ça que j'ai découvert le problème de l'interface utilisateur. Une fuite de mémoire dans mon application, provoquant de nombreux événements du ramasse-miettes, a entraîné de mauvaises performances de l'interface utilisateur, car Android VM a dû arrêter mon application pour collecter de la mémoire à chaque image.

En regardant le code, une fuite s'est produite dans une vue personnalisée car je ne désenregistrais pas un auditeur de l'instance Android Choreographer. Après avoir publié le correctif, tout est devenu normal :)

Si votre application supprime des images en raison d'un problème de mémoire, vous devez vérifier deux erreurs courantes:

Vérifiez si votre application alloue des objets dans une méthode appelée plusieurs fois par seconde. Même si cette allocation peut être effectuée dans un endroit différent où votre application est en train de devenir lente. Vous pouvez, par exemple, créer de nouvelles instances d'un objet dans une méthode de vue personnalisée onDraw sur onBindViewHolder dans votre détenteur de vue de recycleur . Vérifiez si votre application enregistre une instance dans le SDK Android sans la publier. L'enregistrement d'un auditeur dans un événement de bus pourrait également être une fuite possible.

Avertissement: L'outil que j'utilise pour contrôler mon application est en cours de développement. J'ai accès à cet outil car je suis l'un des développeurs :) Si vous souhaitez accéder à cet outil, nous publierons bientôt une version bêta! Vous pouvez rejoindre notre site web: http://flowup.io/ .

Si vous souhaitez utiliser différents outils, vous pouvez utiliser: traveview, dmtracedump, systrace ou le moniteur de performances Andorid intégré à Android Studio. Mais rappelez-vous que ces outils surveilleront votre appareil connecté et non le reste de vos appareils utilisateur ou des installations Android OS.

Il est également important de vérifier la mise en page parente dans laquelle vous avez inséré votre Recyclerview. J'ai eu des problèmes de défilement similaires lorsque j'ai testé recyclerView dans un Nestedcrollview. Une vue qui défile dans une autre vue peut faire souffrir dans les performances lors du défilement

2
saintjab

Dans mon cas, j’ai découvert que la cause notable du décalage est le chargement pouvant être dessiné fréquemment dans la méthode #onBindViewHolder(). Je l'ai résolu simplement en chargeant les images en tant que Bitmap une fois à l'intérieur du ViewHolder et d'y accéder à partir de la méthode mentionnée. C'est tout ce que j'ai fait.

2
Chan Teck Wei

Cela m'a aidé à obtenir un défilement plus fluide:

remplacer le onFailedToRecycleView (détenteur de ViewHolder) dans l'adaptateur

et arrêtez toutes les animations en cours (le cas échéant) titulaire. "animateview" .clearAnimation ();

rappelez-vous de retourner vrai;

1
Roar Grønmo

Je vois dans les commentaires que vous implémentez déjà le modèle ViewHolder, mais je posterai ici un exemple d'adaptateur qui utilise le modèle RecyclerView.ViewHolder pour vous permettre de vérifier que vous l'intégrez de la même manière. Votre constructeur peut également varier en fonction de vos besoins , Voici un exemple:

public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {

    Context mContext;
    List<String> mNames;

    public RecyclerAdapter(Context context, List<String> names) {
        mContext = context;
        mNames = names;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
        View view = LayoutInflater.from(viewGroup.getContext())
                .inflate(Android.R.layout.simple_list_item_1, viewGroup, false);

        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int position) {
        //Populate.
        if (mNames != null) {
            String name = mNames.get(position);

            viewHolder.name.setText(name);
        }
    }

    @Override
    public int getItemCount() {

        if (mNames != null)
            return mNames.size();
        else
            return 0;
    }

    /**
     * Static Class that holds the RecyclerView views. 
     */
    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView name;

        public ViewHolder(View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(Android.R.id.text1);
        }
    }
}

Si vous rencontrez des problèmes avec RecyclerView.ViewHolder, assurez-vous de disposer des dépendances appropriées que vous pourrez vérifier à tout moment dans Gradle Please

J'espère que cela résoudra votre problème.

1
Joel

Ajoutant à la réponse de @ Galya, dans bind viewHolder, j’utilisais la méthode Html.fromHtml (). apparemment, cela a un impact sur les performances.

1
Sami Adam

Dans ma casse, j'ai des enfants complexes recyclerview. Cela a donc affecté le temps de chargement de l'activité (~ 5 secondes pour le rendu de l'activité)

Je charge l'adaptateur avec postDelayed () -> Cela donnera le bon résultat pour le rendu d'activité. après l’activité, ma charge de recyclage est lisse.

Essayez cette réponse,

    recyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {
            recyclerView.setAdapter(mAdapter);
        }
    },100); 
1
Ranjith Kumar

Dans mon RecyclerView, j'utilise des images bitmap pour le fond de mon item_layout.
Tout ce que @Galya a dit est vrai (et je le remercie pour sa bonne réponse). Mais ils n'ont pas travaillé pour moi.

C'est ce qui a résolu mon problème:

BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);

Pour plus d'informations, veuillez lire this Answer .

1
mohandes

Je l'ai résolu par cette ligne de code

recyclerView.setNestedScrollingEnabled(false);
0
eLi