JetPack (五)之 Paging 分页库
类关系
2021-01-24 13.03.05.pngDataSource 类关系
paging_类结构.pngabstract 子类 PageKeyedDataSource
PageKeyedDataSource<Key, Value>:适用于目标数据根据页信息
请求数据的场景,即Key字段是页相关的信息。比如请求的数据的
参数中包含类似next / pervious页数的信息。
如果页面需要实现上一页、下一页,需要将请求的Token传递到下一步时使用
abstract 子类 ItemKeyedDataSource
ItemKeyedDataSource<Key, Value>:适用于目标数据的加载依赖
特定item的信息, 即Key字段包含的是Item中的信息,比如需要根
据第N项的信息加载第N+1项的数据,传参中需要传入第N项的ID
时,该场景多出现于论坛类应用评论信息的请求。
程序需要根据上一条数据信息(ID)获取下一条数据时使用
abstract 子类 PositionalDataSource
PositionalDataSource<T>:适用于目标数据总数固定,通过特定
的位置加载数据,这里Key是Integer类型的位置信息,T即Value。
比如从数据库中的1200条开始加在20条数据。
需要从数据存储中选择的任何位置获取数据页;例如,请求可能返回以位置1200开头的20个数据项
源码流程分析
20181021221030916.gif角色
- 角色1 数据源
数据的来源,可以有多种来源渠道,例如:“网络数据”,“本地数据”,“数据库数据”
DataSource.Factory:工厂类提供DataSource的实例,在自定义DataSource时使用
- 角色2 数据工厂
管理 数据源 的工厂,为了后续的扩展
- 角色3 数据模型
数据模型其实就是 ViewModel,用来管理数据
PagedList: 数据源获取的数据最终靠PagedList来承载。
对于PagedList,我们可以这样来理解,它就是一页数据的集合。
每请求一页,就是新的一个PagedList对象。
数据集散中心,根据需要向DataSource索取加载数据,并将得到的数据传递到PagedListAdapter
- 角色4 适配器
RecyclerView的Adapter需要继承PagedListAdapter
LiveData观察到的数据,把感应到的数据 给 适配器,适配器又绑定了 RecyclerView,那么RecyclerView的列表数据就改变了
数据适配器,这里除了起到普通界面加载适配器的作用外,更重要的是根据滑动显示的坐标,起到了确定什么时候要求向PagedList加载数据
Paging的各个角色职责:
DataSource:数据的来源
DataSource.Factory:工厂类提供DataSource的实例,在自定义DataSource时使用
PagedList:数据集散中心,根据需要向DataSource索取加载数据,并将得到的数据传递到PagedListAdapter
PagedListAdapter:数据适配器,这里除了起到普通界面加载适配器的作用外,更重要的是根据滑动显示的坐标,起到了确定什么时候要求向PagedList加载数据
DiffUtil.ItemCallback:判断数据是否发生改变以确定界面是否更新
调用和入口
我们用最简单的使用demo,做分析入口。
RecyclerView----ViewModel----LiveData
public class PagingActivity extends AppCompatActivity {
private RecyclerView recyclerView;
RecyclerViewPageingAdapter recyclerViewPageingAdapter;
MyViewModel studentViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_paging);
studentViewModel = new ViewModelProvider(this,
new ViewModelProvider.NewInstanceFactory())
.get(MyViewModel.class);
recyclerView = findViewById(R.id.recycle_view);
recyclerViewPageingAdapter = new RecyclerViewPageingAdapter();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(recyclerViewPageingAdapter);
studentViewModel.getListLiveData().observe(this, new Observer<PagedList<MyStudent>>() {
@Override
public void onChanged(PagedList<MyStudent> myStudents) {
recyclerViewPageingAdapter.submitList(myStudents);
}
});
}
}
核心就是ViewModel获取LiveData数据源,在观察方法合适的时机将数据发射回调到RecyclerView的UI展示
看下我们StudentViewModel中 studentViewModel.getListLiveData() 的源码
public
class MyViewModel extends ViewModel {
private final LiveData<PagedList<MyStudent>> listLiveData;
public MyViewModel() {
StudentDataSourceFactory factory = new StudentDataSourceFactory();
this.listLiveData = new LivePagedListBuilder<Integer, MyStudent>
(factory, Config.PAGESIZE)
.build();
}
// TODO 暴露数据出去
public LiveData<PagedList<MyStudent>> getListLiveData() {
return listLiveData;
}
}
分析
先从数据源DataSource入手
LivePagedListBuilder(xxx).build()------>
create() 创建LiveData
ComputableLiveData<PagedList<Value>> 大管家
简单来看最外层架构模型
ComputableLiveData 包装了LiveData和Executor,通过Executor的任务 通过 compute() 方法 返回LiveData---PagedList 的过程。
LivePagedListBuilder(xxx).build()------>
create() 创建LiveData
ComputableLiveData<PagedList<Value>> 大管家
------->
DataSource.Factory#create
创建DataSource
自定义实现数据源创建 StudentDataSourceFactory extends DataSource.Factory<Integer, MyStudent>
------>
DataSoure#addInvalidatedCallback
执行回调 onInvalidated 回调方法,不停的判断,isActive 激活执行任务mInvalidationRunnable
------>
PagedList.Builder#build()
------------>
PagedList#create
----------->
大体上分成两类:ContiguousPagedList 和TiledPagedList
通过条件if (dataSource.isContiguous() || !config.enablePlaceholders) 判断
mEnablePlaceholders 默认true 也就是config.enablePlaceholders 默认true
1. isContigous==true 并且 config.enablePlaceHolders==true
new ContiguousPagedList
创建 ContiguousPagedList
2. isContigous==false 并且 config.enablePlaceHolders==false
PagedList#wrapAsContiguousWithoutPlaceholders
包裹PositionalDataSource
生成ContiguousDataSource包装类
3. isContigous==false 并且config.enablePlaceHolders==true
创建TiledPagedList
new TiledPagedList
-----------> 我们随便走一个人值,回调显示路径差不多,选择路径 1 分析
PageKeyedDataSource#dispatchLoadInitial
---------->
LoadInitialCallbackImpl实例化
PageResult.Receiver 传入
---------->
loadInitial(LoadInitialCallbackImpl 回调)
留给数据仓库调用实现,例子
// loadInitial 初始加载数据
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Person> callback) {
List<Person> dataList = dataRepository.initData(params.requestedLoadSize);
callback.onResult(dataList, null, 2);
}
----------->
调用回调
PageKeyedDataSource#onResult
LoadInitialCallbackImpl#onResult实现细节
----------->
PageKeyedDataSource #LoadCallbackHelper#dispatchResultToReceiver
---------->
PageResult.Receiver#mReceiver#onPageResult
---------->
会有两个回调
1. ContiguousPagedList#onPageResult
2. TiledPagedList#onPageResult
---------->
我们还是根据分析路径选择 路径 1
返回PageResult 有0,1,2,3 type类型
resultType==0,1,2 的时候会执行有3个分支
1.PageList#mStorage#init
2.PageList#mStorage#appendPage
3. PageList#mStorage#prependPage
----------->
随意选择一个,我们跟踪3 路径
PageStorage#Callback#onPagePrepended
----------->
PageList#notifyInserted------>AsyncPagedListDiffer#onInserted
----------->
RecyclerView#notifyItemRangeInserted
终点,RecyclerView 执行相应的方法
image-20200405190707615.png
图片展示调用的流程
细化分析调用
数据的创建
开始查看 ”private final LiveData<PagedList<Student>> listLiveData;“ 此变量是如何创建的:
我们自己定义的方法
public MyViewModel() {
StudentDataSourceFactory factory = new StudentDataSourceFactory();
this.listLiveData = new LivePagedListBuilder<Integer, MyStudent>
(factory, Config.PAGESIZE)
.build();
}
点击进入build函数分析:
@NonNull
@SuppressLint("RestrictedApi")
public LiveData<PagedList<Value>> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
进入create函数分析:
使用LivePagedListBuilder配置Factory和Config,然后调用build创建实例,在build方法中直接调用了create()方法创建LiveData
在create()中直接返回了ComputableLiveData的实例,在ComputableLiveData实例重写的compute中执行了一些主要操作:
一:调用传入的Factory的create()创建DataSource实例
二:创建并返回PagedList实例
三:PagedList.build() & PagedList.create() 就是如下代码(细节):
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
PagedList的创建过程,在PagedList.build()中调用了PagedList.create(),所以真正的创建是在create()中发生的:
private static <K, T> PagedList<T> create(...) {
if (dataSource.isContiguous() || !config.enablePlaceholders) {
......
return new ContiguousPagedList<>(contigDataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
key,
lastLoad);
} else {
return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
notifyExecutor,
fetchExecutor,
boundaryCallback,
config,
(key != null) ? (Integer) key : 0);
}
}
从上面的代码中看出根据 条件(dataSource.isContiguous() || !config.enablePlaceholders)的不同分别创建ContiguousPagedList和TiledPagedList,其实这里就是区分上面的三个自定义DataSource的类型(三个数据源),如果是PositionalDataSource创建TiledPagedList,其他的返回ContiguousPagedList,我们依次查看三个DataSource中的isContiguous()方法:
PositionalDataSource类中的:
@Override
boolean isContiguous() {
return false;
}
ItemKeyedDataSource和PageKeyedDataSource都继承与ContiguousDataSource,只查看ContiguousDataSource类中的:
@Override
boolean isContiguous() {
return true;
}
又回来,从 .build开始看:
new ComputableLiveData有什么用 与 何时执行compute函数, 这两个疑问,查看ComputableLiveData源码,发现在ComputableLiveData的构造函数中创建LiveData实例,下面查看Runnable接口中执行了哪些逻辑:
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
------------------------------------------------------------------------------
final Runnable mRefreshRunnable = new Runnable() {
@WorkerThread
@Override
public void run() {
boolean computed;
do {
computed = false;
// compute can happen only in 1 thread but no reason to lock others.
if (mComputing.compareAndSet(false, true)) {
// as long as it is invalid, keep computing.
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
// 同学们 这里会执行 compute(); 函数
// 调用了compuet创建了PagedList
value = compute();
}
if (computed) {
// 设置LiveData的值
mLiveData.postValue(value);
}
} finally {
// release compute lock
mComputing.set(false);
}
}
.......
} while (computed && mInvalid.get());
}
};
在mRefreshRunnable中调用了ComputableLiveData的compute()方法创建了PagedList,所以此处的Value就是PagedList,然后为mLiveData初始化赋值PagedList
细心的同学会留意到,在上面的create()方法最后一句调用了getLiveData()获取到的就是ComputableLiveData构造函数中创建的LIveData:
@SuppressWarnings("WeakerAccess")
@NonNull
public LiveData<T> getLiveData() {
return mLiveData;
}
数据的加载工作
ContiguousPagedList 作为触发点:
当我们自定义实现ItemKeySource时,创建的PagedList实际为ContiguousPagedList,As查看ContiguousPagedList构造函数源码:
ContiguousPagedList(
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key,
int lastLoad) {
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
mLastLoad = lastLoad;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}
mShouldTrim = mDataSource.supportsPageDropping()
&& mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
}
在构造函数中执行一下逻辑,所以继续追踪代码:
第一点:创建PagedStorage实例,主要根据滑动的位置显示是否要继续加载数据
第二点:调用DataSource.dispatchLoadInitial方法,此时使用的时ItermKeyDataSource的dispatchLoadInitial 方法
@Override
final void dispatchLoadInitial(@Nullable Key key, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
LoadInitialCallbackImpl<Value> callback =
new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
上面代码在ItermKeyDataSource的dispatchLoadInitial()方法中调用了抽象函数loadInitial(),根据前面的学习我们知道在 loadInitial() 中设置了初始化的网络请求,到此实现了Paging组件初始化数据的加载
数据的显示工作
在自定义ItemDataSource的loadInitial()中加载数据后,调用了callback.onResult(it?.data!!.datas!!)方法,此处的callback是LoadInitialCallback的实现类LoadInitialCallbackImpl,在onResult()方法中又调用了LoadCallbackHelper.dispatchResultToReceiver()
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
Executor executor;
synchronized (mSignalLock) {
if (mHasSignalled) {
throw new IllegalStateException(
"callback.onResult already called, cannot call again.");
}
mHasSignalled = true;
executor = mPostExecutor;
}
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
mReceiver.onPageResult(mResultType, result);
}
});
} else {
mReceiver.onPageResult(mResultType, result);
}
}
在dispatchResultToReceiver()方法中,调用PageResult.Receiver.onPageResult()方法,这里的mReceiver是在调用 mDataSource.dispatchLoadInitial()时传入的最后一个参数,他的实现在ContiguousPagedList中匿名创建:
// mSignalLock protects mPostExecutor, and mHasSignalled
private final Object mSignalLock = new Object();
private Executor mPostExecutor = null;
private boolean mHasSignalled = false;
LoadCallbackHelper(@NonNull DataSource dataSource, @PageResult.ResultType int resultType,
@Nullable Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
mDataSource = dataSource;
mResultType = resultType;
mPostExecutor = mainThreadExecutor;
mReceiver = receiver;
}
ContiguousPagedList:
在onPageResult()方法中根据resultType的类型执行操作,PageResult的三个数据类型分别对应者ItemKeyDataSource的三个方法:
loadInitial:对应初始化状态PageResult.INIT
loadBefore:对应初始化状态PageResult.PREPEND
loadAfter:对应初始化状态PageResult.APPEND
此出分析初始化,回调的类型为PageResult.INIT,调用了PagedStorage的init()方法:
在init()方法中首先调用另一个init()方法记录加载的位置,并保存加载的数据, 然后调用callback.onInitialized(),在onInitialzed()方法中调用了notifyInserted(),在notifyInserted()中遍历mCallbacks回调callback的onInserted()
interface Callback {
void onInitialized(int count);
void onPagePrepended(int leadingNulls, int changed, int added);
void onPageAppended(int endPosition, int changed, int added);
void onPagePlaceholderInserted(int pageIndex);
void onPageInserted(int start, int count);
void onPagesRemoved(int startOfDrops, int count);
void onPagesSwappedToPlaceholder(int startOfDrops, int count);
void onEmptyPrepend();
void onEmptyAppend();
}
继续追踪:
ContiguousPagedList:
public void onInitialized(int count) {
notifyInserted(0, count);
}
PagedList:
void notifyInserted(int position, int count) {
if (count != 0) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
Callback callback = mCallbacks.get(i).get();
if (callback != null) {
callback.onInserted(position, count);
}
}
}
}
PagedList 的 接口:
public abstract void onInserted(int position, int count);
一: 加载的数据保存在PagedStorage中,并记录了加载的位置信息
二: 加载完成后根据数据的变化,回调callback.onInserted()通知数据改变的数量和位置
CallBack 是哪来的,我们简单的追踪下代码 能否到PagedListAdapter
AsyncPagedListDiffer:
public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback, @NonNull AsyncDifferConfig<T> config) {
class NamelessClass_1 extends Callback {
NamelessClass_1() {
}
public void onInserted(int position, int count) {
AsyncPagedListDiffer.this.mUpdateCallback.onInserted(position, count);
}
public void onRemoved(int position, int count) {
AsyncPagedListDiffer.this.mUpdateCallback.onRemoved(position, count);
}
public void onChanged(int position, int count) {
AsyncPagedListDiffer.this.mUpdateCallback.onChanged(position, count, (Object)null);
}
}
ListUpdateCallback:
public interface ListUpdateCallback {
......
void onInserted(int position, int count);
AdapterListUpdateCallback:
@Override
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
PS:这个过程细枝末节很复杂,如果我们自己实现根据业务封装核心部分就好。