Paging使用及源码分析

说到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    
}

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容