97

我基于以下内容创建了RecyclerView示例创建列表和卡片指南。我的适配器有一个仅用于充气布局的模式实现。

问题是滚动性能不佳。这是一个只有8个项目的循环视图。

在一些测试中,我验证了Android L中没有出现这个问题。但在KitKat版本中,性能明显下降。

10

16个答案16

重置为默认值
250

我最近遇到过同样的问题,所以这是我使用最新的RecyclerView支持库所做的:

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

    编译“com.android.support.constraint:constraint-layout:1.0.2”
  2. 如果可能,请使用相同高度。并添加:

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

    recyclerView.setItemViewCacheSize(20);recyclerView.setDrawingCacheEnabled(true);recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
  4. 如果你用很多图像,确保他们尺寸和压缩都是最佳的。缩放图像也可能影响性能。问题有两个方面——使用的源图像和解码的位图。以下示例为您提供了如何解码从web下载的аn图像的提示:

    InputStream是=(InputStream)url.getContent();位图工厂。选项选项=新BitmapFactory。选项();options.inPreferredConfig=位图。配置。RGB_565;位图图像=BitmapFactory.decodeStream(is,null,options);

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

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

    viewHolder.itemView.setOnClickListener(新View.OnClickListener(){@覆盖public void onClick(查看视图){项目项=getItem(getAdapterPosition());outsideClickListener.onItemClicked(项目);}});
  2. 当数据发生更改时,请尝试仅更新受影响的项目。例如,而不是使用notifyDataSetChanged(),添加/加载更多项目时,只需使用:

    适配器.notifyItemRangeInserted(范围开始,范围结束);适配器.notifyItemRemoved(位置);adapter.notifyItemChanged(位置);适配器.notifyItemInserted(位置);
  3. 发件人Android开发人员网站:

最后依靠notifyDataSetChanged()。

但如果您需要使用它,请使用唯一ID:

适配器.setHasStableIds(true);

RecyclerView将尝试合成可见的结构更改适配器的事件,这些适配器报告它们具有稳定的ID方法。这有助于实现动画和视觉效果对象持久性,但单个项目视图仍需要反弹并放松。

即使您做得很好,RecyclerView仍有可能无法像您希望的那样顺利运行。

23
  • 23
    一票支持适配器.setHasStableIds(true);真正有助于快速回收利用的方法。
    – 阿图拉
    评论 2016年11月24日6:54
  • 1
    第七部分完全错了!setHasStableIds(true)除了使用adapter.notifyDataSetChanged()之外什么也不做。链接:developer.android.com/reference/android/support/v7/widget/… 评论 2017年8月14日14:45
  • 1
    我明白为什么recyclerView.setItemViewCacheSize(20);可以提高性能。但是recyclerView.setDrawingCacheEnabled(true);recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);尽管如此!我不确定这些会改变什么。这些是查看允许您以编程方式将图形缓存检索为位图并在以后使用它的特定调用。回收站视图似乎对此没有任何反应。 评论 2017年11月13日20:33
  • 2
    @AbdelhakimAkodadi,滚动通过缓存变得平滑。我已经测试过了。你怎么能不这么说,这是显而易见的。当然,如果有人像疯了一样滚动,没有任何帮助。我只显示了其他选项,比如setDrawingCacheQuality,我不使用它,因为图像质量在我的例子中很重要。我不鼓吹DRAWING_CACHE_QUALI TY_HIGH,但建议有兴趣的人深入研究并调整选项。
    – 加利娅
    评论 2017年11月14日12:07
  • 不推荐使用setDrawingCacheEnabled()和setDrawing CacheQuality()。而是使用硬件加速。developer.android.com/reference/android/view/… 评论 2018年12月28日13:41
13

我通过添加以下标志解决了这个问题:

https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#setHasStableIds(布尔值)

2
  • 这使我的性能略有提高。但RecyclerView的死日志速度仍然很慢,比同等的自定义ListView慢得多。 评论 2016年6月10日7:23
  • 1
    啊,但我发现了为什么我的代码这么慢——它与setHasStableIds()无关。我会发布一个包含更多信息的答案。 评论 2016年6月10日15:54
13

