Android Paging library详解(一)

2024-06-19 11:18
文章标签 android 详解 library paging

本文主要是介绍Android Paging library详解(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

官方文档翻译

文章目录

  • 1.概览
    • 1.1 库架构
    • 1.2 支持不同的数据架构
      • 1.2.1 网络获取或者数据库
      • 1.2.2 网络和数据库同时获取
    • 1.2.3 处理网络错误
    • 1.2.4 更新现有App
    • 1.2.4.1 自定义分页解决
    • 1.2.4.2 使用paging作为数据加载
    • 1.2.4.3 使用CursorAdapter加载数据
    • 1.2.4.4 使用AsyncListUtil异步加载数据
    • 1.2.4.5 数据库实例
  • 2.现有分页库
  • 3.Paging用户指南
    • 3.1 加入工程
    • 3.2 UI组件以及相关事项
      • 3.2.1 将UI和View Modle建立联系
      • 3.2.2 实现 diffing callback
      • 3.2.3 在你的UI中提供占位
    • 3.3 数据组件以及相关事项
      • 3.3.1 构建一个 observable的列表
      • 3.3.2 定义你自己的paging配置
    • 3.3.3 选择正确的数据源类型
    • 3.3.4 数据刷新
    • 3.3.5 构建你自己的数据源
      • 3.3.6 考虑内容如何更新
      • 3.3.7 提供数据表示的映射
    • 3.3.8 PaingList将数据加载进内存
    • 3.4 Paging数据加载原理图

1.概览

Paging 使您的应用程序配合RecyclerView更容易从数据源中高效优雅地加载所需的数据,不会因为数据库数据量大而造成查询时间过长。

在我们的实际项目中有大量的数据,但是我们常常只需要向用户展示一小部分信息。例如,一个应用中可能会有几千个item但是我们常常只需要显示十几个或者几十个。我们处理不当加载数据时可能会造成APP崩溃。如果数据被存储或与远程数据库同步,这也会减慢应用程序的速度,影响用户的体验。

Paging库能够提供给你的app观察和显示这个数据合理的子集。这个功能库有几个优点:

  1. 数据请求占用比较少的网络带宽和系统资源。
  2. 在数据更新和刷新期间,App也能够快速响应用户输入。
    如果你的app已经包含了分页处理的逻辑,我们已经提供了更新现有app的指导。

1.1 库架构

分页库关键组件是PagedList类,该类异步加载你的app需要的数据以及分页数据。这个类为你app的其他架构组件提供了联系。

Data

每个PagingList实例从DataSource为你的App提供最新的数据。数据流从你的app后台或者数据库流向PagingList对象。

分页库支持支持很多app架构,包括独立数据库或者服务器网络数据。

了解更多,请看Data组件以及相关事项。

UI

PagingList类和PagingListAdapter配合使用,加载items数据到RecyclerView中。这些类一起使用来获取和显示已经加载的数据,预加载以及观察数据变化并自动更新。

了解更多,请看UI组件以及相关事项。

分页库实现了观察者模式。实际上,这些组件会创建一个LiveData<PagedList>数据对象(或者Rxjava2基础类的可观察数据对象)你的app UI就可以将这些数据从PagingList中展示,同时具有可控的生命周期。

1.2 支持不同的数据架构

分页库支持三种数据获取:仅从网络获取;仅从本地数据库获取;从网络和数据库同时获取。

相关的示例代码可以参考 “PagingWithNetworkSample” 以及 PagingSample on GitHub

1.2.1 网络获取或者数据库

首先页面加载的数据来自于网络和数据库二者之一。如上所示,使用LiveData将数据加载进UI中。需要指定一个元数据传递DataSource.Factory至LivePagedListBuilder。

image

Figure 2.DataSource提供一个单一的元数据,Factory加载内容。

数据库获取时, 观察一个数据库时,数据库内容发生改变数据库就会pull一个新的PagedList。
RecyclerView加载本地数据,可以使用Room库。通过这种方式,当向数据库中添加、修改、删除数据,这些变化都会被观察到,从而触发UI自动刷新。

网络获取时, 下拉刷新加载网络加载时(后端不发送更新)会pull一个新的PagedList并使旧的无效。
从后台服务器获取,使用同步的Retrofit API加载网络数据到你的DataSource对象中。

注意:分页库的DataSource对象不提供任何错误处理,因为不同app处理和展示错误的方式不同。如果一个错误发生,将错误传递给callback中,尝试后续重新请求。可以查看PagingWithNetworkSample中的示例代码。

以上这两种方式的数据都是异步加载的。

分页加载本地数据库实例 PagingSample

怎么通过实现DataSource.Factory+Retrofit 网络加载处理下拉刷新,网络异常和重试PagingWithNetworkSample

1.2.2 网络和数据库同时获取

实例二,加载本地存储页,它本身会从网络加载附加数据。这通常是为了最小化网络负载并提供更好的低连接性体验——数据库被用作存储在后端的数据的缓存。

如图,LiveData从数据库获取一个连接,然后通过LivePagedListBuilder至BoundaryCallback,最后观察到数据完成的信号。

本地配合网络获取数据

Figure 3.数据库时网络数据的缓存,UI是从数据库中加载,如果数据库中没有再从网络中获取。

然后在回调中请求网络,将这些数据直接储存在数据库中。UI订阅了数据库的更新,因此,新的数据会自动流向正在观察的UI。

怎么通过实现DataSource.Factory+Retrofit 网络加载处理下拉刷新,网络异常,和重试PagingWithNetworkSample

当你已经加载了本地的数据库,你可以通过PagedList.BoundaryCallback监听数据库的边界,从而触发从网络加载更多数据添加到数据库中。如果你已经添加数据库的监听逻辑,那么UI可以实现自动刷新。

如下的代码展示如何使用BoundaryCallback

KOTLIN

class ConcertViewModel {fun search(query: String): ConcertSearchResult {val boundaryCallback =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.// val loadingState: LiveData<MyNetworkState> =//        boundaryCallback.loadingState}
}class ConcertBoundaryCallback(private val query: String,private val service: MyService,private val cache: MyLocalCache
) : PagedList.BoundaryCallback<Concert>() {// Requests initial data from the network, replacing all content currently// in the database.override fun onZeroItemsLoaded() {requestAndReplaceInitialData(query)}// Requests additional data from the network, appending the results to the// end of the database's existing data.override fun onItemAtEndLoaded(itemAtEnd: Concert) {requestAndAppendData(query, itemAtEnd.key)}
}

JAVA

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 ConcertBoundaryCallbackextends 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.@Overridepublic void onZeroItemsLoaded() {requestAndReplaceInitialData(mQuery);}// Requests additional data from the network, appending the results to the// end of the database's existing data.@Overridepublic void onItemAtEndLoaded(@NonNull Concert itemAtEnd) {requestAndAppendData(mQuery, itemAtEnd.key);}
}

1.2.3 处理网络错误

当你使用分页库从网络获取数据并分页数据,不要把网络请求仅看做可用或者不可用,通常很多连接都是 间歇性的或片状的:某个特定的时间可能会返回失败的响应。也可能有的时候网络请求缓慢。你应该对每次的失败请求做优雅的处理。例如,你可以添加一个重试按钮让用户选择重新获取数据当数据刷新功能不可用的时候。如果实在数据分页过程中出错,做好是做重新请求该分页数据。

1.2.4 更新现有App

如果你的app已经从数据库或者网络获取了数据。直接升级为分页数据是有有可能的。该部分将展示如何升级一个已经设计好的app。

1.2.4.1 自定义分页解决

如果你用自定义功能去从你的DataSource中加载少量的数据集,你可以将这个逻辑用PagingList类来替换。PagingList实例提供了通用数据源的连接。这些实例也提供了将数据适配到RecyclerView的Adapter。

1.2.4.2 使用paging作为数据加载

如果你使用内存List作为后台数据源适配到UI中,如果数据项变得越来越大,那么考虑使用PagingList类。PagingList实例可以传递LiveData<PagedList>Observable<List>类型的数据到你的UI中,这样能极大缩短加载时间和内存使用。做好,替换List对象为PagingList对象,这个不会影响你APP的UI结构和数据更新逻辑。

1.2.4.3 使用CursorAdapter加载数据

你的app可能会使用CursorAdapter加载一个cursor数据源到你的ListView中。这种情况下,你通常需要从ListVIew迁移到RecyclerVIew作为你的UI容器,然后替换Cursor组件为Room或者PositionalDataSource,依赖于Cursor实例是否涉及到一个SQLite数据库。

在有些情形下,例如当使用Spinner时,你仅需要提供Adapter。一个数据加载库将数据加载到Adapter中并显示出来。这种情况下,更改你的Adapter的数据为LiveData<PagedList>类型,然后使用ArrayAdapter对象包装这个列表将数据显示到你的UI中。

1.2.4.4 使用AsyncListUtil异步加载数据

如果你正在使用AsyncListUtil对象作为分页加载,那么分页库让你加载数据变得更加简单:

你的数据不需要位置切分。分页库让你直接通过Keys加载网络数据

你的数据大小不确定。使用分页库,你可以加载数据到页里,直到所有数据加载完成。

你可以更容易观察你的数据。将你的数据包装进ViewModel这种可观察数据结构,分页库可以展现这些数据数据。

注意:如果你现有的app是通过SQLite数据库访问数据,请看Room持久库。

1.2.4.5 数据库实例

下面的代码将上面介绍的几个组件串联起来使用。

通过LiveData构建可观察的分页数据,通过对数据库进行增删改,RecyclerView中的内容会自动高效地刷新:

KOTLIN

@Dao
interface ConcertDao {// The Int type parameter tells Room to use a PositionalDataSource// object, with position-based loading under the hood.@Query("SELECT * FROM concerts ORDER BY date DESC")fun concertsByDate(): DataSource.Factory<Int, Concert>
}class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {val concertList: LiveData<PagedList<Concert>> =LivePagedListBuilder(concertDao.concertsByDate(), /* page size */ 20).build()
}class ConcertActivity : AppCompatActivity() {public override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val viewModel = ViewModelProviders.of(this).get(ConcertViewModel::class.java!!)val recyclerView = findViewById(R.id.concert_list)val adapter = ConcertAdapter()viewModel.concertList.observe(this, { pagedList ->adapter.submitList(pagedList) })recyclerView.setAdapter(adapter)}
}class ConcertAdapter() :PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) {fun onBindViewHolder(holder: ConcertViewHolder, position: Int) {val 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()}}companion object {private val DIFF_CALLBACK = object :DiffUtil.ItemCallback<Concert>() {// Concert details may have changed if reloaded from the database,// but ID is fixed.override fun areItemsTheSame(oldConcert: Concert,newConcert: Concert): Boolean =oldConcert.id == newConcert.idoverride fun areContentsTheSame(oldConcert: Concert,newConcert: Concert): Boolean =oldConcert == newConcert}}
}

JAVA

@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 {@Overridepublic 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 ConcertAdapterextends PagedListAdapter<Concert, ConcertViewHolder> {protected ConcertAdapter() {super(DIFF_CALLBACK);}@Overridepublic 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.@Overridepublic boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {return oldConcert.getId() == newConcert.getId();}@Overridepublic boolean areContentsTheSame(Concert oldConcert,Concert newConcert) {return oldConcert.equals(newConcert);}};
}

使用RxJava2构建可观察的分页数据

如果你比较偏向于使用RxJava2而不是LiveData,你可以创建一个Observable或者Flowable对象:

KOTLN

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {val concertList: Flowable<PagedList<Concert>> =RxPagedListBuilder(concertDao.concertsByDate(), /* page size */ 50).buildFlowable(BackpressureStrategy.LATEST)
}

JAVA

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);}
}

