相关文章系列
- jetpack组件——入门篇
- jetpack系列——lifecycle源码分析
- jetpack系列—LiveData源码分析和实现事件分发总线
- jetpack系列——ViewModel源码分析
- 数据绑定技术DataBinding
- jetpack系列——DataBinding源码分析
使用
- paging实际就是分页加载,它把几种常见的分页机制提供了统一的解决方案
- paging支持的架构类型
- 网络数据
分页机制所设计的API接口不一样,但是总体可以分为3种,因此paging提供了3种不同的解决方案,分别是:PositionDataSource、PageKeyedDataSource、ItemKeyedDataSource - 数据库
替换数据源
- 网络数据
-
工作原理
image.png - 3个核心类
- pagedListAdapter
- 如果需要使用paging组件,适配器需要继承pagedListAdapter
- pagedList
- pagedList负责通知DataSource何时和如何获取数据,从DataSource获取的数据将存储在pagedList中
- DataSource
- 执行具体的数据载入工作,数据可以来自网络,也可以来自本地数据、数据库数据,根据分页机制不同,paging提供了3种DataSource
- 数据的载入需要再工作线程中进行
- pagedListAdapter
PositionDataSource
- 适用于可通过任意位置加载数据,且目标数据源数量固定的情况。
- 比如:从数据库的第100条数据开始加载20条数据
- DataSource创建
public class StudentDataSource extends PositionalDataSource<Student> {
/**
* 加载第一页数据的时候执行
*
* @param params
* @param callback
*/
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
/**
* position:位置
* totalCount:总的大小
*/
callback.onResult(getStudents(0, 20), 0,1000);
}
/**
* 有了初始化数据之后,滑动的时候如果需要加载数据的话,会调用此方法
*
* @param params
* @param callback
*/
@Override
public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Student> callback) {
callback.onResult(getStudents(params.startPosition, params.loadSize));
}
/**
* 假的数据源
*/
private List<Student> getStudents(int startPosition, int pageSize) {
List<Student> students = new ArrayList<>();
for (int i = startPosition; i < startPosition + pageSize; i++) {
Student student = new Student("Id是:" + i, "名字:" + i);
students.add(student);
}
return students;
}
}
- 数据工厂
/**
* author :Peakmain
* createTime:2021/11/5
* mail:2726449200@qq.com
* describe:PositionalDataSource对应的Key是Integer
*/
public class StudentDataSourceFactory extends DataSource.Factory<Integer,Student> {
@NonNull
@Override
public DataSource<Integer, Student> create() {
return new StudentDataSource();
}
}
- pageList的创建
public class StudentViewModel extends ViewModel {
private final LiveData<PagedList<Student>> listLiveData;
public StudentViewModel() {
StudentDataSourceFactory factory = new StudentDataSourceFactory();
this.listLiveData = new LivePagedListBuilder<>(factory, 20).build();
}
public LiveData<PagedList<Student>> getListLiveData() {
return listLiveData;
}
}
- PagedListAdapter的创建
public class StudentAdapter extends PagedListAdapter<Student, StudentAdapter.CommonRecyclerViewHolder> {
private static DiffUtil.ItemCallback<Student> DIFF_STUDENT = new DiffUtil.ItemCallback<Student>() {
//一般比较的是唯一内容:id
@Override
public boolean areItemsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
return oldItem.getId().equals(newItem.getId());
}
//对象本身的比较
@Override
public boolean areContentsTheSame(@NonNull Student oldItem, @NonNull Student newItem) {
return oldItem.equals(newItem);
}
};
public StudentAdapter() {
//diffCallback比较的行为
super(DIFF_STUDENT);
}
@NonNull
@Override
public CommonRecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.bean_recycler_view, null);
return new CommonRecyclerViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CommonRecyclerViewHolder holder, int position) {
Student student = getItem(position);
holder.mTvName.setText(student.getName());
holder.mTvId.setText(student.getId());
}
public static class CommonRecyclerViewHolder extends RecyclerView.ViewHolder {
private TextView mTvId, mTvName;
public CommonRecyclerViewHolder(@NonNull View itemView) {
super(itemView);
mTvId = itemView.findViewById(R.id.tv_id);
mTvName = itemView.findViewById(R.id.tv_name);
}
}
}
使用
val studentAdapter = StudentAdapter()
recyclerView.adapter = studentAdapter
recyclerView.layoutManager=LinearLayoutManager(context)
val viewModel=ViewModelProvider(this,ViewModelProvider.NewInstanceFactory()).get(StudentViewModel::class.java)
viewModel.listLiveData.observe(viewLifecycleOwner, Observer {
studentAdapter.submitList(it)
})
ItemKeyedDataSource<T>
- 适用于目标数据的加载依赖特定的item的信息,即key字段包含的是item中的信息
- 如:根据第N项的信息加载第n+1项的数据,传参中需要传入第N项的ID时
- 场景:论坛应用评论信息的请求
public class StudentDataSource1 extends ItemKeyedDataSource<Integer,Student> {
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Student> callback) {
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Student> callback) {
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Student> callback) {
}
@NonNull
@Override
public Integer getKey(@NonNull Student item) {
return null;
}
}
PageKeyedDataSource
- 如果页面需要实现上一页、下一页,需要将请求的Token传递到下一步时使用
public class StudentDataSource2 extends PageKeyedDataSource<Integer,Student> {
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Student> callback) {
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Student> callback) {
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Student> callback) {
}
}
源码分析
paging的数据是怎么初始化的?
我们入口首先来选择pagedList,因为这里是数据的集合点
LivePagedListBuilder#build()源码分析
public LiveData<PagedList<Value>> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
private static <Key, Value> LiveData<PagedList<Value>> create(
@Nullable final Key initialLoadKey,
@NonNull final PagedList.Config config,
@Nullable final PagedList.BoundaryCallback boundaryCallback,
@NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
@NonNull final Executor notifyExecutor,
@NonNull final Executor fetchExecutor) {
return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
//代码省略
@Override
protected PagedList<Value> compute() {
//代码省略
} while (mList.isDetached());
return mList;
}
}.getLiveData();
上面代码比较长,我们一点点分析
- 1、ComputableLiveData构造函数源码分析
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;
if (mComputing.compareAndSet(false, true)) {
// as long as it is invalid, keep computing.
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
value = compute();
}
if (computed) {
mLiveData.postValue(value);
}
} finally {
mComputing.set(false);
}
}
} while (computed && mInvalid.get());
}
};
protected abstract T compute();
最终会执行到create的ComputableLiveData的compute
- 2、compute源码分析
private static <Key, Value> LiveData<PagedList<Value>> create(
@Nullable final Key initialLoadKey,
@NonNull final PagedList.Config config,
@Nullable final PagedList.BoundaryCallback boundaryCallback,
@NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
@NonNull final Executor notifyExecutor,
@NonNull final Executor fetchExecutor) {
return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
@Override
protected PagedList<Value> compute() {
@Nullable Key initializeKey = initialLoadKey;
do {
//dataSourceFactory是我们自己传入的StudentDataSourceFactory,此时会调用StudentDataSourceFactory的.create方法
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());
return mList;
}
}.getLiveData();
}
public PagedList<Value> build() {
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
}
static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
@NonNull Executor notifyExecutor,
@NonNull Executor fetchExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
@Nullable K key) {
if (dataSource.isContiguous() || !config.enablePlaceholders) {
int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
if (!dataSource.isContiguous()) {
//noinspection unchecked
dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
.wrapAsContiguousWithoutPlaceholders();
if (key != null) {
lastLoad = (Integer) key;
}
}
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
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);
}
}
- 调用传入的Factorty的create()创建DataSource实例
- 创建并返回pagedList实例
- create主要根据条件dataSource.isContiguous() || !config.enablePlaceholders分别创建ContiguousPagedList和TiledPagedList
- isContiguous其实只是区分三个自定义DataSource类型而已,PositionalDataSource创建TiledPagedList,其他都是创建ContiguousPagedList
- 3、ContiguousPagedList构造源码分析
TiledPagedList(@NonNull PositionalDataSource<T> dataSource, @NonNull Executor mainThreadExecutor, @NonNull Executor backgroundThreadExecutor, @Nullable BoundaryCallback<T> boundaryCallback, @NonNull Config config, int position) {
super(new PagedStorage(), mainThreadExecutor, backgroundThreadExecutor, boundaryCallback, config);
this.mDataSource = dataSource;
int pageSize = this.mConfig.pageSize;
this.mLastLoad = position;
if (this.mDataSource.isInvalid()) {
this.detach();
} else {
int firstLoadSize = Math.max(this.mConfig.initialLoadSizeHint / pageSize, 2) * pageSize;
int idealStart = position - firstLoadSize / 2;
int roundedPageStart = Math.max(0, idealStart / pageSize * pageSize);
//这里mDataSource实际是PositionDataSource
this.mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize, pageSize, this.mMainThreadExecutor, this.mReceiver);
}
}
void dispatchLoadInitial(@Nullable Integer position, int initialLoadSize, int pageSize,
boolean enablePlaceholders, @NonNull Executor mainThreadExecutor,
@NonNull PageResult.Receiver<Value> receiver) {
//代码省略
mSource.dispatchLoadInitial(false, position, initialLoadSize,
pageSize, mainThreadExecutor, receiver);
}
final void dispatchLoadInitial(boolean acceptCount,
int requestedStartPosition, int requestedLoadSize, int pageSize,
@NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
LoadInitialCallbackImpl<T> callback =
new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
loadInitial(params, callback);
}
//最终实际调用的是自己创建的StudentDataSource的loadInitial方法
public abstract void loadInitial(
@NonNull LoadInitialParams params,
@NonNull LoadInitialCallback<T> callback);
- 创建PagedStorage实例,主要根据滑动的位置显示是否需要加载数据
- dispatchLoadInitial方法调用抽象函数loadInitial,前面我们知道loadInitial主要用于初始化数据的加载
paging数据是怎么显示的呢
我们回到自己实现loadInitial的方法
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Student> callback) {
callback.onResult(getStudents(0, 20), 0,1000);
}
细心的人肯定发现了callback就是LoadInitialCallbackImpl
public void onResult(@NonNull List<T> data, int position, int totalCount) {
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
LoadCallbackHelper.validateInitialLoadParams(data, position, totalCount);
if (mCountingEnabled) {
//这里实际是false
int trailingUnloadedCount = totalCount - position - data.size();
mCallbackHelper.dispatchResultToReceiver(
new PageResult<>(data, position, trailingUnloadedCount, 0));
} else {
//所以走到这里
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, position));
}
}
}
void dispatchResultToReceiver(final @NonNull PageResult<T> result) {
Executor executor;
if (executor != null) {
executor.execute(new Runnable() {
@Override
public void run() {
mReceiver.onPageResult(mResultType, result);
}
});
} else {
mReceiver.onPageResult(mResultType, result);
}
}
Receiver<T> mReceiver = new Receiver<T>() {
@AnyThread
public void onPageResult(int type, @NonNull PageResult<T> pageResult) {
if (pageResult.isInvalid()) {
TiledPagedList.this.detach();
} else if (!TiledPagedList.this.isDetached()) {
if (type != 0 && type != 3) {
throw new IllegalArgumentException("unexpected resultType" + type);
} else {
List<T> page = pageResult.page;
if (TiledPagedList.this.mStorage.getPageCount() == 0) {
TiledPagedList.this.mStorage.initAndSplit(pageResult.leadingNulls, page, pageResult.trailingNulls, pageResult.positionOffset, TiledPagedList.this.mConfig.pageSize, TiledPagedList.this);
}
}
}
}
};
- PageResult的三个数据类型分别对应ItemKeyDataSource的三个方法
loadInitial:对应初始化状态PageResult.INIT
loadBefore:对应初始化状态PagerResult.PREPEND
loadAfter:对应初始化PageResultAPPEND
- 2、init源码分析
void initAndSplit(int leadingNulls, @NonNull List<T> multiPageList,
int trailingNulls, int positionOffset, int pageSize, @NonNull Callback callback) {
callback.onInitialized(size());
}
public void onInitialized(int count) {
notifyInserted(0, count);
}
void notifyInserted(int position, int count) {
if (count != 0) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
final Callback callback = mCallbacks.get(i).get();
if (callback != null) {
callback.onInserted(position, count);
}
}
}
}
- 加载的数据保存在PagedStorage中,并记录加载的位置信息
- 加载完成后根据数据的变化,回调callback.onInserted()通知数据改变的数量和位置
public class AsyncPagedListDiffer<T> {
public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mUpdateCallback = new AdapterListUpdateCallback(adapter);
mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build();
}
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);
}
}
this.mPagedListCallback = new NamelessClass_1();
this.mUpdateCallback = listUpdateCallback;
this.mConfig = config;
}
}
public void onInserted(int position, int count) {
mAdapter.notifyItemRangeInserted(position, count);
}
最终实际调用的RecycleView的adapter的notifyItemRangeInserted方法,至此paging源码分析已全部分析完了
网友评论