Android RecyclerView Scrolling Performance

JavaAndroidPerformanceAndroid Recyclerview

Java Problem Overview


I have created RecyclerView example basing on Creating Lists and Cards guide. My adapter have a pattern implementation only for inflate the layout.

The problem is the poor scrolling performance. This in a RecycleView with only 8 items.

In some tests I verified that in Android L this problem does not occurs. But in the KitKat version the decreasing of performance is evident.

Java Solutions


Solution 1 - Java

I've recently faced the same issue, so this is what I've done with the latest RecyclerView support library:

  1. Replace a complex layout (nested views, RelativeLayout) with the new optimized ConstraintLayout. Activate it in Android Studio: Go to SDK Manager -> SDK Tools tab -> Support Repository -> check ConstraintLayout for Android & Solver for ConstraintLayout. Add to the dependencies:

     compile 'com.android.support.constraint:constraint-layout:1.0.2'
    
  2. If possible, make all elements of the RecyclerView with the same height. And add:

     recyclerView.setHasFixedSize(true);
    
  3. Use the default RecyclerView drawing cache methods and tweak them according to your case. You don't need third party library to do so:

     recyclerView.setItemViewCacheSize(20);
     recyclerView.setDrawingCacheEnabled(true);
     recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
    
  4. If you use many images, make sure their size and compression are optimal. Scaling images may also affect the performance. There are two sides of the problem - the source image used and the decoded Bitmap. The following example gives you a hint how to decode аn image, downloaded from the web:

     InputStream is = (InputStream) url.getContent();
     BitmapFactory.Options options = new BitmapFactory.Options();
     options.inPreferredConfig = Bitmap.Config.RGB_565;
     Bitmap image = BitmapFactory.decodeStream(is, null, options);
    

The most important part is specifying inPreferredConfig - it defines how many bytes will be used for each pixel of the image. Keep in mind that this is a preferred option. If the source image has more colors, it will still be decoded with a different config.

  1. Make sure onBindViewHolder() is as cheap as possible. You can set OnClickListener once in onCreateViewHolder() and call through an interface a listener outside of the Adapter, passing the clicked item. This way you don't create extra objects all the time. Also check flags and states, before making any changes to the view here.

     viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View view) {
               Item item = getItem(getAdapterPosition());
               outsideClickListener.onItemClicked(item);
           }
     });
    
  2. When data gets changed, try to update only the affected items. For example instead of invalidating the whole data set with notifyDataSetChanged(), when adding / loading more items, just use:

     adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
     adapter.notifyItemRemoved(position);
     adapter.notifyItemChanged(position);
     adapter.notifyItemInserted(position);
    
  3. From Android Developer Web Site :

> Rely on notifyDataSetChanged() as a last resort.

But if you need to use it, maintain your items with unique ids:

    adapter.setHasStableIds(true);

> RecyclerView will attempt to synthesize visible structural change > events for adapters that report that they have stable IDs when this > method is used. This can help for the purposes of animation and visual > object persistence but individual item views will still need to be > rebound and relaid out.

Even if you do everything right, chances are that the RecyclerView is still not performing as smoothly as you would like.

Solution 2 - Java

Solution 3 - Java

I discovered at least one pattern that can kill your performance. Remember that onBindViewHolder() is called frequently. So anything you do in that code has the potential to slam your performance to halt. If your RecyclerView does any customization, it's very easy to accidentally put some slow code in this method.

I was changing the background images of each RecyclerView depending on the position. But loading images takes a bit of work, causing my RecyclerView to be sluggish and jerky.

Creating a cache for the images worked wonders; onBindViewHolder() now just modifies a reference to a cached image instead of loading it from scratch. Now the RecyclerView zips along.

I know that not everyone will have this exact problem, so I'm not bothering to load code. But please consider any work that is done in your onBindViewHolder() as a potential bottle-neck for poor RecyclerView performance.

Solution 4 - Java

In addition to @Galya's detailed answer, I want to state that even though it may be an optimization issue, it is also true that having the debugger enabled can slow things down a lot.

If you do everything to optimize your RecyclerView and it still doesn't work smoothly, try switching your build variant to release, and check how it works in a non-development environment (with the debugger disabled).

It happened to me that my app was performing slowly in debug build variant, but as soon as I switched to the release variant it worked smoothly. This doesn't mean that you should develop with the release build variant, but it is good to know that whenever you are ready to ship your app, it will work just fine.

Solution 5 - Java

I'm not really sure if the usage of setHasStableId flag is going to fix your issue. Based on the information you provide your performance issue could be related to a memory issue. Your application performance in terms of user interface and memory is quite related.

Last week I discovered my app was leaking memory. I discovered this because after 20 minutes using my app I noticed the UI was performing really slow. Closing/opening an activity or scrolling a RecyclerView with a bunch of elements was really slow. After monitoring some of my users in production using http://flowup.io/ I found this:

enter image description here

The frame time was really really high and the frames per second really really low. You can see that some frames needed about 2 seconds to render :S.

Trying to figure it out what was causing this bad frame time/fps I discovered I had a memory issue as you can see here:

enter image description here

Even when the average memory consumption was close to the 15MB at the same time the app was dropping frames.

That's how I discovered the UI issue. I had a memory leak in my app causing a lot of garbage collector events and that's was causing the bad UI performance because the Android VM had to stop my app to collect memory every single frame.