你可以通过如下代码开始和停止观察数据
KOTLN

class ConcertActivity : AppCompatActivity() {private lateinit var adapter: ConcertAdapterprivate lateinit var viewModel: ConcertViewModelprivate val disposable = CompositeDisposable()public override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)val recyclerView = findViewById(R.id.concert_list)viewModel = ViewModelProviders.of(this).get(ConcertViewModel::class.java!!)adapter = ConcertAdapter()recyclerView.setAdapter(adapter)}override fun onStart() {super.onStart()disposable.add(viewModel.concertList.subscribe({flowableList -> adapter.submitList(flowableList)}))}override fun onStop() {super.onStop()disposable.clear()}
}

JAVA

public class ConcertActivity extends AppCompatActivity {private ConcertAdapter mAdapter;private ConcertViewModel mViewModel;private CompositeDisposable mDisposable = new CompositeDisposable();@Overridepublic 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);}@Overrideprotected void onStart() {super.onStart();mDisposable.add(mViewModel.concertList.subscribe(flowableList -> mAdapter.submitList(flowableList)));}@Overrideprotected void onStop() {super.onStop();mDisposable.clear();}
}

基于RxJava2和基于LiveData解决方案,ConcertDao与ConcertAdapter代码结构是一致的。

2.现有分页库

