(译) 分页库概览

原文链接

分页库可帮助你一次加载和显示小块数据。按需加载部分数据可减少网络带宽和系统资源的使用。

本指南提供了库的几个概念性示例,以及它如何工作的概述。要查看此库如何运行的完整示例,请尝试使用其它资源部分中的 codelab 和示例。

库架构

这部分描述并展示了分页库的主要组件。

分页列表

分页库的关键组件是 PagedList 类,它会加载应用程序数据或页面的块。由于需要更多数据,因此将其分页到现有的 PagedList 对象中。如果任何加载的数据发生更改,则会从 基于 LiveData 或 RxJava2 的对象向可观察数据持有者发出新的 PagedList 实例。生成 PagedList 对象时,应用程序的 UI 会显示其内容,同时尊重 UI 控制器的 Lifecycle。

以下代码段显示了如何使用 PagedList 对象的 LiveData 持有者配置应用程序的视图模型以加载和显示数据:

1
2
3
4
class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
val concertList: LiveData<PagedList<Concert>> =
concertDao.concertsByDate().toLiveData(pageSize = 50)
}

数据

PagedList 的每个实例都从其相应的 DataSource 对象加载应用程序数据的最新快照。数据从应用程序的后端或数据库流入 PagedList 对象。

以下示例使用 Room 持久化库来组织应用程序的数据,但如果要使用其他方法存储数据,还可以提供自己的数据源工厂。

1
2
3
4
5
6
@Dao
interface ConcertDao {
// The Int type parameter tells Room to use a PositionalDataSource object.
@Query("SELECT * FROM concerts ORDER BY date DESC")
fun concertsByDate(): DataSource.Factory<Int, Concert>
}

要了解有关如何将数据加载到 PagedList 对象的更多信息,请参阅有关如何加载分页数据的指南。

用户界面

PagedList 类使用 PagedListAdapter 将项目加载到 RecyclerView 中。这些类一起工作以在加载内容时获取和显示内容,预取视图内容并动画内容更改。

有关详细信息,请参阅有关如何显示分页列表的指南。

支持不同的数据架构

分页库支持以下数据架构:

  • 仅从后端服务器提供。
  • 仅存储在设备上的数据库中。
  • 使用设备上数据库作为缓存的其他源的组合。
    图1显示了每种架构方案中数据的流动方式。对于仅限网络或仅限数据库的解决方案,数据直接流向应用程序的 UI 模型。如果你使用的是组合方法,则数据会从后端服务器流入设备上的数据库,然后流入应用程序的 UI 模型。每隔一段时间,每个数据流的端点就会耗尽要加载的数据,此时它会从提供数据的组件请求更多数据。例如,当设备上数据库用完数据时,它会从服务器请求更多数据。

图1.数据如何流经分页库支持的每个体系结构
本节的其余部分提供了配置每个数据流用例的建议。

仅网络

要显示来自后端服务器的数据,请使用 Retrofit API 的同步版本将信息加载到你自己的自定义 DataSource 对象中

注意:分页库的 DataSource 对象不提供任何错误处理,因为不同的应用程序以不同的方式处理和显示错误 UI。如果发生错误,请遵循结果回调,稍后重试该请求。有关此行为的示例,请参阅PagingWithNetwork 示例

仅数据库

设置 RecyclerView 以观察本地存储,最好使用 Room 持久化库。这样,无论何时在应用程序的数据库中插入或修改数据,这些更改都会自动反映在显示此数据的 RecyclerView 中。

网络和数据库

在开始观察数据库之后,你可以使用 PagedList.BoundaryCallback 监听数据库何时没有数据。然后,你可以从网络中获取更多项目并将其插入数据库。如果你的 UI 正在观察数据库,那就是你需要做的。

处理网络错误

当使用网络获取或分页你正在使用分页库显示的数据时,重要的是不要将网络视为“可用”或“不可用”,因为许多连接是间歇性的或片状的:

  • 特定服务器可能无法响应网络请求。
  • 设备可能连接到缓慢或弱的网络。

相反,你的应用应检查每个失败请求,并在网络不可用的情况下尽可能优雅地恢复。例如,你可以提供“重试”按钮,供用户选择数据刷新步骤是否不起作用。如果在数据分页步骤期间发生错误,则最好自动重试分页请求。

修改已有的应用

