Android.Arch.Paging: 分頁(yè)加載的新選項(xiàng)

一、概述

在很久很久以前,加載并展示大量數(shù)據(jù)就已成為各家應(yīng)用中必不可少的業(yè)務(wù)場(chǎng)景,分頁(yè)加載也就成了必不可少的方案。在現(xiàn)有的Android API中也已存在支持分頁(yè)加載內(nèi)容的方案, 比如:

  • CursorAdapter:它簡(jiǎn)化了數(shù)據(jù)庫(kù)中數(shù)據(jù)到ListView中Item的映射, 僅查詢(xún)需要展示的數(shù)據(jù),但是查詢(xún)的過(guò)程是在UI線(xiàn)程中執(zhí)行。
  • SupportV7包中的AsyncListUtil支持基于position的數(shù)據(jù)集分頁(yè)加載到RecyclerView中,但不支持不基于position的數(shù)據(jù)集,而且它強(qiáng)制一個(gè)有限數(shù)據(jù)集中的null項(xiàng)必須展示Placeholder.

針對(duì)現(xiàn)有方案所存在的一些問(wèn)題,Google推出了Android架構(gòu)組件中的Paging Library, 不過(guò)目前還是alpha版本。Paging Library主要由3個(gè)部分組成:DataSource、PagedList、PagedListAdapter。

二、Paging Libray介紹

DataSource, PagedList, PagedAdapter三者之間的關(guān)系以及加載數(shù)據(jù)到展示數(shù)據(jù)的流程如下圖所示:

Paging Libraray Diagram

2.1 Datasource

顧名思義,Datasource<Key, Value>是數(shù)據(jù)源相關(guān)的類(lèi),其中Key對(duì)應(yīng)加載數(shù)據(jù)的條件信息,Value對(duì)應(yīng)返回結(jié)果, 針對(duì)不同場(chǎng)景,Paging提供了三種Datasource:

  • PageKeyedDataSource<Key, Value> :適用于目標(biāo)數(shù)據(jù)根據(jù)頁(yè)信息請(qǐng)求數(shù)據(jù)的場(chǎng)景,即Key 字段是頁(yè)相關(guān)的信息。比如請(qǐng)求的數(shù)據(jù)的參數(shù)中包含類(lèi)似next/previous的信息。
  • ItemKeyedDataSource<Key, Value> :適用于目標(biāo)數(shù)據(jù)的加載依賴(lài)特定item的信息, 即Key字段包含的是Item中的信息,比如需要根據(jù)第N項(xiàng)的信息加載第N+1項(xiàng)的數(shù)據(jù),傳參中需要傳入第N項(xiàng)的ID時(shí),該場(chǎng)景多出現(xiàn)于論壇類(lèi)應(yīng)用評(píng)論信息的請(qǐng)求。
  • PositionalDataSource<T>:適用于目標(biāo)數(shù)據(jù)總數(shù)固定,通過(guò)特定的位置加載數(shù)據(jù),這里Key是Integer類(lèi)型的位置信息,TValue。 比如從數(shù)據(jù)庫(kù)中的1200條開(kāi)始加在20條數(shù)據(jù)。

以上三種Datasource都是抽象類(lèi), 使用時(shí)需實(shí)現(xiàn)請(qǐng)求數(shù)據(jù)的方法。三種Datasource都需要實(shí)現(xiàn)loadInitial()方法, 各自都封裝了請(qǐng)求初始化數(shù)據(jù)的參數(shù)類(lèi)型LoadInitialParams。 不同的是分頁(yè)加載數(shù)據(jù)的方法,PageKeyedDataSourceItemKeyedDataSource比較相似, 需要實(shí)現(xiàn)loadBefore()loadAfter()方法,同樣對(duì)請(qǐng)求參數(shù)做了封裝,即LoadParams<Key>。PositionalDataSource需要實(shí)現(xiàn)loadRange(),參數(shù)的封裝類(lèi)為LoadRangeParams