当前实现分页加载的方法有两种:使用CursorAdapter;使用AsyncListUtil

  1. CursorAdapter 可以是ListView很容易的加载数据,但是他是运行在UI线程的,而且Cursor也会在页面上出现无效的现象。
  2. AsyncListUtil允许RecycleView基于position进行分页加载,但是不允许非position进行分页加载,在数据集中强制使用null作为修饰符。

Paging解决了这些问题。此库简化了请求数据的过程。

3.Paging用户指南

3.1 加入工程

在app Module的build.gradle中添加如下依赖

dependencies {def paging_version = "1.0.0"implementation "android.arch.paging:runtime:$paging_version"// alternatively - without Android dependencies for testingtestImplementation "android.arch.paging:common:$paging_version"// optional - RxJava support, currently in release candidateimplementation "android.arch.paging:rxjava2:1.0.0-rc1"
}

AndroidX(Android Jetpack)

dependencies {def paging_version = "2.0.0-beta01"implementation "androidx.paging:paging-runtime:$paging_version"// alternatively - without Android dependencies for testingtestImplementation "androidx.paging:paging-common:$paging_version"// optional - RxJava supportimplementation "androidx.paging:paging-rxjava2:$paging_version"
}

3.2 UI组件以及相关事项

3.2.1 将UI和View Modle建立联系

