我已经基于“创建列表和卡片”指南创建了RecyclerView示例。我的适配器具有仅用于扩大布局的模式实现。
问题是滚动性能差。这在RecycleView中只有8个项目。
在某些测试中,我验证了在Android L中不会发生此问题。但是在KitKat版本中,性能下降是显而易见的。
Answers:
我最近遇到了同样的问题,所以这是我对最新的RecyclerView支持库所做的事情:
用新的优化的ConstraintLayout替换复杂的布局(嵌套视图,RelativeLayout)。在Android Studio中激活它:转到SDK Manager-> SDK Tools选项卡->支持信息库->检查ConstraintLayout(对于Android)和Solver(对于ConstraintLayout)。添加到依赖项:
compile 'com.android.support.constraint:constraint-layout:1.0.2'
如果可能,请使RecyclerView的所有元素具有相同的height。并添加:
recyclerView.setHasFixedSize(true);
使用默认的RecyclerView图形缓存方法,并根据您的情况进行调整。您不需要第三方库来这样做:
recyclerView.setItemViewCacheSize(20);
recyclerView.setDrawingCacheEnabled(true);
recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
如果使用很多图像,请确保它们的大小和压缩率最佳。缩放图像也可能会影响性能。问题有两个方面-使用的源图像和解码的位图。以下示例向您提示如何解码从网上下载的图像:
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
-它定义将为图像的每个像素使用多少字节。请记住,这是首选。如果源图像具有更多颜色,则仍将使用其他配置对其进行解码。
确保onBindViewHolder()尽可能便宜。您可以一次设置OnClickListeneronCreateViewHolder()
并通过接口调用Adapter外部的侦听器,从而传递被单击的项。这样,您就不会一直创建额外的对象。在对视图进行任何更改之前,还要检查标志和状态。
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Item item = getItem(getAdapterPosition());
outsideClickListener.onItemClicked(item);
}
});
更改数据后,请尝试仅更新受影响的项目。例如notifyDataSetChanged()
,当添加/加载更多项目时,不要使用来使整个数据集无效,而只需使用:
adapter.notifyItemRangeInserted(rangeStart, rangeEnd);
adapter.notifyItemRemoved(position);
adapter.notifyItemChanged(position);
adapter.notifyItemInserted(position);
作为最后的手段,依赖notifyDataSetChanged()。
但是,如果您需要使用它,请使用唯一的ID维护商品:
adapter.setHasStableIds(true);
RecyclerView将尝试为使用此方法报告适配器具有稳定ID的适配器合成可见的结构更改事件。这可以帮助实现动画和视觉对象的持久性,但是仍然需要重新放置和重新布置单个项目视图。
即使您做的一切正确,RecyclerView仍然可能无法达到您想要的平稳状态。
recyclerView.setItemViewCacheSize(20);
可以改善性能。但是recyclerView.setDrawingCacheEnabled(true);
,recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
尽管!我不确定这些会改变什么。这些是View
特定的调用,使您可以以编程方式将图形缓存作为位图进行检索,并在以后利用它来发挥自己的优势。RecyclerView
似乎对此无能为力。
我发现至少有一种模式可以破坏您的表现。请记住,onBindViewHolder()
被称为常。因此,您在该代码中所做的任何事情都有可能使您的性能停止。如果您的RecyclerView进行了任何自定义,则很容易意外地在此方法中放入一些慢速代码。
我根据位置更改每个RecyclerView的背景图像。但是加载图像需要一些工作,导致我的RecyclerView呆滞而生涩。
为图像创建缓存可以实现奇迹; onBindViewHolder()
现在只修改对缓存图像的引用,而不是从头开始加载它。现在,RecyclerView随即压缩。
我知道并不是每个人都会遇到这个确切的问题,因此我不必费心加载代码。但是,请考虑将您所做的任何工作onBindViewHolder()
作为降低RecyclerView性能的潜在瓶颈。
除了@Galya的详细答案之外,我想说明一下,即使这可能是一个优化问题,也可以启用调试器,这会使速度大大降低。
如果您已尽一切努力优化自己RecyclerView
,但仍无法顺利运行,请尝试将您的构建变体切换为release
,并检查其在非开发环境中的工作方式(禁用调试器)。
我碰巧我的应用在debug
构建变体中运行缓慢,但是当我切换到该release
变体后,它就顺利运行了。这并不意味着您应该使用release
build变体进行开发,但是很高兴知道,只要准备好交付应用,它就可以正常工作。
我谈到了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
有关详细说明,请参见幻灯片。
我不太确定使用setHasStableId
flag是否可以解决您的问题。根据您提供的信息,性能问题可能与内存问题有关。就用户界面和内存而言,您的应用程序性能非常相关。
上周,我发现我的应用正在泄漏内存。我发现此问题是因为使用我的应用程序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安装。
检查放入Recyclerview的父布局也很重要。在nestedscrollview中测试recyclerView时,我遇到了类似的滚动问题。在另一个视图中滚动的视图在滚动过程中可能会降低性能
在我的情况下,我有复杂的recyclerview孩子。因此,它影响了活动的加载时间(活动呈现大约需要5秒)
我用postDelayed()加载适配器->这将为活动渲染提供良好的结果。活动结束后,我的recyclerview负载呈现平滑状态。
试试这个答案,
recyclerView.postDelayed(new Runnable() {
@Override
public void run() {
recyclerView.setAdapter(mAdapter);
}
},100);
我在注释中看到您已经实现了该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上随时进行验证,请
希望它能解决您的问题。
添加到@Galya的答案中,在bind viewHolder中,我正在使用Html.fromHtml()方法。显然,这会对性能产生影响。
我通过使用毕加索库中的仅一行来解决此问题
。适合()
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();
}
});