如果項(xiàng)目中使用Android架構(gòu)組件中的Room, Room可以創(chuàng)建一個(gè)產(chǎn)出PositionalDataSourceDataSource.Factory

@Query("select * from users WHERE age > :age order by name DESC, id ASC")
DataSource.Factory<Integer, User> usersOlderThan(int age);

總的來(lái)說(shuō),Datasource就像是一個(gè)抽水泵,而不是真正的水源,它負(fù)責(zé)從數(shù)據(jù)源加載數(shù)據(jù),可以看成是Paging Library與數(shù)據(jù)源之間的接口。

2.2 PagedList

如果將Datasource比作抽水泵,那PagedList就像是一個(gè)蓄水池,但不僅僅如此。PagedList是List的子類(lèi),支持所有List的操作, 除此之外它主要有五個(gè)成員:

  • mMainThreadExecutor: 一個(gè)主線(xiàn)程的Excutor, 用于將結(jié)果post到主線(xiàn)程。

  • mBackgroundThreadExecutor: 后臺(tái)線(xiàn)程的Excutor.

  • BoundaryCallback:加載Datasource中的數(shù)據(jù)加載到邊界時(shí)的回調(diào).

  • Config: 配置PagedList從Datasource加載數(shù)據(jù)的方式, 其中包含以下屬性:

    • pageSize:設(shè)置每頁(yè)加載的數(shù)量
    • prefetchDistance:預(yù)加載的數(shù)量
    • initialLoadSizeHint:初始化數(shù)據(jù)時(shí)加載的數(shù)量
    • enablePlaceholders:當(dāng)item為null是否使用PlaceHolder展示
  • PagedStorage<T>: 用于存儲(chǔ)加載到的數(shù)據(jù),它是真正的蓄水池所在,它包含一個(gè)ArrayList<List<T>> 對(duì)象mPages,按頁(yè)存儲(chǔ)數(shù)據(jù)。

PagedList會(huì)從Datasource中加載數(shù)據(jù),更準(zhǔn)確的說(shuō)是通過(guò)Datasource加載數(shù)據(jù), 通過(guò)Config的配置,可以設(shè)置一次加載的數(shù)量以及預(yù)加載的數(shù)量。 除此之外,PagedList還可以向RecyclerView.Adapter發(fā)送更新的信號(hào),驅(qū)動(dòng)UI的刷新。

2.3 PagedListAdapter

PagedListAdapte是RecyclerView.Adapter的實(shí)現(xiàn),用于展示PagedList的數(shù)據(jù)。它本身實(shí)現(xiàn)的更多是Adapter的功能,但是它有一個(gè)小伙伴PagedListAdapterHelper<T>, PagedListAdapterHelper會(huì)負(fù)責(zé)監(jiān)聽(tīng)PagedList的更新, Item數(shù)量的統(tǒng)計(jì)等功能。這樣當(dāng)PagedList中新一頁(yè)的數(shù)據(jù)加載完成時(shí), PagedAdapte就會(huì)發(fā)出加載完成的信號(hào),通知RecyclerView刷新,這樣就省略了每次loading后手動(dòng)調(diào)一次notifyDataChanged().

除此之外,當(dāng)數(shù)據(jù)源變動(dòng)產(chǎn)生新的PagedList,PagedAdapter會(huì)在后臺(tái)線(xiàn)程中比較前后兩個(gè)PagedList的差異,然后調(diào)用notifyItem...()方法更新RecyclerView.這一過(guò)程依賴(lài)它的另一個(gè)小伙伴ListAdapterConfig, ListAdapterConfig負(fù)責(zé)主線(xiàn)程和后臺(tái)線(xiàn)程的調(diào)度以及DiffCallback的管理,DiffCallback的接口實(shí)現(xiàn)中定義比較的規(guī)則,比較的工作則是由PagedStorageDiffHelper來(lái)完成。