你可以将一个 LiveData<PagedList>实例与一个PagedListAdapter联系起来,如下示例代码:

KOTLIN

private val adapter = ConcertAdapter()
private lateinit var viewModel: ConcertViewModeloverride fun onCreate(savedInstanceState: Bundle?) {viewModel = ViewModelProviders.of(this).get(ConcertViewModel::class.java)viewModel.concerts.observe(this, adapter::submitList)
}

JAVA

public class ConcertActivity extends AppCompatActivity {private ConcertAdapter mAdapter;private ConcertViewModel mViewModel;@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);mViewModel = ViewModelProviders.of(this).get(ConcertViewModel.class);mViewModel.concertList.observe(this, mAdapter::submitList);}
}

正如数据源提供了PagingList的新的实例,Activity发送这个对象到adapter中,PagedListAdapter实现了如何更新的算法,可以自动处理分页以及列表的数据差异。因此,你的ViewHolder只需要处理绑定实际的item。

KOTLIN

class ConcertAdapter() :PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) {override fun onBindViewHolder(holder: ConcertViewHolder, position: Int) {val concert: Concert? = getItem(position)// Note that "concert" is a placeholder if it's null.holder.bindTo(concert)}companion object {private val DIFF_CALLBACK = object :DiffUtil.ItemCallback<Concert>() {// The ID property identifies when items are the same.override fun areItemsTheSame(oldItem: Concert, newItem: Concert) =oldItem.id = newItem.id// Use the "==" operator to know when an item's content changes.// Implement equals(), or write custom data comparison logic here.override fun areContentsTheSame(oldItem: Concert, newItem: Concert) = oldItem == newItem}}
}

JAVA

public class ConcertAdapterextends PagedListAdapter<Concert, ConcertViewHolder> {protected ConcertAdapter() {super(DIFF_CALLBACK);}@Overridepublic void onBindViewHolder(@NonNull ConcertViewHolder holder,int position) {Concert concert = getItem(position);// Note that "concert" can be null if it's a placeholder.holder.bindTo(concert);}private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =new DiffUtil.ItemCallback<Concert>() {// The ID property identifies when items are the same.@Overridepublic boolean areItemsTheSame(Concert oldItem, Concert newItem) {return oldItem.getId() == newItem.getId();}// Use Object.equals() to know when an item's content changes.// Implement equals(), or write custom data comparison logic here.@Overridepublic boolean areContentsTheSame(Concert oldItem, Concert newItem) {return oldItem.equals(newItem);}};
}

.PagedListAdapter通过PagedList.Callback处理分页加载事件。当用户滚动的的时候,PagedListAdapter调用PagedList.loadAround()给PagingList提供触发事件,PagingList计算出它需要从数据源获取哪些items。

Note: PagedList is content-immutable. This means that, although new content can be loaded into an instance of PagedList, the loaded items themselves cannot change once loaded. As such, if content in a PagedList updates, the PagedListAdapter object receives a completely new PagedList that contains the updated information.

3.2.2 实现 diffing callback

The preceding sample展示了实现areContentsTheSame()接口,通过比较对象域。你也可以通过比较对象实现,确保实现对象的equals()函数。

Diffing using a different adapter type
If you choose not to inherit from PagedListAdapter—such as when you’re using a library that provides its own adapter—you can still use the Paging Library adapter’s diffing functionality by working directly with an AsyncPagedListDiffer object.

用不同的Adapter类型比较Item差异

如果你不选择继承PagingListAdapter-例如你使用了一个自己实现的Adapter库-你仍然可以使用Paging 库中的Adapter的diffing 功能,通过AsyncPagedListDiffer对象实现。

3.2.3 在你的UI中提供占位

在你的app获取到数据之前,如果你想让你的UI显示列表,你可以先向你的用户展示占位列表。RecyclerView可以处理列表的Item被设置为null数据的情形。

注意:默认情况下,placeHolder是开启的。

占位符有以下优点:

1.支持滚动条:PagingList提供了List item的数量给PagedListAdapter.这些信息允许Adapter绘制一个滚动条传达list的尺寸。当新的页数加载后,滚动条不会滚动,因为有的list没有改变尺寸。

2.没有加载Loading提示的必要:因为list尺寸已经知道了,没有必要告诉用户有更多的items正在被加载。placeholders已经传达了这些信息。

在使用占位支持之前,如下先决条件需要知道:

