美文网首页
Paging使用及源码分析

Paging使用及源码分析

作者: 虞_18bd | 来源:发表于2021-01-07 21:28 被阅读0次

说到Paging很多人应该都挺陌生的,但是它也是JetPack中的一员,既然Google能觉得用上他就如同坐上火箭,这里就来看一下到底怎么使用以及原理

google这里给出了两个实例

PagingSample

PagingWithNetworkSample

从字面上就可以看出来 一个是针对于网络的例子,话不多说 直接上码

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

官方用了一个数据库数据读取,来作为paging的展示demo

数据格式 paging_database.PNG

可以看到很简单,只有一个id和name

而那个PagingSource则在下面源码分析时在提,现在看上去它很像是一堆数据的查询事件结果

// CheeseViewModel.kt 

val allCheeses = Pager(
    PagingConfig(
        //该值至少可以填充几个屏幕上的内容
        pageSize = 60, 
        //简单来说,打开了,滚动条就是完整大小,关上了;随着多页面增加,滚动条会抖动(建议不显示滚动条)
        enablePlaceholders = true,
        //PagedList一次可以保存在内存中的最大项目数
        maxSize = 200
    )
) {
    dao.allCheesesByName()
}.flow

以上部分就是从数据库中读取指定的数据

// MainActivity.kt
lifecycleScope.launch {
    viewModel.allCheeses.collectLatest { adapter.submitData(it) }
}

页面则更简单了,获取的数据直接提交给页面,这个提交方式很类似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
    }
}

我第一次看的时候,想着这就完了? 然后立马去修改请求的maxSize想看看会有什么反应

aximum size must be at least

pageSize + 2*prefetchDist, pageSize=20, prefetchDist=20, maxSize=20

然后就出现这个错误 ,我立马就明白了,最大的缓存需要是你展示单页数据的3倍 因为每次都要至少加载你上方,下方和当前页面 =。=

然后我把pageSize改成了15,maxSize改成了45,进入后迅速滑动,第一时间出现卡顿情况(预想是超出内存缓存,一次缓存45,之后就可以正常滑动了(在我看来应该是allCheesesByName把所有数据都落地了,这时候就要在submitData()这里断点看看数据是什么样子的))

然后我发现submitData()只被调用了一次,接着立刻查询了一下数据库里的数据

paging_database_num.PNG

ok有654条,再debug看submitData提交的数据

=。= 它居然是一个PagingData<T>的对象 好吧看来到这里就不得不分析源码了

PagingSource

首先来看PagingSource,它是一个抽象类

// PagingSource.kt

// PagedList.Config 调用 toRefreshLoadParams后 就会刷新参数
fun <Key : Any> PagedList.Config.toRefreshLoadParams(key: Key?): PagingSource.LoadParams<Key> =
    PagingSource.LoadParams.Refresh(
        key,
        initialLoadSizeHint,
        enablePlaceholders,
    )

abstract class PagingSource<Key : Any, Value : Any> {
    // 密封类 加载所需的参数
    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,
        )
        
        // 伴生对象  这里可以看出来是对几种模式进行了赋值操作
        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,
                )
            }
        }
    }
    
    // 加载结果
    sealed class LoadResult<Key : Any, Value : Any> {
        // 错误类
        data class Error<Key : Any, Value : Any>(
            val throwable: Throwable
        ) : LoadResult<Key, Value>()
        
        // Success result object for [PagingSource.load]
        // 可以看出这是load返回成功的数据集
        data class Page<Key : Any, Value : Any> constructor(
            val data: List<Value>,  //  数据
            val prevKey: Key?,      //  上一页 (可以为空)
            val nextKey: Key?,      //  下一页 (可以为空) 
            @IntRange(from = COUNT_UNDEFINED.toLong())
            val itemsBefore: Int = COUNT_UNDEFINED,//        加载前项目数量
            @IntRange(from = COUNT_UNDEFINED.toLong())
            val itemsAfter: Int = COUNT_UNDEFINED//          加载后项目数量
        ) : LoadResult<Key, Value>() {
            constructor(
                data: List<Value>,
                prevKey: Key?,
                nextKey: Key?
            ) : this(data, prevKey, nextKey, COUNT_UNDEFINED, COUNT_UNDEFINED)
            ......
            // 一小段初始化 
        }
        
        open val jumpingSupported: Boolean // 是否支持跳转(是否是连续的)
            get() = false
        
        open val keyReuseSupported: Boolean // 是否支持重用Key
            get() = false
    
}

以上就是PagingSource的代码了,它只是个抽象类,看上去目的是定义了大致的数据框架,确定了加载模式,并确定了数据格式,之后就到了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    
}

// 今天先到这,明天继续

相关文章

网友评论

      本文标题:Paging使用及源码分析

      本文链接:https://www.haomeiwen.com/subject/ckytaktx.html