如果你的应用已经使用了数据库或后端源中的数据,则可以直接升级到分页库提供的功能。本节介绍如何升级具有通用现有设计的应用程序。

定制分页解决方案

如果使用自定义功能从应用程序的数据源加载小的数据子集,则可以将此逻辑替换为 PagedList 类中的逻辑。PagedList 的实例提供与公共数据源的内置连接。这些实例还为您可能包含在应用程序 UI 中的 RecyclerView 对象提供适配器。

使用列表而不是页面加载数据

如果使用内存列表作为 UI 适配器的后备数据结构,请考虑如果列表中的项目数量变大,则使用PagedList 类观察数据更新。 PagedList 的实例可以使用 LiveData或Observable 将数据更新传递到应用程序的 UI,从而最大限度地减少加载时间和内存使用量。更好的是,在应用程序中用 PagedList 对象替换 List 对象不需要对应用程序的UI结构或数据更新逻辑进行任何更改。

使用 CursorAdapter 将数据光标与列表视图相关联

你的应用程序可能使用 CursorAdapter 将 Cursor 中的数据与 ListView 相关联。在这种情况下,你通常需要从 ListView 迁移到 RecyclerView 作为应用程序的列表UI容器,然后将Cursor 组件替换为 Room 或 PositionalDataSource,具体取决于 Cursor 实例是否访问SQLite 数据库。

在某些情况下,例如使用 Spinner 实例时,只提供适配器本身。然后,库将获取加载到该适配器中的数据并为你显示数据。在这些情况下,将适配器数据的类型更改为 LiveData ,然后在尝试让库类在 UI 中对这些项进行充气之前,将此列表包装在ArrayAdapter 对象中。

使用 AsyncListUtil 异步加载内容

如果你使用 AsyncListUtil 对象异步加载和显示信息组,则分页库可让你更轻松地加载数据:

  • 你的数据不需要是位置的。分页库允许您使用网络提供的密钥直接从后端加载数据。
  • 你的数据可能非常大。使用分页库,您可以将数据加载到页面中,直到没有剩余数据。
  • 你可以更轻松地观察数据。 Paging 库可以显示应用程序的 ViewModel 在可观察数据结构中保存的数据。

    注意:如果现有应用程序访问 SQLite 数据库,请参阅有关使用 Room 持久化库的部分。

数据库示例

以下代码片段显示了使所有部分协同工作的几种可能方法。

使用 LiveData 观察分页数据
以下代码段显示了一起工作的所有部分。随着在数据库中添加,删除或更改 Concert 事件,RecyclerView 中的内容将自动且高效地更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@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 concertDao;
public final LiveData<PagedList<Concert>> concertList;

public ConcertViewModel(ConcertDao concertDao) {
this.concertDao = concertDao;
concertList = new LivePagedListBuilder<>(
concertDao.concertsByDate(), /* page size */ 50).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 观察分页数据

如果你更喜欢使用 RxJava2 而不是 LiveData,则可以创建一个 Observable 或 Flowable对象:

1
2
3
4
5
6
7
8
9
10
11
12
public class ConcertViewModel extends ViewModel {
private ConcertDao concertDao;
public final Observable<PagedList<Concert>> concertList;

public ConcertViewModel(ConcertDao concertDao) {
this.concertDao = concertDao;

concertList = new RxPagedListBuilder<>(
concertDao.concertsByDate(), /* page size */ 50)
.buildObservable();
}
}

然后,你可以使用以下代码段中的代码开始和停止观察数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ConcertActivity extends AppCompatActivity {
private ConcertAdapter adapter = new ConcertAdapter();
private ConcertViewModel viewModel;

private CompositeDisposable disposable = new CompositeDisposable();

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RecyclerView recyclerView = findViewById(R.id.concert_list);

viewModel = ViewModelProviders.of(this).get(ConcertViewModel.class);
recyclerView.setAdapter(adapter);
}

@Override
protected void onStart() {
super.onStart();
disposable.add(viewModel.concertList
.subscribe(adapter.submitList(flowableList)
));
}

@Override
protected void onStop() {
super.onStop();
disposable.clear();
}
}

对于基于 RxJava2 的解决方案,ConcertDao 和 ConcertAdapter 的代码与基于 LiveData 的解决方案的代码相同。

非典型前端coder wechat
想要随时Follow我的最新博客,可扫码关注我的公众号