  1. 需要一个数据集大小是确定的:通过Room持久库获取的DataSource可以有效的确定它们的数量。如果你正在使用一个本地存储解决方案或者仅仅通过网路数据架构,那很可能无法确定你的数据集的数量。

  2. 需要Adapter触发未加载数据的加载:Adapter或者已存在的机制用于处理null items的情形。例如,当你bind数据到一个ViewHolder,你需要提供默认值给未加载的数据。

  3. 需要每个item有相同的view大小:如果对于社交网络更新(例如:聊天界面)item的大小依赖于内容。强烈建议你关掉placeholders功能。

3.3 数据组件以及相关事项

3.3.1 构建一个 observable的列表

典型的,你的UI代码观察一个LiveData<PagedList>对象(或者,你正在使用Rxjava2, 那么这个对象可以是mermaid flowchatable<PagedList>或者Observable<PagedList>对象),一个和你的App中ViewModel联系在一起的对象。这个可被观察的对象建立起了list数据中展示与内容之间联系。

为了创建一个observable类型的PagedList对象,传递一个DataSource.Factory的实例给一个LivePagedListBuilder 或者 RxPagedListBuilder对象。一个DataSource对象给一个PagingList加载分页数据。当数据更新的时候,这个工厂类将创建一个PagedList新的实例,例如数据库表更新或者网络刷新。Room库能够提供给你一个DataSource.Factory, 你也可以自己创建一个你自己的DataSource.Factory。

如下的代码片段,展示了如何创建一个新的LiveData<PagedList>在你的ViewModel类中,通过Room的DataSource.Factory类的构造能力:

KOTLIN

@Dao
interface ConcertDao {// The Int type parameter tells Room to use a PositionalDataSource// object, with position-based loading under the hood.@Query("SELECT * FROM concerts ORDER BY date DESC")fun concertsByDate(): DataSource.Factory<Int> concertsByDate()
}

JAVA

@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();
}

ConcertViewModel
KOTLIN

// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =concertDao.concertsByDate()val concertList = LivePagedListBuilder(myConcertDataSource, /* page size */ 20).build()

JAVA

// The Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =concertDao.concertsByDate();LiveData<PagedList<Concert>> concertList =LivePagedListBuilder(myConcertDataSource, /* page size */ 20).build()

观察数据的更新

Paging使用下列类来构造PagedList容器实时更新:

1.LivePageListBuilder,此类是从DataSource.Factory构建LiveData。如果使用Room来管理数据库,Dao可以生成DataSource.Factory,使用PositionnalDataSource,如下:

LiveData<PagedList<Item>> pagedItems =LivePagedListBuilder(myDataSource, /* page size */ 50).setFetchExecutor(myNetworkExecutor).build();

2.RXPagedListBuilder,此类支持RXJava。使用此类实现PagedList来构造Flowable和Observable如下所示:

Flowable<PagedList<Item>> pagedItems =RxPagedListBuilder(myDataSource, /* page size */ 50).setFetchScheduler(myNetworkScheduler).buildFlowable(BackpressureStrategy.LATEST);

3.3.2 定义你自己的paging配置

Paging配置可以通过如下属性设置完成:
1.Page size:每一页item数量。
2.Prefetch distance:预加载数量,通常是page size整数倍
3.Placeholder presence:是否启用占位功能。

如果你想控制更多paging加载过程,你可以传递一定制化的Executor对象到LivePagedListBuilder中,如下代码片段:

KOTLIN

val myPagingConfig = PagedList.Config.Builder().setPageSize(50).setPrefetchDistance(150).setEnablePlaceholders(true).build()// The Int type argument corresponds to a PositionalDataSource object.
val myConcertDataSource : DataSource.Factory<Int, Concert> =concertDao.concertsByDate()val concertList = LivePagedListBuilder(myConcertDataSource, myPagingConfig).setFetchExecutor(myExecutor).build()

JAVA

PagedList.Config myPagingConfig = new PagedList.Config.Builder().setPageSize(50).setPrefetchDistance(150).setEnablePlaceholders(true).build();// The Integer type argument corresponds to a PositionalDataSource object.
DataSource.Factory<Integer, Concert> myConcertDataSource =concertDao.concertsByDate();LiveData<PagedList<Concert>> concertList =new LivePagedListBuilder<>(myConcertDataSource, myPagingConfig).setFetchExecutor(myExecutor).build();

3.3.3 选择正确的数据源类型

使用DataSource定义你需要提取分页的数据源。根据使用场景使用的不同使用它不同的子类,如下

1.使用PageKeyDataSource,让你加载的页面插入到下一个或者以前的key,例如:例如:你要从网络获取社交媒体的帖子,你就需要通过nextPage加载到后续的加载中。

2.使用ItemKeyDataSource,如果你需要让使用的数据的item从N条增加到N+1条请使用。例如:你需要从嵌套评论中获取一条评论,需要通过一条评论的ID获取下一条评论的内容。

3.PositionalDataSource,如果你需要从事数据存储的任意位置来获取数据页,此类支持你从任意你选择的位置开始请求item的数据集。比如从第1200条返回20条item。

如果使用Room来管理数据,就要使用DataSource.Factory来初始化PositionalDataSource。如下:

@Query("select * from users WHERE age > :age order by name DESC, id ASC")
DataSource.Factory<Integer, User> usersOlderThan(int age);

3.3.4 数据刷新

当你的数据过时,你需要刷新DataSource,并通知其他层更新

注意:app UI可以触发数据重绘,通过使用下拉刷新模型swipe to refresh

3.3.5 构建你自己的数据源

如果你用一个自定义的本地数据解决,或者直接从网络获取,你可以实现一个DataSource的子类。如下代码片段:
KOTLIN

class ConcertTimeDataSource() :ItemKeyedDataSource<Date, Concert>() {override fun getKey(item: Concert) = item.startTimeoverride fun loadInitial(params: LoadInitialParams<Date>,callback: LoadInitialCallback<Concert>) {val items = fetchItems(params.key, params.requestedLoadSize)callback.onResult(items)}override fun loadAfter(params: LoadParams<Date>,callback: LoadCallback<Concert>) {val items = fetchItemsAfter(date = params.key,limit = params.requestedLoadSize)callback.onResult(items)}
}

JAVA

public class ConcertTimeDataSourceextends ItemKeyedDataSource<Date, Concert> {@NonNull@Overridepublic Date getKey(@NonNull Concert item) {return item.getStartTime();}@Overridepublic void loadInitial(@NonNull LoadInitialParams<Date> params,@NonNull LoadInitialCallback<Concert> callback) {List<Concert> items =fetchItems(params.key, params.requestedLoadSize);callback.onResult(items);}@Overridepublic void loadAfter(@NonNull LoadParams<Date> params,@NonNull LoadCallback<Concert> callback) {List<Concert> items =fetchItemsAfter(params.key, params.requestedLoadSize);callback.onResult(items);}

你也可通过DataSource.Factory创建数据源:

KOTLIN

class ConcertTimeDataSourceFactory() :DataSource.Factory<Date, Concert>() {val sourceLiveData = MutableLiveData<ConcertTimeDataSource>()override fun create(): DataSource<Date, Concert> {val source = ConcertTimeDataSource()sourceLiveData.postValue(source)return source}
}

JAVA

public class ConcertTimeDataSourceFactoryextends DataSource.Factory<Date, Concert> {private MutableLiveData<ConcertTimeDataSource> mSourceLiveData =new MutableLiveData<>();@Overridepublic DataSource<Date, Concert> create() {ConcertTimeDataSource source =new ConcertTimeDataSource();mSourceLiveData.postValue(source);return source;}
}

3.3.6 考虑内容如何更新

如果你构造了observable PagedList对象,考虑你的内容如何更新。如果你通过Room数据,那么你可以获取推送更新,从而自动更新UI。

如果你通过网络分页api,你需要一个GUI接口,例如,“swipe to refresh”,提供一个刷新的动作重新获取数据源。示例代码如下:

KOTLIN

class ConcertActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {// ...concertTimeViewModel.refreshState.observe(this, Observer {// Shows one possible way of triggering a refresh operation.swipeRefreshLayout.isRefreshing =it == MyNetworkState.LOADING})swipeRefreshLayout.setOnRefreshListener {concertTimeViewModel.invalidateDataSource()}}
}class ConcertTimeViewModel(firstConcertStartTime: Date) : ViewModel() {val dataSourceFactory = ConcertTimeDataSourceFactory(firstConcertStartTime)val concertTimeDataSource = dataSourceFactory.create()val concertList: LiveData<PagedList<Concert>> =LivePagedListBuilder(dataSourceFactory, 20).setFetchExecutor(myExecutor).build()fun invalidateDataSource() = concertTimeDataSource.invalidate()
}

JAVA

public class ConcertActivity extends AppCompatActivity {@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {// ...mViewModel.getRefreshState().observe(this, new Observer<NetworkState>() {// Shows one possible way of triggering a refresh operation.@Overridepublic void onChanged(@Nullable MyNetworkState networkState) {swipeRefreshLayout.isRefreshing =networkState == MyNetworkState.LOADING;}};swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshListener() {@Overridepublic void onRefresh() {mViewModel.invalidateDataSource();}});}
}public class ConcertTimeViewModel extends ViewModel {private LiveData<PagedList<Concert>> mConcertList;private DataSource<Date, Concert> mConcertTimeDataSource;public ConcertTimeViewModel(Date firstConcertStartTime) {ConcertTimeDataSourceFactory dataSourceFactory =new ConcertTimeDataSourceFactory(firstConcertStartTime);mConcertTimeDataSource = dataSourceFactory.create();mConcertList = new LivePagedListBuilder<>(dataSourceFactory, 20).setFetchExecutor(myExecutor).build();}public void invalidateDataSource() {mConcertTimeDataSource.invalidate();}
}

3.3.7 提供数据表示的映射

分页库支持item-based和page-based的 item数据的转换, 如下代码展示了将concert实体类中name和date组合成一个字符串的功能:

KOTLIN

class ConcertViewModel : ViewModel() {val concertDescriptions : LiveData<PagedList<String>>init {val factory = database.allConcertsFactory().map { concert ->concert.name + " - " + concert.date}concerts = LivePagedListBuilder(factory, /* page size */ 30).build()}}
}

JAVA

public class ConcertViewModel extends ViewModel {private LiveData<PagedList<String>> mConcertDescriptions;public ConcertViewModel(MyDatabase database) {DataSource.Factory<Integer, Concert> factory =database.allConcertsFactory().map(concert ->concert.getName() + "-" + concert.getDate());mConcertDescriptions = new LivePagedListBuilder<>(factory, /* page size */ 30).build();}
}

这个非常有用,如果你想要包装,转换,或者准备items在它们被加载后。因为这些工作实在获取执行器中完成,你可以执行耗时的工作,例如从磁盘读取或者从数据库查询。

3.3.8 PaingList将数据加载进内存

PagedList类加载的数据来自于DataSource。你可以配置一次加载多少数据,可以预先获取多少数据,尽量减少加载数据的时间。此类也可以向其他类提供数据更新的信号,比如:RecycleView.Adapter一样,为RecycleView页允许数据加载。

PagedList数据加载机制:

