【问题标题】:Android RecyclerView Scrolling PerformanceAndroid RecyclerView 滚动性能
【发布时间】:2015-01-27 02:32:21
【问题描述】:

我根据Creating Lists and Cards 指南创建了 RecyclerView 示例。我的适配器有一个仅用于扩展布局的模式实现。

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

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

【问题讨论】:

  • 尝试使用 ViewHolder 设计模式来提高滚动性能:developer.android.com/training/improving-layouts/…
  • @HareshChhelana 谢谢你的回答!但我已经在使用 ViewHolder 模式,根据链接:developer.android.com/training/material/lists-cards.html
  • 您能否分享一些有关您的适配器设置和布局的 XML 文件的代码。这看起来不正常。另外,您是否进行了分析并查看了时间花在了哪些地方?
  • 我面临着几乎相同的问题。除了在 Lollipop 之前的速度很快,在 Android L 中速度非常慢。
  • 你能不能也分享一下你正在导入的库的版本。

标签: java android performance android-recyclerview


【解决方案1】:

我在 cmets 中看到您已经在实现 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 Please 进行验证

希望它能解决您的问题。

【讨论】:

    【解决方案2】:

    【讨论】:

    • 这对我来说性能略有提升。但是 RecyclerView 仍然很慢——比等效的自定义 ListView 慢得多。
    • 啊,但我发现了为什么我的代码这么慢——它与 setHasStableIds() 无关。我会发布一个包含更多信息的答案。
    【解决方案3】:

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

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

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

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

    【讨论】:

    • 我遇到了同样的问题。现在我正在使用 Fresco 进行图像加载和缓存。您是否有另一个更好的解决方案来在 RecyclerView 中加载和缓存图像。谢谢。
    • 我不熟悉 Fresco(阅读...他们的承诺很好)。也许他们对如何通过 RecyclerViews 最好地使用缓存有一些见解。而且我发现您不是唯一遇到此问题的人:github.com/facebook/fresco/issues/414
    【解决方案4】:

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

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

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

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

      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 Developer Web Site

    将 notifyDataSetChanged() 作为最后的手段。

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

        adapter.setHasStableIds(true);
    

    RecyclerView 会尝试合成可见的结构变化 适配器报告它们具有稳定 ID 的事件 使用方法。这有助于动画和视觉的目的 对象持久性,但仍需要单独的项目视图 反弹并反弹。

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

    【讨论】:

    • 一票给 adapter.setHasStableIds(true);真正有助于使 recyclerview 快速的方法。
    • 第 7 部分完全错误!除了使用 adapter.notifyDataSetChanged() 之外,setHasStableIds(true) 不会做任何事情。链接:developer.android.com/reference/android/support/v7/widget/…
    • 我明白为什么recyclerView.setItemViewCacheSize(20); 可以提高性能。但是recyclerView.setDrawingCacheEnabled(true);recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);!我不确定这些会改变什么。这些是View 特定的调用,允许您以编程方式将绘图缓存作为位图检索,并在以后为您所用。 RecyclerView 似乎对此无动于衷。
    • @AbdelhakimAkodadi,滚动通过缓存变得平滑。我已经测试过了。不然怎么说呢,很明显。当然,如果有人像疯了一样卷轴,那将无济于事。我只展示了其他选项,例如我不使用的 setDrawingCacheQuality,因为图像质量对我来说很重要。我不宣扬 DRAWING_CACHE_QUALI‌​TY_HIGH,但建议任何有兴趣深入研究并调整选项的人。
    • setDrawingCacheEnabled() 和 setDrawingCacheQuality() 已弃用。而是使用硬件加速。 developer.android.com/reference/android/view/…
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

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

      帧时间真的非常高,而每秒帧数非常低。你可以看到一些帧需要大约 2 秒来渲染 :S。

      试图找出导致这种糟糕的帧时间/fps 的原因,我发现我遇到了内存问题,如您在此处看到的:

      即使在应用丢帧的同时平均内存消耗接近 15MB。

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

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

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

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

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

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

      【讨论】:

        【解决方案7】:

        我谈到了RecyclerView 的表现。这里是slides in Englishrecorded video in Russian

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

        这里是一个简短的总结:

        • 如果 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 的内容发生了变化,它也会保持不变。
          P.S.如果您使用的是 DiffUtil,则无需这样做!

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

        • 始终使用最新版本的RecyclerView。例如,25.1.0 - prefetch 有巨大的性能改进。
          更多信息here

        • 使用 DiffUtill。
          DiffUtil is a must
          Official documentation

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

        更详细的解释见slides

        【讨论】:

          【解决方案8】:

          这帮助我获得更流畅的滚动:

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

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

          记得 返回真;

          【讨论】:

            【解决方案9】:

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

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

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

            更多信息请阅读 this Answer.

            【讨论】:

              【解决方案10】:

              除了@Galya 的详细回答之外,我想声明的是,即使它可能是一个优化问题,启用调试器也确实会减慢很多速度。

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

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

              【讨论】:

              • 这条评论对我很有帮助!我已经尝试了一切来提高我的 recyclerview 的性能,但没有任何帮助,但是一旦我切换到发布版本,我意识到一切都很好。
              • 即使没有附加调试器,我也遇到了同样的问题,但一旦转移到发布版本,问题就不再可见了
              【解决方案11】:

              在我的情况下,我有复杂的 recyclerview 孩子。所以它影响了活动加载时间(活动渲染约 5 秒)

              我使用 postDelayed() 加载适配器 -> 这将为活动渲染提供良好的结果。活动后使我的 recyclerview 加载顺利。

              试试这个答案,

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

              【讨论】:

                【解决方案12】:

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

                【讨论】:

                  【解决方案13】:

                  我通过这行代码解决了

                  recyclerView.setNestedScrollingEnabled(false);
                  

                  【讨论】:

                  • 具有讽刺意味的是,大多数人可能正在寻找这个简单的选项,而通过查看他们正在分析或寻找如此复杂的解决方案的赞成票
                  【解决方案14】:

                  添加到@Galya 的答案,在绑定 viewHolder 中,我使用的是 Html.fromHtml() 方法。显然这会对性能产生影响。

                  【讨论】:

                    【解决方案15】:

                    我通过使用毕加索库中唯一的一行来解决这个问题

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

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-03-26
                      • 1970-01-01
                      • 2018-01-05
                      相关资源
                      最近更新 更多