Java RecyclerView 滚动性能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27188536/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 03:57:39  来源:igfitidea点击:

RecyclerView Scrolling Performance

javaandroidperformanceandroid-recyclerviewrecycler-adapter

提问by falvojr

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

我根据创建列表和卡片指南创建了 RecyclerView 示例。我的适配器有一个仅用于膨胀布局的模式实现。

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

问题是滚动性能不佳。这在只有 8 个项目的 RecycleView 中。

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.

在一些测试中,我验证了在 Android L 中不会出现此问题。但是在 KitKat 版本中,性能的下降很明显。

采纳答案by Galya

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

我最近遇到了同样的问题,所以这就是我对最新的 RecyclerView 支持库所做的:

  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 cachemethods 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);
    
  1. 新优化的 ConstraintLayout替换复杂的布局(嵌套视图、RelativeLayout)。在 Android Studio 中激活它:转到 SDK Manager -> SDK Tools 选项卡 -> Support Repository -> 检查 ConstraintLayout for Android 和 Solver for ConstraintLayout。添加到依赖项:

    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    
  2. 如果可能,使 RecyclerView 的所有元素具有相同的高度。并添加:

    recyclerView.setHasFixedSize(true);
    
  3. 使用默认的 RecyclerView绘图缓存方法并根据您的情况调整它们。您不需要第三方库来这样做:

    recyclerView.setItemViewCacheSize(20);
    recyclerView.setDrawingCacheEnabled(true);
    recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
    
  4. 如果您使用许多图像,请确保它们的大小和压缩是最佳的。缩放图像也可能影响性能。问题有两个方面 - 使用的源图像和解码的位图。以下示例为您提供了如何解码从网络下载的图像的提示:

    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 preferredoption. If the source image has more colors, it will still be decoded with a different config.

最重要的部分是指定inPreferredConfig- 它定义了图像的每个像素将使用多少字节。请记住,这是首选选项。如果源图像有更多颜色,它仍将使用不同的配置进行解码。

  1. Make sure onBindViewHolder()is as cheapas 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:

  1. 确保onBindViewHolder()尽可能便宜。您可以设置 OnClickListener 一次,onCreateViewHolder()并通过接口调用适配器外部的侦听器,传递单击的项目。这样您就不会一直创建额外的对象。在对此处的视图进行任何更改之前,还要检查标志和状态。

    viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View view) {
              Item item = getItem(getAdapterPosition());
              outsideClickListener.onItemClicked(item);
          }
    });
    
  2. 当数据发生变化时,尽量只更新受影响的项目。例如notifyDataSetChanged(),在添加/加载更多项目时,不要使用 使整个数据集无效,只需使用:

    adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
    adapter.notifyItemRemoved(position);
    adapter.notifyItemChanged(position);
    adapter.notifyItemInserted(position);
    
  3. 来自Android 开发者网站

Rely on notifyDataSetChanged() as a last resort.

依靠 notifyDataSetChanged() 作为最后的手段。

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

但是,如果您需要使用它,请使用唯一 id维护您的项目:

    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.

RecyclerView 将尝试为使用此方法时报告它们具有稳定 ID 的适配器合成可见的结构更改事件。这有助于实现动画和视觉对象持久性,但仍需要重新分配和重新布局单个项目视图。

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

即使你做的一切都正确,很可能 RecyclerView 仍然没有你想要的那么顺利。

回答by Joel

I see in the comments that you are already implementing the ViewHolderpattern, but I will post an example adapter here that uses the RecyclerView.ViewHolderpattern 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:

我在评论中看到您已经在实施该ViewHolder模式,但我将在此处发布一个使用该RecyclerView.ViewHolder模式的示例适配器,以便您可以验证是否以类似的方式集成它,同样,您的构造函数可以根据您的需要而有所不同,here是一个例子:

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.ViewHoldermake sure you have the appropriate dependencies which you can verify always at Gradle Please

如果您在使用时遇到任何问题,请RecyclerView.ViewHolder确保您拥有适当的依赖项,您可以随时在Gradle 中验证这些依赖项,

Hope it resolves your problem.

希望它能解决您的问题。

回答by Scott Biggs

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.

我发现了至少一种可以扼杀你表现的模式。请记住,这onBindViewHolder()经常调用。因此,您在该代码中所做的任何事情都有可能使您的性能停止。如果你的 RecyclerView 做了任何自定义,很容易不小心在这个方法中放了一些慢代码。

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.

我正在根据位置更改每个 RecyclerView 的背景图像。但是加载图像需要一些工作,导致我的 RecyclerView 缓慢而生涩。

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.

为图像创建缓存创造了奇迹;onBindViewHolder()现在只是修改对缓存图像的引用,而不是从头开始加载它。现在 RecyclerView 开始了。

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.

我知道并不是每个人都会遇到这个确切的问题,所以我不费心去加载代码。但是请考虑将您所做的任何工作onBindViewHolder()视为 RecyclerView 性能不佳的潜在瓶颈。

回答by Chan Teck Wei

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 Bitmaponce inside the ViewHolderand access it from the mentioned method. That is all I did.

