Android RecyclerView滚动性能


86

我已经基于“创建列表和卡片”指南创建了RecyclerView示例。我的适配器具有仅用于扩大布局的模式实现。

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

在某些测试中,我验证了在Android L中不会发生此问题。但是在KitKat版本中,性能下降是显而易见的。


1
尝试使用ViewHolder设计模式为滚动性能:developer.android.com/training/improving-layouts/...
Haresh Chhelana

@HareshChhelana谢谢您的回答!但是根据链接,我已经在使用ViewHolder模式:developer.android.com/training/material/lists-cards.html
falvojr 2014年

2
您能否共享一些有关适配器设置和布局XML文件的代码。这看起来不正常。另外,您是否配置文件并查看花费的时间?
yigit 2014年

2
我面临着几乎所有相同的问题。除了在棒棒糖之前的快速速度和在Android L上令人难以置信的(真正)缓慢之外
Servus7

1
您还可以共享要导入的库的版本吗?
Droidekas

Answers:


227

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

  1. 新的优化的ConstraintLayout替换复杂的布局(嵌套视图,RelativeLayout)。在Android Studio中激活它:转到SDK Manager-> SDK Tools选项卡->支持信息库->检查ConstraintLayout(对于Android)和Solver(对于ConstraintLayout)。添加到依赖项:

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

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

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

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

    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开发者网站

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

但是,如果您需要使用它,请使用唯一的ID维护商品:

    adapter.setHasStableIds(true);

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

即使您做的一切正确,RecyclerView仍然可能无法达到您想要的平稳状态。


18
对adapter.setHasStableIds(true)一票;确实有助于快速实现recyclerview的方法。
Atula

1
第7部分是完全错误的!setHasStableIds(true)不会执行任何操作,除非您使用adapter.notifyDataSetChanged()。链接:developer.android.com/reference/android/support/v7/widget/...
本地主机

1
我明白了为什么recyclerView.setItemViewCacheSize(20);可以改善性能。但是recyclerView.setDrawingCacheEnabled(true);recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);尽管!我不确定这些会改变什么。这些是View特定的调用,使您可以以编程方式将图形缓存作为位图进行检索,并在以后利用它来发挥自己的优势。RecyclerView似乎对此无能为力。
阿卜杜勒·哈基姆(Abdelhakim AKODADI)

1
@AbdelhakimAkodadi,缓存使滚动变得流畅。我已经测试过了 很明显,你怎么说呢?当然,如果有人像疯了似的滚动,没有任何帮助。我只显示其他选项,例如setDrawingCacheQuality,我不使用它,因为图像质量对我而言很重要。我不讲道DRAWING_CACHE_QUALI‌TY_HIGH,但建议有兴趣深入研究和调整选项的人。
加利亚

2
不推荐使用setDrawingCacheEnabled()和setDrawingCacheQuality()。而是使用硬件加速。developer.android.com/reference/android/view/…–
Shayan_Aryan


13

我发现至少有一种模式可以破坏您的表现。请记住,onBindViewHolder()被称为。因此,您在该代码中所做的任何事情都有可能使您的性能停止。如果您的RecyclerView进行了任何自定义,则很容易意外地在此方法中放入一些慢速代码。

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

为图像创建缓存可以实现奇迹; onBindViewHolder()现在只修改对缓存图像的引用,而不是从头开始加载它。现在,RecyclerView随即压缩。

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


我有同样的问题。现在,我正在使用Fresco进行图像加载和缓存。您是否有另一个更好的解决方案来在RecyclerView中加载和缓存图像。谢谢。
Androidicus

我不熟悉壁画(阅读...他们的诺言很好)。也许他们对如何在RecyclerViews中最好地使用缓存有一些见识。而且我知道您不是唯一遇到此问题的人:github.com/facebook/fresco/issues/414
SMBiggs

10

除了@Galya的详细答案之外,我想说明一下,即使这可能是一个优化问题,也可以启用调试器,这会使速度大大降低。

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

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


此评论对我真的很有帮助!我已经尽一切努力提高了recyclerview的性能,但并没有什么真正的帮助,但是当我切换到发行版后,我意识到一切都很好。
塔尔·巴尔达

1
即使没有安装调试器,我也面临相同的问题,但是一旦移至发布版本,问题就再也看不到了
Farmaan Elahi

8

我谈到了RecyclerView的表现。这是英文幻灯片俄语录制的视频

它包含一组技术(@Darya的答案已经涵盖了其中的一些技术)。

这是一个简短的摘要:

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

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

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

  • 始终使用最新版本的RecyclerView。例如,25.1.0预取在性能上有了巨大的提高。
    更多信息在这里

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

  • 简化物品的布局!
    微型库可丰富TextViews- TextViewRichDrawable

有关详细说明,请参见幻灯片


7

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

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

在此处输入图片说明

帧时间真的很高,每秒帧数真的很低。您会看到某些帧需要大约2秒钟才能渲染:S。

试图弄清楚是什么原因导致了不好的帧时间/ fps,我发现我遇到了内存问题,如下所示:

在此处输入图片说明

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

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

在查看代码时,自定义视图内部存在泄漏,因为我没有从Android Choreographer实例中注销侦听器。发布此修复程序后,一切都变得正常:)

如果您的应用由于内存问题而丢帧,则应查看两个常见错误:

查看您的应用是否在每秒多次调用的方法中分配对象。即使可以在应用程序变慢的其他位置执行此分配。例如,可以在回收者视图视图持有者的onBindViewHolder的onDraw自定义视图方法内创建对象的新实例。查看您的应用是否正在将实例注册到Android SDK中而不是将其释放。将侦听器注册到总线事件中也可能会泄漏。

免责声明:我一直在使用的用于监视我的应用程序的工具正在开发中。我可以使用此工具,因为我是开发人员之一:)如果您想使用此工具,我们将尽快发布beta版!您可以加入我们的网站:http : //flowup.io/

如果要使用其他工具,则可以使用:traveview,dmtracedump,systrace或集成到Android Studio中的Andorid性能监视器。但是请记住,此工具将监视您连接的设备,而不监视其余的用户设备或Android OS安装。


3

检查放入Recyclerview的父布局也很重要。在nestedscrollview中测试recyclerView时,我遇到了类似的滚动问题。在另一个视图中滚动的视图在滚动过程中可能会降低性能


是有效点。我也面临同样的问题!
Ranjith Kumar

2

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


2

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

这是解决我的问题的方法:

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

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


2

在我的情况下,我有复杂的recyclerview孩子。因此,它影响了活动的加载时间(活动呈现大约需要5秒)

我用postDelayed()加载适配器->这将为活动渲染提供良好的结果。活动结束后,我的recyclerview负载呈现平滑状态。

试试这个答案,

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

1

我在注释中看到您已经实现了该ViewHolder模式,但是我将在此处发布一个使用该RecyclerView.ViewHolder模式的示例适配器,以便您可以验证是否以类似的方式对其进行了集成,构造函数也可以根据您的需求而变化,此处是一个例子:

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

如果您在使用时遇到任何麻烦,请RecyclerView.ViewHolder确保您拥有适当的依赖项,可以在Gradle上随时进行验证,

希望它能解决您的问题。


1

这使我的滚动更加流畅:

重写适配器中的onFailedToRecycleView(ViewHolder持有人)

并停止所有正在进行的动画(如果有)的持有人。“ animateview” .clearAnimation();

记得返回true;


1

我用这行代码解决了

recyclerView.setNestedScrollingEnabled(false);


0

我通过使用毕加索库中的仅一行来解决此问题

。适合()

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();
                        }
                    });
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.