我发现至少有一种模式会影响你的表现。记住onBindViewHolder()被称为频繁地因此,您在该代码中所做的任何操作都有可能使您的性能急剧下降。如果您的RecyclerView进行了任何自定义,则很容易意外地在此方法中放入一些速度较慢的代码。

我根据位置改变了每个RecyclerView的背景图像。但加载图像需要一些工作,导致我的RecyclerView缓慢而不稳定。

为图像创建缓存产生了奇迹;onBindViewHolder()现在只需修改对缓存图像的引用,而不是从头开始加载。现在,RecyclerView快速运行。

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

2
  • 我也有同样的问题。现在我正在使用Fresco进行图像加载和缓存。您是否有其他更好的解决方案来在RecyclerView中加载和缓存图像。谢谢。 评论 2016年6月28日7:06
  • 我不熟悉弗雷斯科(阅读……他们的承诺很好)。也许他们对如何将缓存与RecyclerViews结合使用有一些见解。我知道你不是唯一有这个问题的人:github.com/facebook/fresco/issues/414. 评论 2016年6月28日17:40
11

我谈过回收站视图的性能。这里有英文幻灯片俄语录制视频.

它包含一组技术(其中一些已经由@Darya的回答).

下面是一个简短的总结:

  • 如果适配器项目具有固定大小,然后设置:
    recyclerView.setHasFixedSize(true);

  • 如果数据实体可以用long表示(哈希代码()例如),然后设置:
    适配器.hasStableIds(true);
    并实施:
    //YourAdapter.java(您的适配器.java)
    @覆盖
    public long getItemId(int位置){
    return-items.get(position).hashcode()//id()
    }
    在这种情况下项目.id()不会起作用,因为即使项目的内容已更改。
    附言:如果您使用DiffUtil,则无需执行此操作!

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

  • 始终使用最新版本的回收站视图例如,在25.1.0-预取。
    更多信息在这里.

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

  • 简化物品的布局!
    用于丰富TextViews的小型库-文本视图RichDrawable

请参阅幻灯片以获得更详细的解释。

1
  • 我正在将DiffUtil与ListAdapter一起使用并覆盖获取项目ID仍然需要。
    – 位DEVS
    评论 2022年3月27日10:21
11

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

如果您竭尽全力优化您的回收站视图但它仍然无法顺利运行,请尝试将构建变体切换为释放,并检查它在非开发环境中的工作方式(禁用调试器)。

碰巧我的应用程序在调试构建变体,但只要我切换到释放变种它运行平稳。这并不意味着你应该与释放构建变体,但很高兴知道,只要您准备好发布应用程序,它就会正常工作。

2
  • 这个评论对我真的很有帮助!我尝试了所有方法来提高我的回收视图的性能,但没有什么真正的帮助,但当我切换到发布版本时,我意识到一切都很好。 评论 2018年2月22日17:24
  • 1
    即使没有附加调试器,我也面临着同样的问题,但一旦转移到发布版本,问题就不复存在了 评论 2018年8月8日6:14
10

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

上周我发现我的应用程序正在泄漏内存。我发现这一点是因为在使用我的应用程序20分钟后,我注意到UI的运行速度非常慢。关闭/打开一个活动或滚动一个包含大量元素的RecyclerView真的很慢。在使用http://flowup.io/我发现了这个:

在此处输入图像描述

帧时间非常高,每秒帧数非常低。您可以看到,某些帧需要大约2秒钟才能渲染:S。

试图找出导致这种错误帧时间/fps的原因,我发现我有一个内存问题,如您所见:

在此处输入图像描述

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

这就是我发现UI问题的原因。我的应用程序内存泄漏,导致了很多垃圾收集器事件,这导致了UI性能不佳,因为Android虚拟机必须停止我的应用以收集每一帧的内存。

查看代码时,我在自定义视图中发现了一个漏洞,因为我没有从Android Choreographer实例中注销侦听器。发布修复程序后,一切正常:)

如果你的应用程序由于内存问题而丢弃帧,你应该查看两个常见错误:

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

免责声明:我用来监控我的应用程序的工具正在开发中。我可以访问这个工具,因为我是开发人员之一:)如果您想访问这个工具的话,我们很快就会发布测试版!您可以加入我们的网站:http://flowup.io/.

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

在我的RecyclerView中,我使用位图图像作为项目布局的背景。
@Galya所说的一切都是真的(我感谢他的精彩回答)。但他们不适合我。

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

