一、RecyclerView
1、RecyclerView的多级缓存机制,每一级缓存具体作用是什么,分别在什么场景下会用到哪些缓存
RecyclerView的多级缓存机制是为了提高滚动和数据更新的效率而设计的。每一级缓存都有其特定的作用和使用场景。以下是各级缓存的作用和它们的使用场景:
- 一级缓存:mAttachedScrap 和 mChangedScrap
- 二级缓存:mCachedViews
- 三级缓存:ViewCacheExtension
- 四级缓存:RecycledViewPool
- mAttachedScrap:
- 作用:这是一个ArrayList,用于存储暂时分离的子视图(Scrap view)。这些视图可以重复使用而不需要与RecyclerView完全分离。如果视图不需要重新绑定数据,就不会进行修改。
- 使用场景:在布局期间和数据更新时,这个缓存会被使用。
- mChangedScrap:
- 作用:类似于mAttachedScrap,但它存放的是发生变化的ViewHolder。如果使用到了这里的缓存的ViewHolder,需要重新走Adapter的绑定方法。
- 使用场景:在数据有局部更新时使用。
- mCachedViews:
- 作用:这是一个有容量限制的ArrayList,默认大小为2。它存放的是已经从RecyclerView中移除的视图,但ViewHolder仍然保存着之前的信息,如位置和绑定的数据等。
- 使用场景:在滚动过程中和预取(prefetch)时使用。
- RecycledViewPool:
- 作用:这是一个全局的缓存池,可以跨多个RecyclerView共享。它用于存储不再需要的ViewHolder,任何绑定过的痕迹都没有了。当需要新的item时,可以从这个池中获取,并进行重用。
- 使用场景:在Item被移除、有更新或滚动过程中使用。
- ViewCacheExtension:
- 作用:这是一个开发者可以自定义的缓存层级。官方没有默认实现,它允许开发者根据自己的需求来缓存ViewHolder。
- 使用场景:取决于开发者如何实现它。
通过有效地管理ViewHolder的缓存和重用,RecyclerView的多级缓存机制极大地提高了处理大量数据集时的性能和效率。
2、RecyclerView的滑动回收复用机制
RecyclerView的滑动回收复用机制是其核心功能之一,它允许应用高效地处理大量的数据集。这个机制确保了用户界面的流畅滚动,即使是在数据集非常大的情况下。
- 滑动和回收:
- 当用户滑动屏幕时,RecyclerView会检测到哪些项(item views)不再可见,并将这些项的ViewHolder回收到缓存中。这个过程称为回收(Recycling)。
- 回收的ViewHolder会被存放在一个叫做
mCachedViews
的缓存列表中。这个列表有一个固定的大小,当达到上限时,最早回收的ViewHolder会被移除并放入另一个缓存池,即RecycledViewPool
。
- 复用:
- 当新的项需要显示在屏幕上时,RecyclerView会首先尝试从
mCachedViews
中复用ViewHolder。如果找到了合适的ViewHolder,就会直接使用它,而不需要重新创建一个新的。 - 如果
mCachedViews
中没有可用的ViewHolder,RecyclerView会转而从RecycledViewPool
中寻找。RecycledViewPool
是一个更大的缓存池,可以跨多个RecyclerView实例共享。
- 当新的项需要显示在屏幕上时,RecyclerView会首先尝试从
- 绑定数据:
- 一旦找到了一个可复用的ViewHolder,RecyclerView会通过调用Adapter的
onBindViewHolder
方法来绑定新的数据。这个过程称为绑定(Binding)。 - 绑定数据是必要的步骤,因为虽然ViewHolder可能是复用的,但显示的数据需要是当前项的数据。
- 一旦找到了一个可复用的ViewHolder,RecyclerView会通过调用Adapter的
- LayoutManager的角色:
LayoutManager
负责决定屏幕上项的布局方式。它也参与回收和复用的过程,因为它知道哪些项不再可见,以及何时需要新的项来填充屏幕。
- 优化:
- RecyclerView还包含了其他优化措施,比如预取(Prefetching),它会在后台线程中提前绑定数据,以减少滚动时的延迟。
通过这种方式,RecyclerView能够快速地回收和复用ViewHolder,从而实现高效的滚动性能。这个机制是Android开发中非常重要的优化手段,它使得即使是数据量巨大的列表也能够流畅地滚动。
3、RecyclerView的刷新回收复用机制
RecyclerView的刷新回收复用机制是其核心特性之一,它允许应用程序高效地处理和显示大量数据。这个机制主要包括以下几个步骤:
- 刷新(Refresh):
- 当数据集发生变化时,例如通过
notifyDataSetChanged()
方法,RecyclerView会被通知需要刷新。 - 刷新操作会导致RecyclerView重新绑定和布局视图,但尽可能地利用已有的ViewHolder进行数据绑定,以避免不必要的视图创建。
- 当数据集发生变化时,例如通过
- 回收(Recycle):
- 当滑动RecyclerView时,屏幕上不再可见的视图会被回收到缓存中。
- 回收的视图不会立即被销毁,而是存放在缓存中,等待复用。
- 复用(Reuse):
- 当需要显示新的数据项时,RecyclerView会首先尝试从缓存中查找可复用的ViewHolder。
- 如果缓存中有合适的ViewHolder,就会直接使用它并绑定新的数据,而不是创建一个新的ViewHolder。
RecyclerView的回收复用机制涉及到几个关键的缓存结构:
- mCachedViews: 这是一个临时缓存,用于存放最近被回收的ViewHolder。它的默认大小为2,但可以根据需要调整。
- RecycledViewPool: 这是一个更大的缓存池,用于存放不同类型的ViewHolder。它允许不同的RecyclerView共享ViewHolder,从而提高复用效率。
- ViewCacheExtension: 这是一个可选的缓存扩展点,开发者可以自定义缓存策略,以适应特定的复用需求。
在刷新过程中,RecyclerView会尽量复用已有的ViewHolder,减少创建和销毁视图的开销,从而提高性能和流畅度。这个机制确保了即使在数据频繁更新的情况下,用户界面也能保持流畅的滚动体验。
4、RecyclerView 为什么要预布局
RecyclerView 的预布局用于 Item 动画中,也叫做预测动画。其用于当 Item 项进行变化时执行的一次布局过程(如添加或删除 Item 项),使 ItemAnimator 体验更加友好。
考虑以下 Item 项删除场景,屏幕内的 RecyclerView 列表包含两个 Item 项:item1 和 item2。当删除 item2 时,item3 从底部平滑出现在 item2 的位置:
+-------+ +-------+
| | <-----+ | | <-----+
| item1 | | | item1 | |
| | | | | |
+-------+ screen ----> +-------+ screen
| | | | | |
| item2 | | | item3 | |
| | <-----+ | | <-----+
+-------+ +-------+
上述效果是如何实现的呢?我们知道 RecyclerView 只会布局屏幕内可见的 Item,对于屏幕外的 item3,如何知道其要运行的动画轨迹呢?要形成轨迹,至少需要知道起始点,而 item3 的终点位置是很明确的,也就是被删除的 item2 位置。那起点是如何确定的呢?
对于这种情况,RecyclerView 会进行两次布局:
- 第一次被称为 pre-layout,也就是预布局。在这一阶段,RecyclerView 将不可见的 item3 也加载进布局内,得到 [item1, item2, item3] 的布局信息。
- 之后再执行一次 post-layout,得到 [item1, item3] 的布局信息。比对两次 item3 的布局信息,也就确定了 item3 的动画轨迹了。
这个预布局的过程使得 ItemAnimator 能够更好地执行动画,提升用户体验。
5、ListView 与 RecyclerView区别
ListView 和 RecyclerView 都是 Android 中用于展示列表数据的组件,但它们在设计和功能上有一些关键的区别。以下是 ListView 和 RecyclerView 的主要区别:
- 缓存机制:
- ListView 有一个基本的缓存机制,它通过重用屏幕外的视图来优化性能。
- RecyclerView 引入了一个更复杂的缓存系统,包括四级缓存,使得视图的重用和数据更新更加高效。
- 布局管理:
- ListView 默认只支持垂直滚动的列表。
- RecyclerView 通过 LayoutManager 支持更多样化的布局,如线性布局、网格布局和瀑布流布局。
- ViewHolder 的使用:
- 在 ListView 中,开发者需要自定义 ViewHolder 并使用
setTag()
和getTag()
方法来优化视图的重用。 - RecyclerView 有一个规范化的 ViewHolder 使用方式,不需要
setTag()
和getTag()
方法。
- 在 ListView 中,开发者需要自定义 ViewHolder 并使用
- 数据更新:
- ListView 在数据更新时通常使用
notifyDataSetChanged()
方法,这会导致整个列表刷新。 - RecyclerView 允许局部数据更新,如
notifyItemChanged()
,这样可以提高性能和用户体验。
- ListView 在数据更新时通常使用
- 动画和装饰:
- RecyclerView 提供了丰富的动画 API 和装饰功能,可以轻松添加分隔线、动画等。
- ListView 的动画和装饰功能较为有限。
- 触摸反馈:
- RecyclerView 通过 ItemTouchHelper 支持拖动和滑动操作,这在 ListView 中不是原生支持的。
- 嵌套滚动:
- RecyclerView 实现了嵌套滚动机制,可以与其他滚动容器(如 ViewPager)更好地协同工作。
- ListView 没有实现嵌套滚动机制。
6、RecyclerView性能优化
当涉及到 Android 中的列表展示时,RecyclerView
是一个常用的组件。它的性能对用户体验至关重要。
数据优化
- 分页加载和数据缓存:
- 对于远程数据,建议进行分页加载,并对拉取的数据进行缓存。这样可以提高二次加载速度。
- 使用
DiffUtil
来局部刷新新增或删除的数据,而不是全局刷新整个列表。DiffUtil
是 Android Support 库中的一个工具类,用于判断新旧数据的差异,从而进行局部刷新。
- 分离数据处理和视图绑定:
- 在
RecyclerView.Adapter
的onBindViewHolder
方法中,应该只是将数据设置到视图中,而不应进行耗时的业务处理。例如,避免在此方法中进行日期比较和格式化操作。
- 在
布局优化
- 减少过度绘制:
- 减少布局层级,可以考虑使用自定义 View 来减少层级,或者更合理地设置布局来减少层级。
- 注意:目前不推荐在
RecyclerView
中使用ConstraintLayout
,因为在某些版本中性能表现不佳。
- 减少 XML 文件的 Inflate 时间:
- XML 文件包括
layout
和drawable
的 XML,它们的 Inflate 操作是耗时的 I/O 操作。 - 可以考虑使用代码生成布局,即通过
new View()
的方式来创建布局。
- XML 文件包括
- 减少 View 对象的创建:
- 复杂的列表项可能包含大量的 View,而 View 的创建会消耗时间。因此,尽量简化 ItemView,设计共用的部分,减少 View 的构造和嵌套。
- 设置高度固定:
- 如果列表项的高度是固定的,可以使用
RecyclerView.setHasFixedSize(true)
来避免不必要的requestLayout
操作。
- 如果列表项的高度是固定的,可以使用
- 共用
RecycledViewPool
:- 在嵌套的
RecyclerView
中,如果子RecyclerView
具有相同的适配器,可以设置共用一个RecycledViewPool
。 - 注意:如果使用的是
LinearLayoutManager
或其子类,需要手动开启这个特性。
- 在嵌套的
二、Fragment
1、Fragment的生命周期(结合Activity的生命周期)
在 Android 开发中,理解 Fragment 的生命周期及其与 Activity 生命周期的关系是非常重要的。Fragment 的生命周期与 Activity 紧密相关,但也有其独特的回调方法。以下是 Fragment 生命周期的一个概述,结合了 Activity 的生命周期:
- onAttach():
- 当 Fragment 与 Activity 关联时调用。
- 这是 Fragment 生命周期的第一个回调。
- onCreate():
- 用于进行 Fragment 的初始创建。
- 在这里可以初始化除了视图以外的组件。
- onCreateView():
- 创建并返回与 Fragment 关联的视图层次结构。
- 这是设置 Fragment 布局的地方。
- onActivityCreated():
- 当 Activity 完成 onCreate() 方法后调用。
- 可以在这里进行最终的初始化,如检索视图和恢复状态。
- onStart():
- 当 Fragment 对用户可见时调用。
- Activity 的 onStart() 也会被调用。
- onResume():
- 当 Fragment 开始与用户交互时调用。
- Activity 的 onResume() 也会被调用。
- onPause():
- 当 Fragment 不再与用户交互时调用