分页库使您的应用程序能够更轻松地根据需要从数据源加载信息,而不会使设备过载或等待太长的数据库查询时间。
在Model - build.gradle添加依赖
implementation "android.arch.paging:runtime:1.0.0-alpha7"
概观
许多应用程序可以处理大量数据,但只需要随时加载和显示一小部分数据。一个应用程序可能有数千个可能显示的项目,但它可能只需要一次访问几十个项目。如果应用程序不小心,它可能最终会请求它实际上不需要的数据,从而给设备和网络带来性能负担。如果数据与远程数据库存储或同步,则这也会降低应用速度并浪费用户的数据计划。
尽管现有的Android API允许在内容中进行分页,但它们带来了明显的限制和缺陷:
-
CursorAdapter
使得将数据库查询结果映射到ListView
项目变得更加容易,但它会在UI线程上运行数据库查询,并以低效的方式使用Cursor
。有关使用缺点的更多详细信息CursorAdapter
,请参阅博客文章Android上的 大型数据库查询。 -
AsyncListUtil
允许将基于位置的数据分页到a中RecyclerView
,但不允许进行非位置分页,并且强制空数据集中的空位占位符。
新的分页库解决了这些问题。该库包含几个类,以便在需要时简化请求数据的过程。这些类还可以与现有的体系结构组件(如Room)无缝 协作。
功能
分页库的一组类允许您完成本节中显示的任务。
定义如何获取数据
使用DataSource
该类来定义您需要从中提取分页数据的数据源。根据你需要访问你的数据的方式,你可以扩展它的一个子类:
- 使用
PageKeyedDataSource
如果页面在加载嵌入一个/上一个键。例如,如果您从网络中提取社交媒体帖子,则可能需要将下一个代码从一个加载传递到后续加载。 - 使用
ItemKeyedDataSource
,如果你需要使用的数据,从项目ñ获取项目N + 1。例如,如果您正在为讨论应用程序提取线索评论,则可能需要传递一条评论的标识以获取下一条评论的内容。 -
PositionalDataSource
如果您需要从您在数据存储中选择的任何位置获取数据页面,请使用此选项 。此类支持从您选择的任何位置开始请求一组数据项,如“返回从位置1200开始的20个数据项”。
如果您使用Room持久性库来管理数据,它可以生成一个 自动为您DataSource.Factory
生成实例 PositionalDataSource
。例如:
@Query("select * from users WHERE age > :age order by name DESC, id ASC")
DataSource.Factory<Integer, User> usersOlderThan(int age);
将数据加载到内存中
在PagedList
从一个类加载数据DataSource
。您可以配置一次加载多少数据,以及预取多少数据,从而最大限度地缩短用户等待数据加载的时间。这个类可以向其他类提供更新信号,例如RecyclerView.Adapter
,允许您RecyclerView
在页面中加载数据时更新您的内容。
根据您的应用程序的体系结构,您有几种使用PagedList
该类的选项 。要了解更多信息,请参阅选择数据加载体系结构。
在您的用户界面中呈现数据
该PagedListAdapter
班是的实现RecyclerView.Adapter
是从呈现的数据 PagedList
。例如,当被装载一个新的页面,该 PagedListAdapter
信号RecyclerView
,该数据已经到达; 这使得RecyclerView
用实际项目替换任何占位符,执行适当的动画。
该PagedListAdapter
还采用了后台线程从一个计算变化 PagedList
到下(例如,当数据库的变化产生一个新的 PagedList
与更新的数据),并调用notifyItem…()
根据需要更新列表中的内容方法。RecyclerView
然后执行必要的更改。例如,如果项目在PagedList
版本之间改变位置,则RecyclerView
将该项目移动到列表中的新位置。
观察数据更新
分页库提供以下类来构建 PagedList
能够实时更新的容器:
-
这个类 从 你提供的一个生成 。如果使用 Room持久性库来管理数据库,则DAO可以 使用以下示例中的示例为您 生成 :
LiveData
<
PagedList
>
DataSource.Factory
DataSource.Factory
PositionalDataSource
LiveData<PagedList<Item>> pagedItems = LivePagedListBuilder(myDataSource, /* page size */ 50) .setFetchExecutor(myNetworkExecutor) .build();
-
这个类提供了 类似于RxJava2的功能
LivePagedListBuilder
。该类由图书馆基于RxJava2的工件 android.arch.paging提供:rxjava2:1.0.0-alpha1。使用这个类,你可以在你的实现中构造Flowable
和Observable
实现对象PagedList
,如下面的代码片段所示:Flowable<PagedList<Item>> pagedItems = RxPagedListBuilder(myDataSource, /* page size */ 50) .setFetchScheduler(myNetworkScheduler) .buildFlowable(BackpressureStrategy.LATEST);
创建一个数据流
Paging Library的组件一起组织来自后台线程生成器的数据流,并在UI线程上呈现。例如,当一个新的项被插入在数据库中, DataSource
被无效,和 或 产生新的在后台线程。LiveData
PagedList
Flowable
PagedList
PagedList
新创建的内容PagedList
被发送到 PagedListAdapter
UI线程上。该 PagedListAdapter
然后使用DiffUtil
一个后台线程来计算当前列表和新列表之间的差异。比较结束后, PagedListAdapter
使用列表差异信息进行适当的调用以RecyclerView.Adapter.notifyItemInserted()
表示新项目已插入。
在RecyclerView
UI线程就知道它只有绑定一个新的项目,它的动画出现在屏幕上。
数据库示例
以下代码片段显示了将所有片段一起使用的几种可能方式。
使用LiveData观察分页数据
下面的代码片段显示了所有一起工作的部分。随着用户在数据库中的添加,删除或更改,其RecyclerView
内容会自动而有效地更新:
@Dao
interface UserDao {
// The Integer type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM user ORDER BY lastName ASC")
public abstract DataSource.Factory<Integer, User> usersByLastName();
}
class MyViewModel extends ViewModel {
public final LiveData<PagedList<User>> usersList;
public MyViewModel(UserDao userDao) {
usersList = new LivePagedListBuilder<>(
userDao.usersByLastName(), /* page size */ 20).build();
}
}
class MyActivity extends AppCompatActivity {
private UserAdapter<User> mAdapter;
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
RecyclerView recyclerView = findViewById(R.id.user_list);
mAdapter = new UserAdapter();
viewModel.usersList.observe(this, pagedList ->
mAdapter.submitList(pagedList));
recyclerView.setAdapter(mAdapter);
}
}
class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
public UserAdapter() {
super(DIFF_CALLBACK);
}
@Override
public void onBindViewHolder(UserViewHolder holder, int position) {
User user = getItem(position);
if (user != null) {
holder.bindTo(user);
} else {
// Null defines a placeholder item - PagedListAdapter will automatically invalidate
// this row when the actual object is loaded from the database
holder.clear();
}
}
public static final DiffUtil.ItemCallback<User> DIFF_CALLBACK = new DiffUtil.ItemCallback<User>() {
@Override
public boolean areItemsTheSame(@NonNull User oldUser, @NonNull User newUser) {
// User properties may have changed if reloaded from the DB, but ID is fixed
return oldUser.getId() == newUser.getId();
}
@Override
public boolean areContentsTheSame(@NonNull User oldUser, @NonNull User newUser) {
// NOTE: if you use equals, your object must properly override Object#equals()
// Incorrectly returning false here will result in too many animations.
return oldUser.equals(newUser);
}
};
}
使用RxJava2观察分页数据
如果您更喜欢使用 RxJava2而不是 LiveData
,则可以创建一个Observable
或一个Flowable
对象:
class MyViewModel extends ViewModel {
public final Flowable<PagedList<User>> usersList;
public MyViewModel(UserDao userDao) {
usersList = new RxPagedListBuilder<>(userDao.usersByLastName(),
/* page size */ 50).buildFlowable(BackpressureStrategy.LATEST);
}
}
然后,您可以使用以下代码片段中的代码开始和停止观察数据:
class MyActivity extends AppCompatActivity {
private UserAdapter<User> mAdapter;
private final CompositeDisposable mDisposable = new CompositeDisposable();
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
RecyclerView recyclerView = findViewById(R.id.user_list);
mAdapter = new UserAdapter();
recyclerView.setAdapter(mAdapter);
}
@Override
protected void onStart() {
super.onStart();
myDisposable.add(mViewModel.usersList.subscribe(flowableList ->
mAdapter.submitList(flowableList)));
}
@Override
protected void onStop() {
super.onStop();
mDisposable.clear();
}
}
基于RxJava2的解决方案的代码UserDao
与基于解决方案 的代码UserAdapter
相同 。LiveData
选择一个数据加载架构
寻呼数据库有两种主要的方法来寻呼数据:
网络或数据库
首先,您可以从单一来源进行分页 - 本地存储或网络。在这种情况下,使用a 将已加载的数据馈送到UI中,如上面的示例中所示。LiveData
<
PagedList
>
要指定您的数据来源,请传递DataSource.Factory
给LivePagedListBuilder
。
当观察数据库时,数据库在发生内容更改时将“推送”新的PagedList。在网络分页情况下(当后端不发送更新时),像刷卡到刷新这样的信号可以通过使当前数据源无效来“拉”新的分页列表。这将异步刷新所有数据。
PagingWithNetworkSample中的内存+网络存储库实现 显示了如何在处理刷新刷新,网络错误和重试时DataSource.Factory
使用Retrofit来实现网络 。
网络和数据库
在第二种情况下,您可以从本地存储页面进行寻址,本地存储页面本身会从网络中寻找其他数据。这通常是为了最大限度地减少网络负载并提供更好的低连接体验 - 数据库被用作存储在后端数据的缓存。
在这种情况下,用 从数据库页面的内容,并传递 到 观察出的数据,信号。LiveData
<
PagedList
>
BoundaryCallback
LivePagedListBuilder
然后将这些回调连接到网络请求,这些请求将直接将数据存储在数据库中。用户界面订阅了数据库更新,所以新的内容自动流向任何观察用户界面。
PagingWithNetworkSample 中的数据库+网络存储库 显示了如何BoundaryCallback
使用Retrofit实施网络,同时处理刷卡刷新,网络错误和重试。