說到Paging很多人應(yīng)該都挺陌生的,但是它也是JetPack中的一員,既然Google能覺得用上他就如同坐上火箭,這里就來看一下到底怎么使用以及原理
google這里給出了兩個(gè)實(shí)例
從字面上就可以看出來 一個(gè)是針對于網(wǎng)絡(luò)的例子,話不多說 直接上碼
在PagingSample中
// CheeseDao.kt
@Query("SELECT * FROM Cheese ORDER BY name COLLATE NOCASE ASC")
fun allCheesesByName(): PagingSource<Int, Cheese>
@Entity
data class Cheese(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
官方用了一個(gè)數(shù)據(jù)庫數(shù)據(jù)讀取,來作為paging的展示demo
數(shù)據(jù)格式可以看到很簡單,只有一個(gè)id和name
而那個(gè)PagingSource則在下面源碼分析時(shí)在提,現(xiàn)在看上去它很像是一堆數(shù)據(jù)的查詢事件結(jié)果
// CheeseViewModel.kt
val allCheeses = Pager(
PagingConfig(
//該值至少可以填充幾個(gè)屏幕上的內(nèi)容
pageSize = 60,
//簡單來說,打開了,滾動條就是完整大小,關(guān)上了;隨著多頁面增加,滾動條會抖動(建議不顯示滾動條)
enablePlaceholders = true,
//PagedList一次可以保存在內(nèi)存中的最大項(xiàng)目數(shù)
maxSize = 200
)
) {
dao.allCheesesByName()
}.flow
以上部分就是從數(shù)據(jù)庫中讀取指定的數(shù)據(jù)
// MainActivity.kt
lifecycleScope.launch {
viewModel.allCheeses.collectLatest { adapter.submitData(it) }
}
頁面則更簡單了,獲取的數(shù)據(jù)直接提交給頁面,這個(gè)提交方式很類似ListAdapter
// CheeseAdapter.kt
class CheeseAdapter : PagingDataAdapter<Cheese, CheeseViewHolder>(diffCallback) {
override fun onBindViewHolder(holder: CheeseViewHolder, position: Int) {
holder.bindTo(getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheeseViewHolder =
CheeseViewHolder(parent)
}
//CheeseViewHolder.kt
class CheeseViewHolder(parent :ViewGroup) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.cheese_item, parent, false)) {
private val nameView = itemView.findViewById<TextView>(R.id.name)
var cheese : Cheese? = null
fun bindTo(cheese : Cheese?) {
this.cheese = cheese
nameView.text = cheese?.name
}
}
我第一次看的時(shí)候,想著這就完了? 然后立馬去修改請求的maxSize想看看會有什么反應(yīng)
aximum size must be at least
pageSize + 2*prefetchDist, pageSize=20, prefetchDist=20, maxSize=20
然后就出現(xiàn)這個(gè)錯(cuò)誤 ,我立馬就明白了,最大的緩存需要是你展示單頁數(shù)據(jù)的3倍 因?yàn)槊看味家辽偌虞d你上方,下方和當(dāng)前頁面 =。=
然后我把pageSize改成了15,maxSize改成了45,進(jìn)入后迅速滑動,第一時(shí)間出現(xiàn)卡頓情況(預(yù)想是超出內(nèi)存緩存,一次緩存45,之后就可以正?;瑒恿耍ㄔ谖铱磥響?yīng)該是allCheesesByName把所有數(shù)據(jù)都落地了,這時(shí)候就要在submitData()這里斷點(diǎn)看看數(shù)據(jù)是什么樣子的))
然后我發(fā)現(xiàn)submitData()只被調(diào)用了一次,接著立刻查詢了一下數(shù)據(jù)庫里的數(shù)據(jù)
ok有654條,再debug看submitData提交的數(shù)據(jù)
=。= 它居然是一個(gè)PagingData<T>的對象 好吧看來到這里就不得不分析源碼了
PagingSource
首先來看PagingSource,它是一個(gè)抽象類
// PagingSource.kt
// PagedList.Config 調(diào)用 toRefreshLoadParams后 就會刷新參數(shù)
fun <Key : Any> PagedList.Config.toRefreshLoadParams(key: Key?): PagingSource.LoadParams<Key> =
PagingSource.LoadParams.Refresh(
key,
initialLoadSizeHint,
enablePlaceholders,
)
abstract class PagingSource<Key : Any, Value : Any> {
// 密封類 加載所需的參數(shù)
sealed class LoadParams<Key : Any> constructor(val loadSize: Int, val placeholdersEnabled: Boolean,){
abstract val key: Key?
// 字面意思就是刷新
class Refresh<Key : Any> constructor(
override val key: Key?,
loadSize: Int,
placeholdersEnabled: Boolean,
) : LoadParams<Key>(
loadSize = loadSize,
placeholdersEnabled = placeholdersEnabled,
)
// 向后加載
class Append<Key : Any> constructor(
override val key: Key,
loadSize: Int,
placeholdersEnabled: Boolean,
) : LoadParams<Key>(
loadSize = loadSize,
placeholdersEnabled = placeholdersEnabled,
)
// 向前加載
class Prepend<Key : Any> constructor(
override val key: Key,
loadSize: Int,
placeholdersEnabled: Boolean,
) : LoadParams<Key>(
loadSize = loadSize,
placeholdersEnabled = placeholdersEnabled,
)
// 伴生對象 這里可以看出來是對幾種模式進(jìn)行了賦值操作
internal companion object {
fun <Key : Any> create(
loadType: LoadType,
key: Key?,
loadSize: Int,
placeholdersEnabled: Boolean,
): LoadParams<Key> = when (loadType) {
LoadType.REFRESH -> Refresh( // 我是刷新
key = key,
loadSize = loadSize,
placeholdersEnabled = placeholdersEnabled,
)
LoadType.PREPEND -> Prepend( // 我是向后添加
loadSize = loadSize,
key = requireNotNull(key) {
"key cannot be null for prepend"
},
placeholdersEnabled = placeholdersEnabled,
)
LoadType.APPEND -> Append( // 我是向前添加
loadSize = loadSize,
key = requireNotNull(key) {
"key cannot be null for append"
},
placeholdersEnabled = placeholdersEnabled,
)
}
}
}
// 加載結(jié)果
sealed class LoadResult<Key : Any, Value : Any> {
// 錯(cuò)誤類
data class Error<Key : Any, Value : Any>(
val throwable: Throwable
) : LoadResult<Key, Value>()
// Success result object for [PagingSource.load]
// 可以看出這是load返回成功的數(shù)據(jù)集
data class Page<Key : Any, Value : Any> constructor(
val data: List<Value>, // 數(shù)據(jù)
val prevKey: Key?, // 上一頁 (可以為空)
val nextKey: Key?, // 下一頁 (可以為空)
@IntRange(from = COUNT_UNDEFINED.toLong())
val itemsBefore: Int = COUNT_UNDEFINED,// 加載前項(xiàng)目數(shù)量
@IntRange(from = COUNT_UNDEFINED.toLong())
val itemsAfter: Int = COUNT_UNDEFINED// 加載后項(xiàng)目數(shù)量
) : LoadResult<Key, Value>() {
constructor(
data: List<Value>,
prevKey: Key?,
nextKey: Key?
) : this(data, prevKey, nextKey, COUNT_UNDEFINED, COUNT_UNDEFINED)
......
// 一小段初始化
}
open val jumpingSupported: Boolean // 是否支持跳轉(zhuǎn)(是否是連續(xù)的)
get() = false
open val keyReuseSupported: Boolean // 是否支持重用Key
get() = false
}
以上就是PagingSource的代碼了,它只是個(gè)抽象類,看上去目的是定義了大致的數(shù)據(jù)框架,確定了加載模式,并確定了數(shù)據(jù)格式,之后就到了CheeseViewModel中的flow操作了
// Pager.kt
class Pager<Key : Any, Value : Any>{
constructor(
config: PagingConfig,
initialKey: Key? = null,
remoteMediator: RemoteMediator<Key, Value>?,
pagingSourceFactory: () -> PagingSource<Key, Value>){
constructor(
config: PagingConfig,
initialKey: Key? = null,
pagingSourceFactory: () -> PagingSource<Key, Value>
) : this(config, initialKey, null, pagingSourceFactory)
}
val flow: Flow<PagingData<Value>> = PageFetcher(
pagingSourceFactory = if (
pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>
) {
pagingSourceFactory::create
} else {
{
pagingSourceFactory()
}
},
initialKey = initialKey,
config = config,
remoteMediator = remoteMediator
).flow
}
// 今天先到這,明天繼續(xù)