分页库使您的应用程序更容易从数据源逐步加载所需的信息,而不会使设备过载或等待太长的时间来处理大型数据库查询。
1.概述
许多应用程序使用大量数据,但只需要随时加载和显示一小部分数据。一个应用程序可能有数千个可能显示的项目,但是它可能只需要一次访问几十个项目。如果应用程序不小心,它可能最终会请求实际上不需要的数据,从而给设备和网络带来性能负担。如果数据被存储或与远程数据库同步,则这也会减慢应用程序并浪费用户的数据计划。
虽然现有的Android API允许在内容中进行分页,但它们带来了明显的限制和缺陷:
- CursorAdapter使得将数据库查询的结果映射到[ListView]条目上(https://developer.android.google.cn/reference/android/widget/ListView.html)变得更加容易,但是它在UI线程上运行数据库查询,并且使用Cursor低效地分页内容。有关使用的缺点的更多详细信息CursorAdapter,请参阅 Android上的大型数据库查询博客文章。
- AsyncListUtil允许将基于位置的数据分页到RecyclerView中,但是不允许非定位分页,并且强制空占位符在可数数据集中的。
新的分页库解决了这些问题。这个库包含几个类,以便在需要时简化请求数据的过程。这些类也可以与现有的体系结构组件无缝协作(如Room)。
2.类
分页库提供了以下类,以及其他支持类:
使用这个类来定义你需要从中提取分页数据的数据源。根据您需要访问数据的方式,您可以扩展其两个子类之一:
- 使用 PageKeyedDataSource , 如果加载页面嵌入了
next
/previous
键。例如,如果您从网络中提取社交媒体帖子,则可能需要传递一个nextPage
令牌从一个加载传递到后续加载。 - 使用 ItemKeyedDataSource ,如果你需要使用从N到N + 1的
item
数据。例如,如果您要为讨论应用程序中提取嵌套评论,则可能需要传递一个评论的ID
以获取下一条评论的内容。 - PositionalDataSource,如果您需要在数据存储中从您选择的任何位置处获取分页数据,请使用此选项。这个类支持从你选择的任何位置开始请求一组数据项,如“返回从位置1200开始的20个数据项”。
如果你使用Room持久性库来管理你的数据,它可以生成一个DataSource.Factory 为您自动生产PositionalDataSources ,例如:
@Query("select * from users WHERE age > :age order by name DESC, id ASC")
DataSource.Factory<Integer, User> usersOlderThan(int age);
这个类从DataSource中加载数据。您可以配置一次加载多少数据,以及应该预取多少数据,从而最大限度地缩短用户等待数据加载的时间。这个类可以向其他类提供更新信号例如RecyclerView.Adapter,允许您的RecyclerView在页面中加载数据时更新您的内容。
这个类是RecyclerView.Adapter的一个实现,它代表了来自 PagedList的数据。例如,当加载新页面时,PagedListAdapter通知RecyclerView数据已经到达的, 这使得RecyclerView用实际项目替换占位符,同时执行适当的动画。
PagedListAdapter 还采用了后台线程计算从一个 PagedList到下(例如,数据库的变化,更新的数据产生一个新的PagedList)的变化,并调用notifyItem…()根据需要更新列表中的内容方法。RecyclerView然后执行必要的更改。例如,如果item
在PagedList版本之间改变位置,则RecyclerView将该项目移动到列表中的新位置。
这个类从你提供的DataSource.Factory中生成LiveData <PagedList> 。此外,如果您使用Room持久性库来管理数据库,则DAO可以使用PositionalDataSource为您生成DataSource.Factory ,列如:
@Query("SELECT * from users order WHERE age > :age order by name DESC,
id ASC")
public abstract LivePagedListProvider<Integer, User> usersOlderThan(int age);
该Integer
参数告诉Room使用基于位置加载的PositionalDataSource 。
Paging Library的组件一起组织来自后台线程生成器的数据流,并在UI线程上呈现。例如,在数据库中插入新项目时, DataSource失效,LiveData< PagedList >在后台线程上生成一个新PagedList。
图1.分页库组件在后台线程中完成大部分工作,所以它们不会给UI线程造成负担。新创建的PagedList在UI线程上被发送到 PagedListAdapter 。然后PagedListAdapter 使用DiffUtil在后台线程来计算当前列表和新列表之间的差异。当比较结束时, PagedListAdapter 使用列表差异信息作出适当的对RecyclerView.Adapter.notifyItemInserted()的调用,以通知一个新的项目被插入。
然后RecyclerView在UI线程上知道它只需绑定一个新的项目,并将其动画显示在屏幕上。
3. 数据库示例
下面的代码示例显示了一起工作的所有部分。随着用户在数据库中的添加,删除或更改,RecyclerView内容会自动而有效地更新:
@Dao
interface UserDao {
@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 = LivePagedListBuilder<>(
userDao.usersByLastName(), /* page size */ 20).build();
}
}
class MyActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
RecyclerView recyclerView = findViewById(R.id.user_list);
UserAdapter<User> adapter = new UserAdapter();
viewModel.usersList.observe(this, pagedList -> adapter.setList(pagedList));
recyclerView.setAdapter(adapter);
}
}
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 DiffCallback<User> DIFF_CALLBACK = new DiffCallback<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);
}
}
}
4. 加载数据
使用分页库有两种主要的方法来请求数据:
4.1网络或数据库
首先,您可以从单一来源进行分页 - 本地存储或网络。在这种情况下,使用LiveData<PagedList>将已加载的数据馈送到UI中,如上面的示例中所示。
要指定您的数据来源,请传递DataSource.Factory给LivePagedListBuilder。
观察数据库时,数据库将在内容发生变化时“推送”新的PagedList。在网络分页的情况下(当后端不发送更新时),诸如刷新之类的信号可以通过使当前数据源无效来“拉”新的分页列表。这将异步刷新所有数据。
PagingWithNetworkSample中的内存+网络库实现,显示了如何通过实现网络DataSource.Factory结合Retrofit来处理刷新,网络错误和重试。
4.2 网络和数据库
在第二种情况下,您可以从本地存储进行分页,本地存储数据本身来自网络,这通常是为了尽量减少网络负载,并提供更好的低连接体验 - 数据库被用作存储在后端的数据缓存。
在这种情况下,用LiveData<PagedList>分页来自数据库的内容,并传递 BoundaryCallback给LivePagedListBuilder发出数据不足的信号。
图3.数据库是网络数据的缓存 - UI从数据库加载数据,并在数据不足时发送信号从网络加载数据到数据库.然后将这些回调连接到网络请求,这些请求将直接将数据存储在数据库中。用户界面订阅数据库更新,所以新的内容自动流向任何观察用户界面。
PagingWithNetworkSample 中的数据库+网络,显示了在刷新,网络错误和重试时,如何使用Retrofit实现网络BoundaryCallback 。
网友评论