三、加載數(shù)據(jù)

使用Paging Library加載數(shù)據(jù)主要有兩種方式,一種是單一數(shù)據(jù)源的加載(本地?cái)?shù)據(jù)或網(wǎng)絡(luò)數(shù)據(jù)), 另一種是多個(gè)數(shù)據(jù)源的加載(本地?cái)?shù)據(jù)+網(wǎng)絡(luò)數(shù)據(jù))。

3.1 加載單一數(shù)據(jù)源的數(shù)據(jù)

首先我們可以通過(guò)LivePagedListBuilder來(lái)創(chuàng)建LiveData<PagedList>為UI層提供數(shù)據(jù)。整個(gè)流程如下圖所示:

single data source Diagram

如果數(shù)據(jù)源是DB,當(dāng)數(shù)據(jù)發(fā)生變化,DB會(huì)推送(push)一個(gè)新的PagedList(這里會(huì)依賴(lài)LiveData的機(jī)制). 如果是網(wǎng)絡(luò)數(shù)據(jù),即客戶(hù)端無(wú)法知道數(shù)據(jù)源的變化,可以通過(guò)諸如滑動(dòng)刷新的方式將調(diào)用Datasource的invalidate()方法來(lái)拉去(pull)新的數(shù)據(jù)。

3.2 加載多個(gè)數(shù)據(jù)源的數(shù)據(jù)

這種場(chǎng)景一般是先加載本地?cái)?shù)據(jù),加載完成后再加載網(wǎng)絡(luò)數(shù)據(jù),比較適合需要本地做緩存的業(yè)務(wù)。比如IM中的聊天消息,當(dāng)打開(kāi)聊天界面時(shí)先加載本地?cái)?shù)據(jù)庫(kù)中的聊天消息,加載完了再加載網(wǎng)絡(luò)的離線(xiàn)消息。這中場(chǎng)景的流程如下圖所示:


multi data source Diagram

這種場(chǎng)景需要為PagedList設(shè)置BoundaryCallback來(lái)監(jiān)聽(tīng)加載完本地?cái)?shù)據(jù)的事件,觸發(fā)加載網(wǎng)絡(luò)數(shù)據(jù),然后入庫(kù),此時(shí)LiveData<PagedList>會(huì)推送一個(gè)新的PagedList, 并觸發(fā)界面刷新。

具體使用案例可以參考Google Sample的PagingWithNetworkSample項(xiàng)目。

四、小結(jié)

Paging Library作為Android架構(gòu)組件庫(kù)的一員,其特點(diǎn)主要還是在其架構(gòu)思想上。Paging將分頁(yè)的業(yè)務(wù)封裝為一條完整的流水線(xiàn),一個(gè)Pattern。其中各個(gè)組件之間存在聯(lián)動(dòng)的關(guān)系:

  • 當(dāng)PagedList創(chuàng)建時(shí)會(huì)立即從Datasource加載數(shù)據(jù)(觸發(fā)loadInitial()), DataSource加載到數(shù)據(jù)后會(huì)更新PagedList, PagedList更新會(huì)通知到PagedAdapter并刷新UI;

  • UI上的展示會(huì)觸發(fā)PagedAdaptergetItem()隨即觸發(fā)PagedList的loadAround()方法從DataSource加載周?chē)臄?shù)據(jù)...

整個(gè)過(guò)程Paging內(nèi)部實(shí)現(xiàn)了線(xiàn)程的切換,數(shù)據(jù)的預(yù)加載,所有聯(lián)動(dòng)的關(guān)系都內(nèi)聚到Paging中,這樣使用時(shí)只需要關(guān)心加載數(shù)據(jù)的具體實(shí)現(xiàn),并且在用戶(hù)體驗(yàn)上,將會(huì)大大減少等待數(shù)據(jù)加載的時(shí)間和次數(shù)。

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

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

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