Paging使用及源碼分析

說到Paging很多人應(yīng)該都挺陌生的,但是它也是JetPack中的一員,既然Google能覺得用上他就如同坐上火箭,這里就來看一下到底怎么使用以及原理

google這里給出了兩個(gè)實(shí)例

PagingSample

PagingWithNetworkSample

從字面上就可以看出來 一個(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ù)格式
paging_database.PNG

可以看到很簡單,只有一個(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ù)

paging_database_num.PNG

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容