位图工厂。选项选项=新BitmapFactory。选项();options.inSampleSize=2;位图Bitmap=BitmapFactory.decodeStream(流,null,选项);

有关更多信息,请阅读这个答案.

检查放置在Recycleview中的父布局也很重要。在嵌套滚动视图中测试recyclerView时,我遇到了类似的滚动问题。在另一个视图中滚动的视图在滚动期间可能会影响性能

0

我用这行代码解决了它

recyclerView.setNestedScrollingEnabled(false);
1
  • 2
    具有讽刺意味的是,这个简单的选项可能是大多数人想要的,而通过看赞成票,他们正在分析或寻找如此复杂的解决方案 评论 2021年12月21日10:50
2

在我的案例中,我发现滞后的显著原因是频繁的内部可拉拔荷载#onBindViewHolder()方法。我通过将图像加载为位图一旦进入视图支架并通过上述方法访问它。这就是我所做的。

1

我在评论中看到您已经在实施ViewHolder(ViewHolder)模式,但我将在此处发布一个使用回收站视图。ViewHolder(ViewHolder)模式,以便您可以验证是否以类似的方式进行了集成,您的构造函数也可以根据您的需要而有所不同,下面是一个示例:

公共类RecyclerAdapter扩展了Recycler视图。适配器<回收适配器。ViewHolder>{上下文mContext;列表<String>mNames;public RecyclerAdapter(上下文,列表<String>名称){mContext=上下文;mNames=名称;}@覆盖CreateViewHolder上的public ViewHolder(ViewGroup ViewGroup,int viewType){视图视图=LayoutInfliter.from(viewGroup.getContext()).inflate(android.R.layout.simple_list_item_1,viewGroup,false);return new ViewHolder(视图);}@覆盖public void onBindViewHolder(ViewHolder ViewHolder,int位置){//填充。if(mNames!=null){字符串名称=mNames.get(位置);viewHolder.name.setText(名称);}}@覆盖public int getItemCount(){if(mNames!=null)return mNames.size();其他的返回0;}/***保存RecyclerView视图的静态类。*/静态类ViewHolder扩展了RecyclerView。视图支架{TextView名称;public ViewHolder(查看项查看){super(itemView);name=(TextView)itemView.findViewById(android.R.id.text1);}}}

如果您在工作中遇到任何困难回收站视图。ViewHolder(ViewHolder)确保您有适当的依赖项,可以随时在格雷德尔请

希望它能解决你的问题。

1

这有助于我更顺畅地滚动:

覆盖适配器中的onFailedToRecycleView(ViewHolder支架)

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

记住返回true;

1

在我的案例中,我有一个复杂的回收儿童。因此,它影响了活动加载时间(活动渲染约5秒)

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

试试这个答案,

recyclerView.postDelayed(新的Runnable()){@覆盖公共void run(){recyclerView.setAdapter(mAdapter);}},100);
1

添加到@Galya的答案中,在bind-viewHolder中,我使用了Html.fromHtml()方法。显然,这对性能有影响。

0

如果你在recycler视图中使用图像,请尝试使用下面的功能使图像更加轻量级

fun getImageThumbnail(context:context,imageFileName:String):位图?{val-options=位图工厂。选项()options.inSampleSize=2个选项options.inPreferredConfig=位图。配置。RGB_565;val目录=context.filesDirval file=文件(目录,图像文件名)if(!file.exists())返回nullval位图:bitmap=BitmapFactory.decodeStream(FileInputStream(file),null,options)!!var较大=位图宽度if(bitmat.width<bitmat.height)bigger=bitmap.heightval zarib=(200.toDouble()/更大)val width=(bitmap.width*zarib).toInt()val height=(bitmap.height*zarib).toInt()return ThumbnailUtils.extractThumbnali(位图、宽度、高度)}
-1

我用毕加索图书馆的唯一一行解决了这个问题

.fit()

毕加索.get().load(currentItem.getArtist_image()).fit()//这将自动获取图像的大小并将其缩小占位符(R.drawable.ic_doctor).into(holder.img_uploaderProfile,新回调(){@覆盖公共void onSuccess(){}@覆盖public void onError(异常e){Toast.makeText(context,“上传者图像出错”,Toast.LENGTH_LONG).show();}});

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.