使用AsyncListUtil优化RecyclerView
Posted 技术视界
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用AsyncListUtil优化RecyclerView相关的知识,希望对你有一定的参考价值。
大家好,今天为大家推荐的是来自极光推送的Hevin同学所翻译的文章,重点是介绍AsyncListUtil这个类的使用,它在android API 23开始就被加入到support.v7当中了,你可能很少看到有人提起过它,但实际上却是非常实用的工具类,至于怎么使用,看完本文你就知道。当然除了AsyncListUtil之外,DiffUtil这个类也是值得你了解一下的,本文虽然没有提及,有兴趣的读者可以自行拓展。文章的示例代码虽是用Kotlin编写,但不妨碍Java的同学阅读。
AsyncListUtil 是一个用于异步内容加载的类,在 Android API 23 时被加入到 support.v7 当中。不过好像很多人对它还并不了解,网上也没有太多相关的资料。今天这里就来介绍下 AsyncListUtil 的用法。
首先,AsyncListUtil 通常和 RecyclerView 搭配使用的。其能够在后台线程中加载 Cursor 数据,同时保持 UI 和缓存的同步来实现更好的用户体验。不过 AsyncListUtil 是通过单个线程加载数据,因此适用于从二级存储(比如硬盘)中加载数据,而不适用于从网络加载数据的情况。
RecyclerView的结构
相信绝大部分 Android 开发者对此都已经非常熟悉了。
RecyclerView + AsyncListUtil的结构
可以看到 AsyncListUtil 是通过 AsyncListUtil.ViewCallback 来判断当前数据可见的范围,再通过 AsyncListUtil.DataCallback 从后台加载所需的数据,并在加载完成时通知 AsyncListUtil.ViewCallback。
因此要使用 AsyncListUtil,首先需要继承实现 AsyncListUtil.DataCallback 和 AsyncListUtil.ViewCallback 这两个抽象类。
下面我们通过代码来看看实际要怎样实现?先上效果图:
数据
作者实现了一个简单的Python脚本生成了 100,000 条数据并存放在 SQLite 数据库中。每一条数据都有 id, title 和 content 三个属性。其中的 title 和 content 都是通过 DWYL’s english-words repository 随机生成。
ItemSource
class Item(var title: String, var content: String)
interface ItemSource {
fun getCount(): Int
fun getItem(position: Int): Item
fun close()
}
定义 SQLiteItemSource 来从 SQLite 中获取数据:
class SQLiteItemSource(val database: SQLiteDatabase) : ItemSource {
private var _cursor: Cursor? = null
private val cursor: Cursor
get() {
if (_cursor == null || _cursor?.isClosed != false) {
_cursor = database.rawQuery("SELECT title, content FROM data", null)
}
return _cursor ?: throw AssertionError("Set to null or closed by another thread")
}
override fun getCount() = cursor.count
override fun getItem(position: Int): Item {
cursor.moveToPosition(position)
return Item(cursor)
}
override fun close() {
_cursor?.close()
}
}
private fun Item(c: Cursor): Item = Item(c.getString(0), c.getString(1))
Callbacks
为了创建 AsyncListUtil,我们需要传入 DataCallback 和 ViewCallback。
首先让我们实现 DataCallback:
private class DataCallback(val itemSource: ItemSource) : AsyncListUtil.DataCallback<Item>() {
override fun fillData(data: Array<Item>?, startPosition: Int, itemCount: Int) {
if (data != null) {
for (i in 0 until itemCount) {
data[i] = itemSource.getItem(startPosition + i)
}
}
}
override fun refreshData(): Int = itemSource.getCount()
fun close() {
itemSource.close()
}
}
DataCallback 是用来为 AsyncListUtil 提供数据访问,其中所有方法都会在后台线程中调用。
其中有两个方法必需要实现:
fillData(data, startPosition, itemCount) - 当 AsyncListUtil 需要更多数据时,将会在后台线程调用该方法。
refreshData() - 返回刷新后的数据个数。
再实现 ViewCallback:
private class ViewCallback(val recyclerView: RecyclerView) : AsyncListUtil.ViewCallback() {
override fun onDataRefresh() {
recyclerView.adapter.notifyDataSetChanged()
}
override fun getItemRangeInto(outRange: IntArray?) {
if (outRange == null) {
return
}
(recyclerView.layoutManager as LinearLayoutManager).let { llm ->
outRange[0] = llm.findFirstVisibleItemPosition()
outRange[1] = llm.findLastVisibleItemPosition()
}
if (outRange[0] == -1 && outRange[1] == -1) {
outRange[0] = 0
outRange[1] = 0
}
}
override fun onItemLoaded(position: Int) {
recyclerView.adapter.notifyItemChanged(position)
}
}
AsyncListUtil 通过 ViewCallback 主要是做两件事:
通知视图数据已经更新(onDataRefresh);
了解当前视图所展示数据的位置,从而确定什么时候获取更多数据或释放掉目前不在窗口内的旧数据(getItemRangeInto);
接下来实现 ScrollListener 来调用 AsyncListUtil 的 onRangeChanged() 方法:
private class ScrollListener(val listUtil: AsyncListUtil<in Item>) : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView?, newState: Int) {
listUtil.onRangeChanged()
}
}
Adapter
至此,AsyncListUtil 所需要的组件都准备好了,可以来实现我们的 RecyclerView.Adapter 了:
class AsyncAdapter(itemSource: ItemSource, recyclerView: RecyclerView) : RecyclerView.Adapter<ViewHolder>() {
private val dataCallback = DataCallback(itemSource)
private val listUtil = AsyncListUtil(Item::class.java, 500, dataCallback, ViewCallback(recyclerView))
private val onScrollListener = ScrollListener(listUtil)
fun onStart(recyclerView: RecyclerView?) {
recyclerView?.addOnScrollListener(onScrollListener)
listUtil.refresh()
}
fun onStop(recyclerView: RecyclerView?) {
recyclerView?.removeOnScrollListener(onScrollListener)
dataCallback.close()
}
override fun onBindViewHolder(holder: ViewHolder?, position: Int) {
holder?.bindView(listUtil.getItem(position), position)
}
override fun getItemCount(): Int = listUtil.itemCount
override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder {
val inf = LayoutInflater.from(parent.context)
return ViewHolder(inf.inflate(R.layout.item, parent, false))
}
}
其中实例化 AsyncListUtil 时的 500 表示分页大小。
要注意的一点是 listUtil.getItem(position) 在指定 position 对应的数据仍在被加载时会返回 null ,因此需要在 ViewHolder 中处理当 item 为 null 的情况:
class ViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView) {
private val title: TextView? = itemView?.findViewById(R.id.title)
private val content: TextView? = itemView?.findViewById(R.id.content)
fun bindView(item: Item?, position: Int) {
title?.text = "$position ${item?.title ?: "loading"}"
content?.text = item?.content ?: "loading"
}
}
这里当 item 为 null 时,就简单的显示 “loading”。
最后,在 Activity 中把所有的这些组合起来:
class MainActivity : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var adapter: AsyncAdapter
private lateinit var itemSource: SQLiteItemSource
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recycler)
itemSource = SQLiteItemSource(getDatabase(this, "database.sqlite"))
adapter = AsyncAdapter(itemSource, recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
}
override fun onStart() {
super.onStart()
adapter.onStart(recyclerView)
}
override fun onStop() {
super.onStop()
adapter.onStop(recyclerView)
}
}
完整项目代码可以在 Github 上找到:https://github.com/jasonwyatt/AsyncListUtil-Example
原文链接:https://android.jlelse.eu/how-to-use-asynclistutil-16b5175bb468
微信文章不支持文中的外链,可以跳转到原文继续阅读。
以上是关于使用AsyncListUtil优化RecyclerView的主要内容,如果未能解决你的问题,请参考以下文章
基于Android官方AsyncListUtil优化改进RecyclerView分页加载机制
Android Room联合AsyncListUtil实现RecyclerView分页加载ORM数据