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的映射, 僅查詢需要展示的數(shù)據(jù)幔妨,但是查詢的過(guò)程是在UI線程中執(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煞额、PagedListPagedListAdapter沾谜。

二膊毁、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è)主線程的Excutor, 用于將結(jié)果post到主線程。

  • mBackgroundThreadExecutor: 后臺(tái)線程的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)線程中比較前后兩個(gè)PagedList的差異疾捍,然后調(diào)用notifyItem...()方法更新RecyclerView.這一過(guò)程依賴(lài)它的另一個(gè)小伙伴ListAdapterConfig奈辰, ListAdapterConfig負(fù)責(zé)主線程和后臺(tái)線程的調(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ù)岩榆,即客戶端無(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ò)的離線消息。這中場(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ù)封裝為一條完整的流水線,一個(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)了線程的切換谨敛,數(shù)據(jù)的預(yù)加載,所有聯(lián)動(dòng)的關(guān)系都內(nèi)聚到Paging中滤否,這樣使用時(shí)只需要關(guān)心加載數(shù)據(jù)的具體實(shí)現(xiàn)脸狸,并且在用戶體驗(yàn)上,將會(huì)大大減少等待數(shù)據(jù)加載的時(shí)間和次數(shù)藐俺。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末炊甲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子欲芹,更是在濱河造成了極大的恐慌卿啡,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菱父,死亡現(xiàn)場(chǎng)離奇詭異颈娜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)浙宜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)揭鳞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人梆奈,你說(shuō)我怎么就攤上這事〕瓶” “怎么了亩钟?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鳖轰。 經(jīng)常有香客問(wèn)我清酥,道長(zhǎng),這世上最難降的妖魔是什么蕴侣? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任焰轻,我火速辦了婚禮,結(jié)果婚禮上昆雀,老公的妹妹穿的比我還像新娘辱志。我一直安慰自己,他們只是感情好狞膘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布揩懒。 她就那樣靜靜地躺著,像睡著了一般挽封。 火紅的嫁衣襯著肌膚如雪已球。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音智亮,去河邊找鬼忆某。 笑死,一個(gè)胖子當(dāng)著我的面吹牛阔蛉,可吹牛的內(nèi)容都是我干的弃舒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼馍忽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棒坏!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起遭笋,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坝冕,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后瓦呼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體喂窟,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年央串,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磨澡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡质和,死狀恐怖稳摄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饲宿,我是刑警寧澤厦酬,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站瘫想,受9級(jí)特大地震影響仗阅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜国夜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一减噪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧车吹,春花似錦筹裕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至馒吴,卻和暖如春扎运,著一層夾襖步出監(jiān)牢的瞬間瑟曲,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工豪治, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洞拨,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓负拟,卻偏偏與公主長(zhǎng)得像烦衣,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子掩浙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容