  1. PagedListAdapter 类实现自RecycleView.Adapter,并且从PagedList中加载数据。例如:当一个新的页面被加载,PagedListAdapter就会发信号通知RecycleView数据已经加载完成,然后RecycleView就是显示和数据量相等的item,并执行适当的动画。

  2. PagedListAdapter对于来自一个PagedList的下一条数据会在后台线程进行计算。(例如:将数据库的新数据更新到PagedList中),调用notifyItem…()方法更新需要的数据列表的内容。然后RecycleView再执行必要的更改。例如:iitem的position的顺序的改变。

3.4 Paging数据加载原理图

Paging的数据流是在后台线程生产,在UI线程中显示。例如:当一条新的item插入到数据库中,DataSource被初始化,LiveData或者Flowable后台线程就会创建一个新的PagedList
image

Figure 1.
Paging在后台线程中完成大部分工作,所以不会给主线程带来负担。

这个新建的PagedList会被发送到UI线程的PagedListAdapter。PagedListAdapter使用DiffUtil在对比现在的表单和新建表单的差异。当对比结束,PagedListAdapter通过调用RecycleView.Adapter.notifyItemInserted()将新的item插入到适当的位置。RecycleView就会知道他需要绑定一个新的item,并将其显示。

官网地址:https://developer.android.google.cn/topic/libraries/architecture/paging

Sunflower:https://github.com/googlesamples/android-sunflower

PagingSample:https://github.com/googlesamples/android-architecture-components/tree/master/PagingSample

PagingWithNetworkSample:https://github.com/googlesamples/android-architecture-components/tree/master/PagingWithNetworkSample

这篇关于Android Paging library详解(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1074876

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建      首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件、动态链接库文件、可执行文件、脚本文件、配置文件等。      我们在编写hellowor

LabVIEW FIFO详解

在LabVIEW的FPGA开发中,FIFO(先入先出队列)是常用的数据传输机制。通过配置FIFO的属性,工程师可以在FPGA和主机之间,或不同FPGA VIs之间进行高效的数据传输。根据具体需求,FIFO有多种类型与实现方式,包括目标范围内FIFO(Target-Scoped)、DMA FIFO以及点对点流(Peer-to-Peer)。 FIFO类型 **目标范围FIFO(Target-Sc