分頁庫使您可以更輕松地在應(yīng)用程序的RecyclerView中逐步和優(yōu)雅地加載數(shù)據(jù)。
許多應(yīng)用程序使用包含大量項目的數(shù)據(jù)源中的數(shù)據(jù)屁奏,但一次只顯示一小部分。
分頁庫可幫助您的應(yīng)用觀察并顯示此數(shù)據(jù)的合理子集错负。 此功能有幾個優(yōu)點:
- 數(shù)據(jù)請求消耗的網(wǎng)絡(luò)帶寬更少坟瓢,系統(tǒng)資源更少。 擁有計量或小數(shù)據(jù)計劃的用戶會欣賞這些具有數(shù)據(jù)意識的應(yīng)用程序犹撒。
- 即使在數(shù)據(jù)更新和刷新期間折联,應(yīng)用程序仍會繼續(xù)快速響應(yīng)用戶輸入。
如果您的應(yīng)用已包含分頁數(shù)據(jù)和顯示列表的邏輯油航,我們就如何更新現(xiàn)有應(yīng)用提供了指導(dǎo)崭庸。
本指南概述了如何使用分頁庫來請求和顯示用戶希望在更經(jīng)濟地消耗系統(tǒng)資源時看到的數(shù)據(jù)怀浆。 有關(guān)特定于應(yīng)用程序體系結(jié)構(gòu)層的指南谊囚,請參閱以下相關(guān)頁面:
- UI組件和注意事項
- 數(shù)據(jù)組件和注意事項
注意:無論您是僅使用設(shè)備內(nèi)部數(shù)據(jù)庫還是從應(yīng)用程序的后端獲取信息,分頁庫都可以幫助您順利地在UI列表容器中顯示數(shù)據(jù)执赡。
一镰踏、庫架構(gòu)
Paging Library的關(guān)鍵組件是PagedList
類,它是一個異步加載應(yīng)用程序數(shù)據(jù)塊或頁面的集合沙合。 此類充當(dāng)應(yīng)用程序架構(gòu)的其他部分之間的中介:
Data
PagedList的每個實例都從其DataSource加載應(yīng)用程序數(shù)據(jù)的最新快照奠伪。 數(shù)據(jù)從應(yīng)用程序的后端或數(shù)據(jù)庫流入PagedList對象。
分頁庫支持各種應(yīng)用程序體系結(jié)構(gòu)首懈,包括獨立數(shù)據(jù)庫和與后端服務(wù)器通信的數(shù)據(jù)庫绊率。
UI
PagedList類與PagedListAdapter一起使用,可以將項目加載到RecyclerView中究履。 這些類一起工作以在加載內(nèi)容時獲取和顯示內(nèi)容滤否,預(yù)取視圖外內(nèi)容并動畫內(nèi)容更改。
分頁庫實現(xiàn)了應(yīng)用程序體系結(jié)構(gòu)指南中建議的觀察者模式最仑。 特別是藐俺,庫的核心組件創(chuàng)建了一個UI可以觀察的LiveData <PagedList>(或等效的基于RxJava2的類)的實例炊甲。 然后,您的應(yīng)用程序的UI可以在生成PagedList對象時顯示內(nèi)容欲芹,同時尊重UI控制器的生命周期卿啡。
二、支持不同的數(shù)據(jù)架構(gòu)
分頁庫支持應(yīng)用程序體系結(jié)構(gòu)菱父,包括應(yīng)用程序僅從后端服務(wù)器獲取數(shù)據(jù)颈娜,僅從設(shè)備上數(shù)據(jù)庫或兩個源的組合獲取數(shù)據(jù)的應(yīng)用程序體系結(jié)構(gòu)。 本節(jié)為每種情況提供建議浙宜。
我們提供了用于不同數(shù)據(jù)架構(gòu)的推薦模式的示例揭鳞。 要查看它們,請參閱GitHub上的PagingWithNetwork示例梆奈。
僅限網(wǎng)絡(luò)
要顯示來自后端服務(wù)器的數(shù)據(jù)野崇,請使用Retrofit API的同步版本將信息加載到您自己的自定義DataSource對象中。
僅數(shù)據(jù)庫
設(shè)置RecyclerView以觀察本地存儲亩钟,最好使用Room persistence library乓梨。 這樣,無論何時在應(yīng)用程序的數(shù)據(jù)庫中插入或修改數(shù)據(jù)清酥,這些更改都會自動反映在顯示此數(shù)據(jù)的RecyclerView中扶镀。
網(wǎng)絡(luò)和數(shù)據(jù)庫
在開始觀察數(shù)據(jù)庫之后,可以使用PagedList.BoundaryCallback監(jiān)聽數(shù)據(jù)庫何時沒有數(shù)據(jù)焰轻。 然后臭觉,您可以從網(wǎng)絡(luò)中獲取更多項目并將其插入數(shù)據(jù)庫。 如果您的UI正在觀察數(shù)據(jù)庫辱志,那就是您需要做的蝠筑。
以下代碼段顯示了邊界回調(diào)的示例用法:
public class ConcertViewModel {
public ConcertSearchResult search(String query) {
ConcertBoundaryCallback boundaryCallback =
new ConcertBoundaryCallback(query, myService, myCache);
// Use a LiveData object to communicate your network's state back
// to your app's UI, as in the following example. Note that error
// handling isn't shown in this snippet.
// LiveData<NetworkState> loadingState =
// boundaryCallback.getLoadingState();
}
}
public class ConcertBoundaryCallback
extends PagedList.BoundaryCallback<Concert> {
private String mQuery;
private MyService mService;
private MyLocalCache mCache;
public ConcertBoundaryCallback(String query, MyService service,
MyLocalCache cache) {
mQuery = query;
// ...
}
// Requests initial data from the network, replacing all content currently
// in the database.
@Override
public void onZeroItemsLoaded() {
requestAndReplaceInitialData(mQuery);
}
// Requests additional data from the network, appending the results to the
// end of the database's existing data.
@Override
public void onItemAtEndLoaded(@NonNull Concert itemAtEnd) {
requestAndAppendData(mQuery, itemAtEnd.key);
}
}
三、處理網(wǎng)絡(luò)錯誤
當(dāng)使用網(wǎng)絡(luò)獲取或分頁您正在使用分頁庫顯示的數(shù)據(jù)時揩懒,重要的是不要將網(wǎng)絡(luò)視為“可用”或“不可用”什乙,因為許多連接是間歇性的或片狀的:
- 特定服務(wù)器可能無法響應(yīng)網(wǎng)絡(luò)請求。
- 設(shè)備可能連接到緩慢或弱的網(wǎng)絡(luò)已球。
相反臣镣,您的應(yīng)用應(yīng)檢查每個失敗請求,并在網(wǎng)絡(luò)不可用的情況下盡可能優(yōu)雅地恢復(fù)智亮。 例如忆某,您可以提供“重試”按鈕,供用戶選擇數(shù)據(jù)刷新步驟是否不起作用阔蛉。 如果在數(shù)據(jù)分頁步驟期間發(fā)生錯誤弃舒,則最好自動重試分頁請求。
四馍忽、更新現(xiàn)有應(yīng)用
如果您的應(yīng)用已經(jīng)消耗了數(shù)據(jù)庫或后端源中的數(shù)據(jù)棒坏,則可以直接升級到分頁庫提供的功能燕差。 本節(jié)介紹如何升級具有通用現(xiàn)有設(shè)計的應(yīng)用程序。
定制分頁解決方案
如果使用自定義功能從應(yīng)用程序的數(shù)據(jù)源加載小的數(shù)據(jù)子集坝冕,則可以將此邏輯替換為PagedList類中的邏輯徒探。 PagedList的實例提供與公共數(shù)據(jù)源的內(nèi)置連接。 這些實例還為您可能包含在應(yīng)用程序UI中的RecyclerView對象提供適配器喂窟。
使用列表而不是頁面加載數(shù)據(jù)
如果使用內(nèi)存列表作為UI適配器的后備數(shù)據(jù)結(jié)構(gòu)测暗,請考慮如果列表中的項目數(shù)可能變大,則使用PagedList類觀察數(shù)據(jù)更新磨澡。 PagedList的實例可以使用LiveData <PagedList>或Observable <List>將數(shù)據(jù)更新傳遞到應(yīng)用程序的UI碗啄,從而最大限度地減少加載時間和內(nèi)存使用量。 更好的是稳摄,在應(yīng)用程序中用PagedList對象替換List對象不需要對應(yīng)用程序的UI結(jié)構(gòu)或數(shù)據(jù)更新邏輯進行任何更改稚字。
使用CursorAdapter將數(shù)據(jù)光標(biāo)與列表視圖相關(guān)聯(lián)
您的應(yīng)用程序可能使用CursorAdapter將Cursor中的數(shù)據(jù)與ListView相關(guān)聯(lián)。 在這種情況下厦酬,您通常需要從ListView遷移到RecyclerView作為應(yīng)用程序的列表UI容器胆描,然后將Cursor組件替換為Room或PositionalDataSource,具體取決于Cursor實例是否訪問SQLite數(shù)據(jù)庫仗阅。
在某些情況下昌讲,例如在處理Spinner實例時,您只提供適配器本身减噪。 然后短绸,庫將獲取加載到該適配器中的數(shù)據(jù)并為您顯示數(shù)據(jù)。 在這些情況下筹裕,將適配器數(shù)據(jù)的類型更改為LiveData <PagedList>醋闭,然后在嘗試讓庫類在UI中對這些項進行膨脹之前,將此列表包裝在ArrayAdapter對象中饶碘。
使用AsyncListUtil異步加載內(nèi)容
如果您使用AsyncListUtil對象異步加載和顯示信息組目尖,則分頁庫可讓您更輕松地加載數(shù)據(jù):
- 您的數(shù)據(jù)不需要是位置的。 分頁庫允許您使用網(wǎng)絡(luò)提供的密鑰直接從后端加載數(shù)據(jù)扎运。
- 您的數(shù)據(jù)可能非常大。 使用分頁庫饮戳,您可以將數(shù)據(jù)加載到頁面中豪治,直到?jīng)]有剩余數(shù)據(jù)。
- 您可以更輕松地觀察數(shù)據(jù)扯罐。 Paging庫可以顯示應(yīng)用程序的ViewModel在可觀察數(shù)據(jù)結(jié)構(gòu)中保存的數(shù)據(jù)负拟。
五、數(shù)據(jù)庫示例
以下代碼片段顯示了將所有部分協(xié)同工作的幾種可能方法歹河。
使用LiveData觀察分頁數(shù)據(jù)
以下代碼段顯示了所有協(xié)同工作掩浙。 隨著在數(shù)據(jù)庫中添加花吟,刪除或更改音樂會事件,RecyclerView中的內(nèi)容將自動且有效地更新:
@Dao
public interface ConcertDao {
// The Integer type parameter tells Room to use a PositionalDataSource
// object, with position-based loading under the hood.
@Query("SELECT * FROM concerts ORDER BY date DESC")
DataSource.Factory<Integer, Concert> concertsByDate();
}
public class ConcertViewModel extends ViewModel {
private ConcertDao mConcertDao;
public final LiveData<PagedList<Concert>> concertList;
public ConcertViewModel(ConcertDao concertDao) {
mConcertDao = concertDao;
concertList = new LivePagedListBuilder<>(
mConcertDao.concertsByDate(), /* page size */ 20).build();
}
}
public class ConcertActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ConcertViewModel viewModel =
ViewModelProviders.of(this).get(ConcertViewModel.class);
RecyclerView recyclerView = findViewById(R.id.concert_list);
ConcertAdapter adapter = new ConcertAdapter();
viewModel.concertList.observe(this, adapter::submitList);
recyclerView.setAdapter(adapter);
}
}
public class ConcertAdapter
extends PagedListAdapter<Concert, ConcertViewHolder> {
protected ConcertAdapter() {
super(DIFF_CALLBACK);
}
@Override
public void onBindViewHolder(@NonNull ConcertViewHolder holder,
int position) {
Concert concert = getItem(position);
if (concert != null) {
holder.bindTo(concert);
} else {
// Null defines a placeholder item - PagedListAdapter automatically
// invalidates this row when the actual object is loaded from the
// database.
holder.clear();
}
}
private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =
new DiffUtil.ItemCallback<Concert>() {
// Concert details may have changed if reloaded from the database,
// but ID is fixed.
@Override
public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {
return oldConcert.getId() == newConcert.getId();
}
@Override
public boolean areContentsTheSame(Concert oldConcert,
Concert newConcert) {
return oldConcert.equals(newConcert);
}
};
}
使用RxJava2觀察分頁數(shù)據(jù)
如果您更喜歡使用RxJava2而不是LiveData厨姚,則可以創(chuàng)建一個Observable或Flowable對象:
public class ConcertViewModel extends ViewModel {
private ConcertDao mConcertDao;
public final Flowable<PagedList<Concert>> concertList;
public ConcertViewModel(ConcertDao concertDao) {
mConcertDao = concertDao;
concertList = new RxPagedListBuilder<>(
mConcertDao.concertsByDate(), /* page size */ 50)
.buildFlowable(BackpressureStrategy.LATEST);
}
}
然后衅澈,您可以使用以下代碼段中的代碼開始和停止觀察數(shù)據(jù):
public class ConcertActivity extends AppCompatActivity {
private ConcertAdapter mAdapter;
private ConcertViewModel mViewModel;
private CompositeDisposable mDisposable = new CompositeDisposable();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RecyclerView recyclerView = findViewById(R.id.concert_list);
mViewModel = ViewModelProviders.of(this).get(ConcertViewModel.class);
mAdapter = new ConcertAdapter();
recyclerView.setAdapter(mAdapter);
}
@Override
protected void onStart() {
super.onStart();
mDisposable.add(mViewModel.concertList.subscribe(
flowableList -> mAdapter.submitList(flowableList)
));
}
@Override
protected void onStop() {
super.onStop();
mDisposable.clear();
}
}
對于基于RxJava2的解決方案,ConcertDao和ConcertAdapter的代碼與基于LiveData的解決方案的代碼相同谬墙。