Looking at the code I had a leak inside a custom view because I was not unregistering a listener from the Android Choreographer instance. After releasing the fix, everything became normal :)

If your app is dropping frames due to a memory issue you should review two common errors:

Review if your app is allocating objects inside a method invoked multiple times per second. Even if this allocation can be performed in a different place where your application is becoming slow. An example could be creating new instances of an object inside a onDraw custom view method on onBindViewHolder in your recycler view view holder. Review if your app is registering an instance into the Android SDK but not releasing it. Registering a listener into a bus event could also be possible leak.

Disclaimer: The tool I've been using to monitor my app is under development. I have access to this tool because I'm one of the developers :) If you want access to this tool we will release a beta version soon! You can join in our web site: http://flowup.io/.

If you want to use different tools you can use: traveview, dmtracedump, systrace or the Andorid performance monitor integrated into Android Studio. But remember that this tools will monitor your connected device and not the rest of your user devices or Android OS installations.

Solution 6 - Java

I had a talk about RecyclerView's performance. Here are slides in English and recorded video in Russian.

It contains a set of techniques (some of them are already covered by @Darya's answer).

Here is a brief summary:

  • If Adapter items have fixed size then set:
    recyclerView.setHasFixedSize(true);

  • If data entities can be represented by long (hashCode() for instance) then set:
    adapter.hasStableIds(true);
    and implement:
    // YourAdapter.java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    In this case Item.id() would not work, because it would stay the same even if Item's content has changed.
    P.S. This is not necessary if you are using DiffUtil!

  • Use correctly scaled bitmap. Don't reinvent the wheel and use libraries.
    More info how to choose here.

  • Always use the latest version of RecyclerView. For instance, there were huge performance improvements in 25.1.0 - prefetch.
    More info here.

  • Use DiffUtill.
    DiffUtil is a must.
    Official documentation.

  • Simplify your item's layout!
    Tiny library to enrich TextViews - TextViewRichDrawable

See slides for more detailed explanation.

Solution 7 - Java

Its also important to check the parent layout in which you put in your Recyclerview. I had a similar scrolling issues when I testing recyclerView in a nestedscrollview. A view that scrolls in another view that scroll can suffer in performance during scrolling

Solution 8 - Java

I solved it by this line of code

recyclerView.setNestedScrollingEnabled(false);

Solution 9 - Java

In my case, I found out that the notable cause of the lag is frequent drawable loading inside #onBindViewHolder() method. I solved it just by loading the images as Bitmap once inside the ViewHolder and access it from the mentioned method. That is all I did.

Solution 10 - Java

In my RecyclerView, I use Bitmap Images For background of my item_layout.
Everything @Galya said is true (and I thank him for his great answer). But they didn't work for me.

This is what solved my problem:

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

For more information please read this Answer.

Solution 11 - Java

In mycase I have complex recyclerview childs. So It affected the activity loading time (~5 sec for activity rendering)

I load the adapter with postDelayed() -> this will give the good result for activity rendering. after activity rendering my recyclerview load with smooth.

Try this answer,

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

Solution 12 - Java

I see in the comments that you are already implementing the ViewHolder pattern, but I will post an example adapter here that uses the RecyclerView.ViewHolder pattern so you can verify that you are integrating it in a similar way, again your constructor can vary depending on your needs, here is an example:

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

If you have any trouble working with RecyclerView.ViewHolder make sure you have the appropriate dependencies which you can verify always at Gradle Please

Hope it resolves your problem.

Solution 13 - Java

This helped me getting more smooth scrolling:

override the onFailedToRecycleView(ViewHolder holder) in the adapter

and stop any ongoing animations (if any) holder."animateview".clearAnimation();

remember to return true;

Solution 14 - Java

Adding to @Galya's answer, in bind viewHolder,I was using Html.fromHtml() method. apparently this has performance impact.

Solution 15 - Java

i Solve this issue by using the only one line in with Picasso library

.fit()

Picasso.get().load(currentItem.getArtist_image())

                    .fit()//this wil auto get the size of image and reduce it 

                    .placeholder(R.drawable.ic_doctor)
                    .into(holder.img_uploaderProfile, new Callback() {
                        @Override
                        public void onSuccess() {


                        }

                        @Override
                        public void onError(Exception e) {
                            Toast.makeText(context, "Something Happend Wrong Uploader Image", Toast.LENGTH_LONG).show();
                        }
                    });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionfalvojrView Question on Stackoverflow
Solution 1 - JavaGalyaView Answer on Stackoverflow
Solution 2 - JavawaclawView Answer on Stackoverflow
Solution 3 - JavaSMBiggsView Answer on Stackoverflow
Solution 4 - JavablastervlaView Answer on Stackoverflow
Solution 5 - JavaPedro Vicente Gómez SánchezView Answer on Stackoverflow
Solution 6 - JavaOleksandrView Answer on Stackoverflow
Solution 7 - JavasaintjabView Answer on Stackoverflow
Solution 8 - JavaeliView Answer on Stackoverflow
Solution 9 - JavaWeiView Answer on Stackoverflow
Solution 10 - JavaSajjadView Answer on Stackoverflow
Solution 11 - JavaRanjithkumarView Answer on Stackoverflow
Solution 12 - JavaJoelView Answer on Stackoverflow
Solution 13 - JavaRoar GrønmoView Answer on Stackoverflow
Solution 14 - JavaSami AdamView Answer on Stackoverflow
Solution 15 - JavaHamza RegardlessView Answer on Stackoverflow