就我而言,我发现延迟的显着原因是#onBindViewHolder()方法内部频繁的可绘制加载。我只是通过在ViewHolder 中将图像作为Bitmap加载一次并从提到的方法访问它来解决它。这就是我所做的。

回答by Pedro Vicente Gómez Sánchez

I'm not really sure if the usage of setHasStableIdflag 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.

我不确定使用setHasStableIdflag 是否会解决您的问题。根据您提供的信息,您的性能问题可能与内存问题有关。您的应用程序在用户界面和内存方面的性能非常相关。

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:

上周我发现我的应用程序正在泄漏内存。我发现这一点是因为在使用我的应用程序 20 分钟后,我注意到 UI 的执行速度非常慢。关闭/打开一个 Activity 或滚动带有一堆元素的 RecyclerView 真的很慢。在使用http://flowup.io/监视我的一些生产用户后,我发现了这一点:

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.

帧时间真的很长,每秒帧数真的很低。你可以看到一些帧需要大约 2 秒来渲染: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:

试图弄清楚是什么导致了这个糟糕的帧时间/fps,我发现我有一个内存问题,你可以在这里看到:

enter image description here

在此处输入图片说明

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

即使平均内存消耗接近 15MB,同时应用程序也会丢帧。

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.

这就是我发现UI问题的方式。我的应用程序内存泄漏导致了大量垃圾收集器事件,这导致了糟糕的 UI 性能,因为 Android VM 必须停止我的应用程序以在每一帧收集内存。

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 :)

查看代码,我在自定义视图中发生了泄漏,因为我没有从 Android Choreographer 实例中取消注册侦听器。发布修复后,一切都变得正常了:)

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.

检查您的应用程序是否在每秒多次调用的方法内分配对象。即使此分配可以在您的应用程序变慢的其他地方执行。一个示例可能是在回收器视图视图持有者的 onBindViewHolder 上的 onDraw 自定义视图方法内创建对象的新实例。检查您的应用程序是否正在将实例注册到 Android SDK 中但未发布它。将侦听器注册到总线事件也可能是泄漏。

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/.

免责声明:我一直用来监控我的应用程序的工具正在开发中。我可以使用此工具,因为我是开发人员之一 :) 如果您想使用此工具,我们将很快发布测试版!您可以加入我们的网站: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.

如果您想使用不同的工具,您可以使用:traveview、dmtracedump、systrace 或集成到 Android Studio 中的 Andorid 性能监视器。但请记住,此工具将监控您连接的设备,而不是您的其他用户设备或 Android 操作系统安装。

回答by Oleksandr

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

我谈过RecyclerView他的表现。这里有英文幻灯片俄文录制的视频

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

它包含一组技术(@Darya's answer已经涵盖了其中一些技术)。

Here is a brief summary:

这里是一个简短的总结:

  • If Adapteritems 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

  • 如果Adapter项目具有固定大小,则设置:
    recyclerView.setHasFixedSize(true);

  • 如果数据实体可以用 long 表示(hashCode()例如),那么 set:
    adapter.hasStableIds(true);
    和 implement:
    // YourAdapter.java
    @Override
    public long getItemId(int position) {
    return items.get(position).hashcode(); //id()
    }
    在这种情况下Item.id()将不起作用,因为即使Item的内容发生变化,它也会保持不变。
    PS 如果您使用的是 DiffUtil,这不是必需的!

  • 使用正确缩放的位图。不要重新发明轮子并使用库。
    更多信息如何选择这里

  • 始终使用最新版本的RecyclerView. 例如,25.1.0预取中有巨大的性能改进。
    更多信息在这里

  • 使用 DiffUtil。
    DiffUtil 是必须的
    官方文档

  • 简化您的项目的布局!
    丰富 TextViews 的小库 - TextViewRichDrawable

See slidesfor more detailed explanation.

有关更详细的解释,请参阅幻灯片

回答by Roar Gr?nmo

This helped me getting more smooth scrolling:

这帮助我获得更平滑的滚动:

override the onFailedToRecycleView(ViewHolder holder) in the adapter

覆盖适配器中的 onFailedToRecycleView(ViewHolder holder)

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

并停止任何正在进行的动画(如果有)holder."animateview".clearAnimation();

remember to return true;

记得返回true;

回答by mohandes

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.

在我的 RecyclerView 中,我使用位图图像作为 item_layout 的背景。
@Galya 所说的一切都是真的(我感谢他的出色回答)。但他们对我不起作用。

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.

有关更多信息,请阅读 此答案

回答by blastervla

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.

除了@Galya 的详细回答之外,我还想说明,尽管这可能是一个优化问题,但启用调试器确实会大大降低速度。

If you do everything to optimize your RecyclerViewand 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).

如果您尽一切努力优化您的RecyclerView,但它仍然不能顺利运行,请尝试将您的构建变体切换到release,并检查它在非开发环境中的工作方式(禁用调试器)。

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

我碰巧我的应用程序在debug构建变体中运行缓慢,但是一旦我切换到release变体,它就可以顺利运行。这并不意味着您应该使用release构建变体进行开发,但很高兴知道只要您准备好发布您的应用程序,它就会正常工作。